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 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; }
Tags *Message::tags() { if(!m_tags) { if(!libnotmuch_message) return 0; notmuch_tags_t* libnotmuch_tags = notmuch_message_get_tags(libnotmuch_message); m_tags = new Tags(libnotmuch_tags, this); // m_tags.loadAll() } return m_tags; }
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; }
/* 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 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 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; }