Esempio n. 1
0
static const char *
_get_tags_as_string (const void *ctx, notmuch_message_t *message)
{
    notmuch_tags_t *tags;
    int first = 1;
    const char *tag;
    char *result;

    result = talloc_strdup (ctx, "");
    if (result == NULL)
        return NULL;

    for (tags = notmuch_message_get_tags (message);
            notmuch_tags_valid (tags);
            notmuch_tags_move_to_next (tags))
    {
        tag = notmuch_tags_get (tags);

        result = talloc_asprintf_append (result, "%s%s",
                                         first ? "" : " ", tag);
        first = 0;
    }

    return result;
}
Esempio n. 2
0
static void
format_message_json (const void *ctx, notmuch_message_t *message)
{
    notmuch_tags_t *tags;
    int first = 1;
    void *ctx_quote = talloc_new (ctx);
    time_t date;
    const char *relative_date;

    date = notmuch_message_get_date (message);
    relative_date = notmuch_time_relative_date (ctx, date);

    printf ("\"id\": %s, \"match\": %s, \"excluded\": %s, \"filename\": %s, \"timestamp\": %ld, \"date_relative\": \"%s\", \"tags\": [",
            json_quote_str (ctx_quote, notmuch_message_get_message_id (message)),
            notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? "true" : "false",
            notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? "true" : "false",
            json_quote_str (ctx_quote, notmuch_message_get_filename (message)),
            date, relative_date);

    for (tags = notmuch_message_get_tags (message);
            notmuch_tags_valid (tags);
            notmuch_tags_move_to_next (tags))
    {
        printf("%s%s", first ? "" : ",",
               json_quote_str (ctx_quote, notmuch_tags_get (tags)));
        first = 0;
    }
    printf("], ");
    talloc_free (ctx_quote);
}
Esempio n. 3
0
static int update_header_tags(HEADER *h, notmuch_message_t *msg)
{
	struct nm_hdrdata *data = h->data;
	notmuch_tags_t *tags;
	char *tstr = NULL, *p;
	size_t sz = 0;

	dprint(2, (debugfile, "nm: tags update requested (%s)\n", h->env->message_id));

	for (tags = notmuch_message_get_tags(msg);
	     tags && notmuch_tags_valid(tags);
	     notmuch_tags_move_to_next(tags)) {

		const char *t = notmuch_tags_get(tags);
		size_t xsz = t ? strlen(t) : 0;

		if (!xsz)
			continue;

		if (NotmuchHiddenTags) {
			p = strstr(NotmuchHiddenTags, t);

			if (p && (p == NotmuchHiddenTags
				  || *(p - 1) == ','
				  || *(p - 1) == ' ')
			    && (*(p + xsz) == '\0'
				  || *(p + xsz) == ','
				  || *(p + xsz) == ' '))
				continue;
		}

		safe_realloc(&tstr, sz + (sz ? 1 : 0) + xsz + 1);
		p = tstr + sz;
		if (sz) {
			*p++ = ' ';
			sz++;
		}
		memcpy(p, t, xsz + 1);
		sz += xsz;
	}

	if (data->tags && tstr && strcmp(data->tags, tstr) == 0) {
		FREE(&tstr);
		dprint(2, (debugfile, "nm: tags unchanged\n"));
		return 1;
	}

	FREE(&data->tags);
	data->tags = tstr;
	dprint(2, (debugfile, "nm: new tags: '%s'\n", tstr));
	return 0;
}
Esempio n. 4
0
static int
do_search_tags (notmuch_database_t *notmuch,
		const search_format_t *format,
		notmuch_query_t *query)
{
    notmuch_messages_t *messages = NULL;
    notmuch_tags_t *tags;
    const char *tag;
    int first_tag = 1;

    /* Special-case query of "*" for better performance. */
    if (strcmp (notmuch_query_get_query_string (query), "*") == 0) {
	tags = notmuch_database_get_all_tags (notmuch);
    } else {
	messages = notmuch_query_search_messages (query);
	if (messages == NULL)
	    return 1;

	tags = notmuch_messages_collect_tags (messages);
    }
    if (tags == NULL)
	return 1;

    fputs (format->results_start, stdout);

    for (;
	 notmuch_tags_valid (tags);
	 notmuch_tags_move_to_next (tags))
    {
	tag = notmuch_tags_get (tags);

	if (! first_tag)
	    fputs (format->item_sep, stdout);

	format->item_id (tags, "", tag);

	first_tag = 0;
    }

    notmuch_tags_destroy (tags);

    if (messages)
	notmuch_messages_destroy (messages);

    if (first_tag)
	fputs (format->results_null, stdout);
    else
	fputs (format->results_end, stdout);

    return 0;
}
Esempio n. 5
0
static int
do_search_tags (const search_context_t *ctx)
{
    notmuch_messages_t *messages = NULL;
    notmuch_tags_t *tags;
    const char *tag;
    sprinter_t *format = ctx->format;
    notmuch_query_t *query = ctx->query;
    notmuch_database_t *notmuch = ctx->notmuch;

    /* should the following only special case if no excluded terms
     * specified? */

    /* Special-case query of "*" for better performance. */
    if (strcmp (notmuch_query_get_query_string (query), "*") == 0) {
	tags = notmuch_database_get_all_tags (notmuch);
    } else {
	notmuch_status_t status;
	status = notmuch_query_search_messages_st (query, &messages);
	if (print_status_query ("notmuch search", query, status))
	    return 1;

	tags = notmuch_messages_collect_tags (messages);
    }
    if (tags == NULL)
	return 1;

    format->begin_list (format);

    for (;
	 notmuch_tags_valid (tags);
	 notmuch_tags_move_to_next (tags))
    {
	tag = notmuch_tags_get (tags);

	format->string (format, tag);
	format->separator (format);

    }

    notmuch_tags_destroy (tags);

    if (messages)
	notmuch_messages_destroy (messages);

    format->end (format);

    return 0;
}
Esempio n. 6
0
/*
 * call-seq: TAGS.each {|item| block } => TAGS
 *
 * Calls +block+ once for each element in +self+, passing that element as a
 * parameter.
 */
