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; }
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); }
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; }
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; }
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; }
/* * 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; }
/* 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); }
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); }
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; }
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; }
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; }
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; }
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; }
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; }