VALUE
notmuch_rb_tags_each (VALUE self)
{
    const char *tag;
    notmuch_tags_t *tags;

    Data_Get_Notmuch_Tags (self, tags);

    for (; notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) {
	tag = notmuch_tags_get (tags);
	rb_yield (rb_str_new2 (tag));
    }

    return self;
}
Esempio n. 7
0
/* Emit a sequence of key/value pairs for the metadata of message.
 * The caller should begin a map before calling this. */
static void
format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
{
    /* Any changes to the JSON or S-Expression format should be
     * reflected in the file devel/schemata. */

    void *local = talloc_new (NULL);
    notmuch_tags_t *tags;
    time_t date;
    const char *relative_date;

    sp->map_key (sp, "id");
    sp->string (sp, notmuch_message_get_message_id (message));

    sp->map_key (sp, "match");
    sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH));

    sp->map_key (sp, "excluded");
    sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED));

    sp->map_key (sp, "filename");
    sp->string (sp, notmuch_message_get_filename (message));

    sp->map_key (sp, "timestamp");
    date = notmuch_message_get_date (message);
    sp->integer (sp, date);

    sp->map_key (sp, "date_relative");
    relative_date = notmuch_time_relative_date (local, date);
    sp->string (sp, relative_date);

    sp->map_key (sp, "tags");
    sp->begin_list (sp);
    for (tags = notmuch_message_get_tags (message);
	 notmuch_tags_valid (tags);
	 notmuch_tags_move_to_next (tags))
	sp->string (sp, notmuch_tags_get (tags));
    sp->end (sp);

    talloc_free (local);
}
Esempio n. 8
0
notmuch_tags_t *
notmuch_messages_collect_tags (notmuch_messages_t *messages)
{
    notmuch_string_list_t *tags;
    notmuch_tags_t *msg_tags;
    notmuch_message_t *msg;
    GHashTable *htable;
    GList *keys, *l;
    const char *tag;

    tags = _notmuch_string_list_create (messages);
    if (tags == NULL) return NULL;

    htable = g_hash_table_new_full (g_str_hash, g_str_equal, free, NULL);

    while ((msg = notmuch_messages_get (messages))) {
	msg_tags = notmuch_message_get_tags (msg);
	while ((tag = notmuch_tags_get (msg_tags))) {
	    g_hash_table_insert (htable, xstrdup (tag), NULL);
	    notmuch_tags_move_to_next (msg_tags);
	}
	notmuch_tags_destroy (msg_tags);
	notmuch_message_destroy (msg);
	notmuch_messages_move_to_next (messages);
    }

    keys = g_hash_table_get_keys (htable);
    for (l = keys; l; l = l->next) {
	_notmuch_string_list_append (tags, (char *)l->data);
    }

    g_list_free (keys);
    g_hash_table_destroy (htable);

    _notmuch_string_list_sort (tags);
    return _notmuch_tags_create (messages, tags);
}
Esempio n. 9
0
static int
do_search_threads (const search_format_t *format,
		   notmuch_query_t *query,
		   notmuch_sort_t sort,
		   output_t output)
{
    notmuch_thread_t *thread;
    notmuch_threads_t *threads;
    notmuch_tags_t *tags;
    time_t date;
    int first_thread = 1;

    threads = notmuch_query_search_threads (query);
    if (threads == NULL)
	return 1;

    fputs (format->results_start, stdout);

    for (;
	 notmuch_threads_valid (threads);
	 notmuch_threads_move_to_next (threads))
    {
	int first_tag = 1;

	if (! first_thread)
	    fputs (format->item_sep, stdout);

	thread = notmuch_threads_get (threads);

	if (output == OUTPUT_THREADS) {
	    format->item_id (thread, "thread:",
			     notmuch_thread_get_thread_id (thread));
	} else { /* output == OUTPUT_SUMMARY */
	    fputs (format->item_start, stdout);

	    if (sort == NOTMUCH_SORT_OLDEST_FIRST)
		date = notmuch_thread_get_oldest_date (thread);
	    else
		date = notmuch_thread_get_newest_date (thread);

	    format->thread_summary (thread,
				    notmuch_thread_get_thread_id (thread),
				    date,
				    notmuch_thread_get_matched_messages (thread),
				    notmuch_thread_get_total_messages (thread),
				    notmuch_thread_get_authors (thread),
				    notmuch_thread_get_subject (thread));

	    fputs (format->tag_start, stdout);

	    for (tags = notmuch_thread_get_tags (thread);
		 notmuch_tags_valid (tags);
		 notmuch_tags_move_to_next (tags))
	    {
		if (! first_tag)
		    fputs (format->tag_sep, stdout);
		printf (format->tag, notmuch_tags_get (tags));
		first_tag = 0;
	    }

	    fputs (format->tag_end, stdout);

	    fputs (format->item_end, stdout);
	}

	first_thread = 0;

	notmuch_thread_destroy (thread);
    }

    fputs (format->results_end, stdout);

    return 0;
}
Esempio n. 10
0
static int update_header_tags(HEADER *h, notmuch_message_t *msg)
{
    struct nm_hdrdata *data = h->data;
    notmuch_tags_t *tags;
    char *tstr = NULL, *ttstr = NULL;
    struct nm_hdrtag *tag_list = NULL, *tmp;

    dprint(2, (debugfile, "nm: tags update requested (%s)\n", data->virtual_id));

    for (tags = notmuch_message_get_tags(msg);
            tags && notmuch_tags_valid(tags);
            notmuch_tags_move_to_next(tags)) {

        const char *t = notmuch_tags_get(tags);
        const char *tt = NULL;

        if (!t || !*t)
            continue;

        tt = hash_find(TagTransforms, t);
        if (!tt)
            tt = t;

        /* tags list contains all tags */
        tmp = safe_calloc(1, sizeof(*tmp));
        tmp->tag = safe_strdup(t);
        tmp->transformed = safe_strdup(tt);
        tmp->next = tag_list;
        tag_list = tmp;

        /* filter out hidden tags */
        if (NotmuchHiddenTags) {
            char *p = strstr(NotmuchHiddenTags, t);
            size_t xsz = p ? strlen(t) : 0;

            if (p && (p == NotmuchHiddenTags
                      || *(p - 1) == ','
                      || *(p - 1) == ' ')
                    && (*(p + xsz) == '\0'
                        || *(p + xsz) == ','
                        || *(p + xsz) == ' '))
                continue;
        }

        /* expand the transformed tag string */
        append_str_item(&ttstr, tt, ' ');

        /* expand the un-transformed tag string */
        append_str_item(&tstr, t, ' ');
    }

    free_tag_list(&data->tag_list);
    data->tag_list = tag_list;

    if (data->tags && tstr && strcmp(data->tags, tstr) == 0) {
        FREE(&tstr);
        FREE(&ttstr);
        dprint(2, (debugfile, "nm: tags unchanged\n"));
        return 1;
    }

    /* free old version */
    FREE(&data->tags);
    FREE(&data->tags_transformed);

    /* new version */
    data->tags = tstr;
    dprint(2, (debugfile, "nm: new tags: '%s'\n", tstr));

    data->tags_transformed = ttstr;
    dprint(2, (debugfile, "nm: new tag transforms: '%s'\n", ttstr));

    return 0;
}
Esempio n. 11
0
static int
database_dump_file (notmuch_database_t *notmuch, gzFile output,
		    const char *query_str, int output_format)
{
    notmuch_query_t *query;
    notmuch_messages_t *messages;
    notmuch_message_t *message;
    notmuch_tags_t *tags;

    if (! query_str)
	query_str = "";

    query = notmuch_query_create (notmuch, query_str);
    if (query == NULL) {
	fprintf (stderr, "Out of memory\n");
	return EXIT_FAILURE;
    }
    /* Don't ask xapian to sort by Message-ID. Xapian optimizes returning the
     * first results quickly at the expense of total time.
     */
    notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);

    char *buffer = NULL;
    size_t buffer_size = 0;

    for (messages = notmuch_query_search_messages (query);
	 notmuch_messages_valid (messages);
	 notmuch_messages_move_to_next (messages)) {
	int first = 1;
	const char *message_id;

	message = notmuch_messages_get (messages);
	message_id = notmuch_message_get_message_id (message);

	if (output_format == DUMP_FORMAT_BATCH_TAG &&
	    strchr (message_id, '\n')) {
	    /* This will produce a line break in the output, which
	     * would be difficult to handle in tools.  However, it's
	     * also impossible to produce an email containing a line
	     * break in a message ID because of unfolding, so we can
	     * safely disallow it. */
	    fprintf (stderr, "Warning: skipping message id containing line break: \"%s\"\n", message_id);
	    notmuch_message_destroy (message);
	    continue;
	}

	if (output_format == DUMP_FORMAT_SUP) {
	    gzprintf (output, "%s (", message_id);
	}

	for (tags = notmuch_message_get_tags (message);
	     notmuch_tags_valid (tags);
	     notmuch_tags_move_to_next (tags)) {
	    const char *tag_str = notmuch_tags_get (tags);

	    if (! first)
		gzputs (output, " ");

	    first = 0;

	    if (output_format == DUMP_FORMAT_SUP) {
		gzputs (output, tag_str);
	    } else {
		if (hex_encode (notmuch, tag_str,
				&buffer, &buffer_size) != HEX_SUCCESS) {
		    fprintf (stderr, "Error: failed to hex-encode tag %s\n",
			     tag_str);
		    return EXIT_FAILURE;
		}
		gzprintf (output, "+%s", buffer);
	    }
	}

	if (output_format == DUMP_FORMAT_SUP) {
	    gzputs (output, ")\n");
	} else {
	    if (make_boolean_term (notmuch, "id", message_id,
				   &buffer, &buffer_size)) {
		    fprintf (stderr, "Error quoting message id %s: %s\n",
			     message_id, strerror (errno));
		    return EXIT_FAILURE;
	    }
	    gzprintf (output, " -- %s\n", buffer);
	}

	notmuch_message_destroy (message);
    }

    notmuch_query_destroy (query);

    return EXIT_SUCCESS;
}
Esempio n. 12
0
static int
do_search_threads (search_context_t *ctx)
{
    notmuch_thread_t *thread;
    notmuch_threads_t *threads;
    notmuch_tags_t *tags;
    sprinter_t *format = ctx->format;
    time_t date;
    int i;
    notmuch_status_t status;

    if (ctx->offset < 0) {
	unsigned count;
	notmuch_status_t status;
	status = notmuch_query_count_threads_st (ctx->query, &count);
	if (print_status_query ("notmuch search", ctx->query, status))
	    return 1;

	ctx->offset += count;
	if (ctx->offset < 0)
	    ctx->offset = 0;
    }

    status = notmuch_query_search_threads_st (ctx->query, &threads);
    if (print_status_query("notmuch search", ctx->query, status))
	return 1;

    format->begin_list (format);

    for (i = 0;
	 notmuch_threads_valid (threads) && (ctx->limit < 0 || i < ctx->offset + ctx->limit);
	 notmuch_threads_move_to_next (threads), i++)
    {
	thread = notmuch_threads_get (threads);

	if (i < ctx->offset) {
	    notmuch_thread_destroy (thread);
	    continue;
	}

	if (ctx->output == OUTPUT_THREADS) {
	    format->set_prefix (format, "thread");
	    format->string (format,
			    notmuch_thread_get_thread_id (thread));
	    format->separator (format);
	} else { /* output == OUTPUT_SUMMARY */
	    void *ctx_quote = talloc_new (thread);
	    const char *authors = notmuch_thread_get_authors (thread);
	    const char *subject = notmuch_thread_get_subject (thread);
	    const char *thread_id = notmuch_thread_get_thread_id (thread);
	    int matched = notmuch_thread_get_matched_messages (thread);
	    int total = notmuch_thread_get_total_messages (thread);
	    const char *relative_date = NULL;
	    notmuch_bool_t first_tag = TRUE;

	    format->begin_map (format);

	    if (ctx->sort == NOTMUCH_SORT_OLDEST_FIRST)
		date = notmuch_thread_get_oldest_date (thread);
	    else
		date = notmuch_thread_get_newest_date (thread);

	    relative_date = notmuch_time_relative_date (ctx_quote, date);

	    if (format->is_text_printer) {
                /* Special case for the text formatter */
		printf ("thread:%s %12s [%d/%d] %s; %s (",
			thread_id,
			relative_date,
			matched,
			total,
			sanitize_string (ctx_quote, authors),
			sanitize_string (ctx_quote, subject));
	    } else { /* Structured Output */
		format->map_key (format, "thread");
		format->string (format, thread_id);
		format->map_key (format, "timestamp");
		format->integer (format, date);
		format->map_key (format, "date_relative");
		format->string (format, relative_date);
		format->map_key (format, "matched");
		format->integer (format, matched);
		format->map_key (format, "total");
		format->integer (format, total);
		format->map_key (format, "authors");
		format->string (format, authors);
		format->map_key (format, "subject");
		format->string (format, subject);
		if (notmuch_format_version >= 2) {
		    char *matched_query, *unmatched_query;
		    if (get_thread_query (thread, &matched_query,
					  &unmatched_query) < 0) {
			fprintf (stderr, "Out of memory\n");
			return 1;
		    }
		    format->map_key (format, "query");
		    format->begin_list (format);
		    if (matched_query)
			format->string (format, matched_query);
		    else
			format->null (format);
		    if (unmatched_query)
			format->string (format, unmatched_query);
		    else
			format->null (format);
		    format->end (format);
		}
	    }

	    talloc_free (ctx_quote);

	    format->map_key (format, "tags");
	    format->begin_list (format);

	    for (tags = notmuch_thread_get_tags (thread);
		 notmuch_tags_valid (tags);
		 notmuch_tags_move_to_next (tags))
	    {
		const char *tag = notmuch_tags_get (tags);

		if (format->is_text_printer) {
                  /* Special case for the text formatter */
		    if (first_tag)
			first_tag = FALSE;
		    else
			fputc (' ', stdout);
		    fputs (tag, stdout);
		} else { /* Structured Output */
		    format->string (format, tag);
		}
	    }

	    if (format->is_text_printer)
		printf (")");

	    format->end (format);
	    format->end (format);
	    format->separator (format);
	}

	notmuch_thread_destroy (thread);
    }

    format->end (format);

    return 0;
}
Esempio n. 13
0
static int
tag_message (notmuch_database_t *notmuch, const char *message_id,
	     char *file_tags, notmuch_bool_t remove_all,
	     notmuch_bool_t synchronize_flags)
{
    notmuch_status_t status;
    notmuch_tags_t *db_tags;
    char *db_tags_str;
    notmuch_message_t *message = NULL;
    const char *tag;
    char *next;
    int ret = 0;

    status = notmuch_database_find_message (notmuch, message_id, &message);
    if (status || message == NULL) {
	fprintf (stderr, "Warning: Cannot apply tags to %smessage: %s\n",
		 message ? "" : "missing ", message_id);
	if (status)
	    fprintf (stderr, "%s\n", notmuch_status_to_string(status));
	return 1;
    }

    /* In order to detect missing messages, this check/optimization is
     * intentionally done *after* first finding the message. */
    if (!remove_all && (file_tags == NULL || *file_tags == '\0'))
	goto DONE;

    db_tags_str = NULL;
    for (db_tags = notmuch_message_get_tags (message);
	 notmuch_tags_valid (db_tags);
	 notmuch_tags_move_to_next (db_tags)) {
	tag = notmuch_tags_get (db_tags);

	if (db_tags_str)
	    db_tags_str = talloc_asprintf_append (db_tags_str, " %s", tag);
	else
	    db_tags_str = talloc_strdup (message, tag);
    }

    if (((file_tags == NULL || *file_tags == '\0') &&
	 (db_tags_str == NULL || *db_tags_str == '\0')) ||
	(file_tags && db_tags_str && strcmp (file_tags, db_tags_str) == 0))
	goto DONE;

    notmuch_message_freeze (message);

    if (remove_all)
	notmuch_message_remove_all_tags (message);

    next = file_tags;
    while (next) {
	tag = strsep (&next, " ");
	if (*tag == '\0')
	    continue;
	status = notmuch_message_add_tag (message, tag);
	if (status) {
	    fprintf (stderr, "Error applying tag %s to message %s:\n",
		     tag, message_id);
	    fprintf (stderr, "%s\n", notmuch_status_to_string (status));
	    ret = 1;
	}
    }

    notmuch_message_thaw (message);

    if (synchronize_flags)
	notmuch_message_tags_to_maildir_flags (message);

DONE:
    if (message)
	notmuch_message_destroy (message);

    return ret;
}
Esempio n. 14
0
static int
makes_changes (notmuch_message_t *message,
	       tag_op_list_t *list,
	       tag_op_flag_t flags)
{

    size_t i;

    notmuch_tags_t *tags;
    notmuch_bool_t changes = FALSE;

    /* First, do we delete an existing tag? */
    changes = FALSE;
    for (tags = notmuch_message_get_tags (message);
	 ! changes && notmuch_tags_valid (tags);
	 notmuch_tags_move_to_next (tags)) {
	const char *cur_tag = notmuch_tags_get (tags);
	int last_op =  (flags & TAG_FLAG_REMOVE_ALL) ? -1 : 0;

	/* scan backwards to get last operation */
	i = list->count;
	while (i > 0) {
	    i--;
	    if (strcmp (cur_tag, list->ops[i].tag) == 0) {
		last_op = list->ops[i].remove ? -1 : 1;
		break;
	    }
	}

	changes = (last_op == -1);
    }
    notmuch_tags_destroy (tags);

    if (changes)
	return TRUE;

    /* Now check for adding new tags */
    for (i = 0; i < list->count; i++) {
	notmuch_bool_t exists = FALSE;

	if (list->ops[i].remove)
	    continue;

	for (tags = notmuch_message_get_tags (message);
	     notmuch_tags_valid (tags);
	     notmuch_tags_move_to_next (tags)) {
	    const char *cur_tag = notmuch_tags_get (tags);
	    if (strcmp (cur_tag, list->ops[i].tag) == 0) {
		exists = TRUE;
		break;
	    }
	}
	notmuch_tags_destroy (tags);

	/* the following test is conservative,
	 * in the sense it ignores cases like +foo ... -foo
	 * but this is OK from a correctness point of view
	 */
	if (! exists)
	    return TRUE;
    }
    return FALSE;

}