X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=lib%2Fmessage.cc;h=fca99082a8483cba035fba37f9a1bc1ebaab0b4e;hp=4677f1df1565bc6df7cff75d459f7d063b10139a;hb=HEAD;hpb=9eebae3da4510a671cbbb2f476c2dc04099b4415 diff --git a/lib/message.cc b/lib/message.cc index 4677f1df..46638f80 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -13,30 +13,36 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/ . + * along with this program. If not, see https://www.gnu.org/licenses/ . * * Author: Carl Worth */ #include "notmuch-private.h" #include "database-private.h" +#include "message-private.h" #include #include -struct visible _notmuch_message { +struct _notmuch_message { notmuch_database_t *notmuch; Xapian::docid doc_id; int frozen; char *message_id; char *thread_id; + size_t thread_depth; char *in_reply_to; notmuch_string_list_t *tag_list; notmuch_string_list_t *filename_term_list; notmuch_string_list_t *filename_list; + char *maildir_flags; char *author; notmuch_message_file_t *message_file; + notmuch_string_list_t *property_term_list; + notmuch_string_map_t *property_map; + notmuch_string_list_t *reference_list; notmuch_message_list_t *replies; unsigned long flags; /* For flags that are initialized on-demand, lazy_flags indicates @@ -44,7 +50,10 @@ struct visible _notmuch_message { unsigned long lazy_flags; /* Message document modified since last sync */ - notmuch_bool_t modified; + bool modified; + + /* last view of database the struct is synced with */ + unsigned long last_view; Xapian::Document doc; Xapian::termcount termpos; @@ -55,16 +64,16 @@ struct visible _notmuch_message { struct maildir_flag_tag { char flag; const char *tag; - notmuch_bool_t inverse; + bool inverse; }; /* ASCII ordered table of Maildir flags and associated tags */ -static struct maildir_flag_tag flag2tag[] = { - { 'D', "draft", FALSE}, - { 'F', "flagged", FALSE}, - { 'P', "passed", FALSE}, - { 'R', "replied", FALSE}, - { 'S', "unread", TRUE } +static const struct maildir_flag_tag flag2tag[] = { + { 'D', "draft", false }, + { 'F', "flagged", false }, + { 'P', "passed", false }, + { 'R', "replied", false }, + { 'S', "unread", true } }; /* We end up having to call the destructor explicitly because we had @@ -81,6 +90,20 @@ _notmuch_message_destructor (notmuch_message_t *message) return 0; } +#define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error) + +static void +_log_xapian_exception (const char *where, notmuch_message_t *message, const Xapian::Error error) +{ + notmuch_database_t *notmuch = notmuch_message_get_database (message); + + _notmuch_database_log (notmuch, + "A Xapian exception occurred at %s: %s\n", + where, + error.get_msg ().c_str ()); + notmuch->exception_reported = true; +} + static notmuch_message_t * _notmuch_message_create_for_document (const void *talloc_owner, notmuch_database_t *notmuch, @@ -107,6 +130,12 @@ _notmuch_message_create_for_document (const void *talloc_owner, message->flags = 0; message->lazy_flags = 0; + /* the message is initially not synchronized with Xapian */ + message->last_view = 0; + + /* Calculated after the thread structure is computed */ + message->thread_depth = 0; + /* Each of these will be lazily created as needed. */ message->message_id = NULL; message->thread_id = NULL; @@ -114,8 +143,12 @@ _notmuch_message_create_for_document (const void *talloc_owner, message->tag_list = NULL; message->filename_term_list = NULL; message->filename_list = NULL; + message->maildir_flags = NULL; message->message_file = NULL; message->author = NULL; + message->property_term_list = NULL; + message->property_map = NULL; + message->reference_list = NULL; message->replies = _notmuch_message_list_create (message); if (unlikely (message->replies == NULL)) { @@ -136,6 +169,7 @@ _notmuch_message_create_for_document (const void *talloc_owner, message->doc = doc; message->termpos = 0; + message->modified = false; return message; } @@ -244,7 +278,7 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch, return NULL; } - if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) + if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY) INTERNAL_ERROR ("Failure to ensure database is writable."); try { @@ -255,9 +289,10 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch, doc_id = _notmuch_database_generate_doc_id (notmuch); } catch (const Xapian::Error &error) { - _notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred creating message: %s\n", - error.get_msg().c_str()); - notmuch->exception_reported = TRUE; + _notmuch_database_log (notmuch, + "A Xapian exception occurred creating message: %s\n", + error.get_msg ().c_str ()); + notmuch->exception_reported = true; *status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION; return NULL; } @@ -287,10 +322,11 @@ _notmuch_message_get_term (notmuch_message_t *message, return NULL; const std::string &term = *i; - if (strncmp (term.c_str(), prefix, prefix_len)) + + if (strncmp (term.c_str (), prefix, prefix_len)) return NULL; - value = talloc_strdup (message, term.c_str() + prefix_len); + value = talloc_strdup (message, term.c_str () + prefix_len); #if DEBUG_DATABASE_SANITY i++; @@ -305,82 +341,119 @@ _notmuch_message_get_term (notmuch_message_t *message, return value; } -void -_notmuch_message_ensure_metadata (notmuch_message_t *message) +static void +_notmuch_message_ensure_metadata (notmuch_message_t *message, void *field) { Xapian::TermIterator i, end; + + if (field && (message->last_view >= message->notmuch->view)) + return; + const char *thread_prefix = _find_prefix ("thread"), - *tag_prefix = _find_prefix ("tag"), - *id_prefix = _find_prefix ("id"), - *type_prefix = _find_prefix ("type"), - *filename_prefix = _find_prefix ("file-direntry"), - *replyto_prefix = _find_prefix ("replyto"); + *tag_prefix = _find_prefix ("tag"), + *id_prefix = _find_prefix ("id"), + *type_prefix = _find_prefix ("type"), + *filename_prefix = _find_prefix ("file-direntry"), + *property_prefix = _find_prefix ("property"), + *reference_prefix = _find_prefix ("reference"), + *replyto_prefix = _find_prefix ("replyto"); /* We do this all in a single pass because Xapian decompresses the * term list every time you iterate over it. Thus, while this is * slightly more costly than looking up individual fields if only * one field of the message object is actually used, it's a huge * win as more fields are used. */ + for (int count = 0; count < 3; count++) { + try { + i = message->doc.termlist_begin (); + end = message->doc.termlist_end (); + + /* Get thread */ + if (! message->thread_id) + message->thread_id = + _notmuch_message_get_term (message, i, end, thread_prefix); + + /* Get tags */ + assert (strcmp (thread_prefix, tag_prefix) < 0); + if (! message->tag_list) { + message->tag_list = + _notmuch_database_get_terms_with_prefix (message, i, end, + tag_prefix); + _notmuch_string_list_sort (message->tag_list); + } - i = message->doc.termlist_begin (); - end = message->doc.termlist_end (); - - /* Get thread */ - if (!message->thread_id) - message->thread_id = - _notmuch_message_get_term (message, i, end, thread_prefix); - - /* Get tags */ - assert (strcmp (thread_prefix, tag_prefix) < 0); - if (!message->tag_list) { - message->tag_list = - _notmuch_database_get_terms_with_prefix (message, i, end, - tag_prefix); - _notmuch_string_list_sort (message->tag_list); - } - - /* Get id */ - assert (strcmp (tag_prefix, id_prefix) < 0); - if (!message->message_id) - message->message_id = - _notmuch_message_get_term (message, i, end, id_prefix); - - /* Get document type */ - assert (strcmp (id_prefix, type_prefix) < 0); - if (! NOTMUCH_TEST_BIT (message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST)) { - i.skip_to (type_prefix); - /* "T" is the prefix "type" fields. See - * BOOLEAN_PREFIX_INTERNAL. */ - if (*i == "Tmail") - NOTMUCH_CLEAR_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST); - else if (*i == "Tghost") - NOTMUCH_SET_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST); - else - INTERNAL_ERROR ("Message without type term"); - NOTMUCH_SET_BIT (&message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST); - } - - /* Get filename list. Here we get only the terms. We lazily - * expand them to full file names when needed in - * _notmuch_message_ensure_filename_list. */ - assert (strcmp (type_prefix, filename_prefix) < 0); - if (!message->filename_term_list && !message->filename_list) - message->filename_term_list = - _notmuch_database_get_terms_with_prefix (message, i, end, - filename_prefix); - - /* Get reply to */ - assert (strcmp (filename_prefix, replyto_prefix) < 0); - if (!message->in_reply_to) - message->in_reply_to = - _notmuch_message_get_term (message, i, end, replyto_prefix); - /* It's perfectly valid for a message to have no In-Reply-To - * header. For these cases, we return an empty string. */ - if (!message->in_reply_to) - message->in_reply_to = talloc_strdup (message, ""); + /* Get id */ + assert (strcmp (tag_prefix, id_prefix) < 0); + if (! message->message_id) + message->message_id = + _notmuch_message_get_term (message, i, end, id_prefix); + + /* Get document type */ + assert (strcmp (id_prefix, type_prefix) < 0); + if (! NOTMUCH_TEST_BIT (message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST)) { + i.skip_to (type_prefix); + /* "T" is the prefix "type" fields. See + * BOOLEAN_PREFIX_INTERNAL. */ + if (*i == "Tmail") + NOTMUCH_CLEAR_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST); + else if (*i == "Tghost") + NOTMUCH_SET_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST); + else + INTERNAL_ERROR ("Message without type term"); + NOTMUCH_SET_BIT (&message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST); + } + + /* Get filename list. Here we get only the terms. We lazily + * expand them to full file names when needed in + * _notmuch_message_ensure_filename_list. */ + assert (strcmp (type_prefix, filename_prefix) < 0); + if (! message->filename_term_list && ! message->filename_list) + message->filename_term_list = + _notmuch_database_get_terms_with_prefix (message, i, end, + filename_prefix); + + + /* Get property terms. Mimic the setup with filenames above */ + assert (strcmp (filename_prefix, property_prefix) < 0); + if (! message->property_map && ! message->property_term_list) + message->property_term_list = + _notmuch_database_get_terms_with_prefix (message, i, end, + property_prefix); + + /* get references */ + assert (strcmp (property_prefix, reference_prefix) < 0); + if (! message->reference_list) { + message->reference_list = + _notmuch_database_get_terms_with_prefix (message, i, end, + reference_prefix); + } + + /* Get reply to */ + assert (strcmp (property_prefix, replyto_prefix) < 0); + if (! message->in_reply_to) + message->in_reply_to = + _notmuch_message_get_term (message, i, end, replyto_prefix); + + + /* It's perfectly valid for a message to have no In-Reply-To + * header. For these cases, we return an empty string. */ + if (! message->in_reply_to) + message->in_reply_to = talloc_strdup (message, ""); + + /* all the way without an exception */ + break; + } catch (const Xapian::DatabaseModifiedError &error) { + notmuch_status_t status = notmuch_database_reopen (message->notmuch, + NOTMUCH_DATABASE_MODE_READ_ONLY); + if (status != NOTMUCH_STATUS_SUCCESS) + INTERNAL_ERROR ("unhandled error from notmuch_database_reopen: %s\n", + notmuch_status_to_string (status)); + } + } + message->last_view = message->notmuch->view; } -static void +void _notmuch_message_invalidate_metadata (notmuch_message_t *message, const char *prefix_name) { @@ -405,6 +478,18 @@ _notmuch_message_invalidate_metadata (notmuch_message_t *message, message->filename_term_list = message->filename_list = NULL; } + if (strcmp ("property", prefix_name) == 0) { + + if (message->property_term_list) + talloc_free (message->property_term_list); + message->property_term_list = NULL; + + if (message->property_map) + talloc_unlink (message, message->property_map); + + message->property_map = NULL; + } + if (strcmp ("replyto", prefix_name) == 0) { talloc_free (message->in_reply_to); message->in_reply_to = NULL; @@ -420,9 +505,14 @@ _notmuch_message_get_doc_id (notmuch_message_t *message) const char * notmuch_message_get_message_id (notmuch_message_t *message) { - if (!message->message_id) - _notmuch_message_ensure_metadata (message); - if (!message->message_id) + try { + _notmuch_message_ensure_metadata (message, message->message_id); + } catch (const Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NULL; + } + + if (! message->message_id) INTERNAL_ERROR ("Message with document ID of %u has no message ID.\n", message->doc_id); return message->message_id; @@ -441,7 +531,7 @@ _notmuch_message_ensure_message_file (notmuch_message_t *message) return; message->message_file = _notmuch_message_file_open_ctx ( - _notmuch_message_database (message), message, filename); + notmuch_message_get_database (message), message, filename); } const char * @@ -467,13 +557,11 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header) * it could just mean we didn't record the header. */ if ((message->notmuch->features & NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES) || - ! value.empty()) + ! value.empty ()) return talloc_strdup (message, value.c_str ()); } catch (Xapian::Error &error) { - _notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred when reading header: %s\n", - error.get_msg().c_str()); - message->notmuch->exception_reported = TRUE; + LOG_XAPIAN_EXCEPTION (message, error); return NULL; } } @@ -496,17 +584,20 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header) const char * _notmuch_message_get_in_reply_to (notmuch_message_t *message) { - if (!message->in_reply_to) - _notmuch_message_ensure_metadata (message); + _notmuch_message_ensure_metadata (message, message->in_reply_to); return message->in_reply_to; } const char * notmuch_message_get_thread_id (notmuch_message_t *message) { - if (!message->thread_id) - _notmuch_message_ensure_metadata (message); - if (!message->thread_id) + try { + _notmuch_message_ensure_metadata (message, message->thread_id); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NULL; + } + if (! message->thread_id) INTERNAL_ERROR ("Message with document ID of %u has no thread ID.\n", message->doc_id); return message->thread_id; @@ -519,17 +610,98 @@ _notmuch_message_add_reply (notmuch_message_t *message, _notmuch_message_list_add_message (message->replies, reply); } +size_t +_notmuch_message_get_thread_depth (notmuch_message_t *message) +{ + return message->thread_depth; +} + +void +_notmuch_message_label_depths (notmuch_message_t *message, + size_t depth) +{ + message->thread_depth = depth; + + for (notmuch_messages_t *messages = _notmuch_messages_create (message->replies); + notmuch_messages_valid (messages); + notmuch_messages_move_to_next (messages)) { + notmuch_message_t *child = notmuch_messages_get (messages); + _notmuch_message_label_depths (child, depth + 1); + } +} + +const notmuch_string_list_t * +_notmuch_message_get_references (notmuch_message_t *message) +{ + _notmuch_message_ensure_metadata (message, message->reference_list); + return message->reference_list; +} + +static int +_cmpmsg (const void *pa, const void *pb) +{ + notmuch_message_t **a = (notmuch_message_t **) pa; + notmuch_message_t **b = (notmuch_message_t **) pb; + time_t time_a = notmuch_message_get_date (*a); + time_t time_b = notmuch_message_get_date (*b); + + if (time_a == time_b) + return 0; + else if (time_a < time_b) + return -1; + else + return 1; +} + +notmuch_message_list_t * +_notmuch_message_sort_subtrees (void *ctx, notmuch_message_list_t *list) +{ + + size_t count = 0; + size_t capacity = 16; + + if (! list) + return list; + + void *local = talloc_new (NULL); + notmuch_message_list_t *new_list = _notmuch_message_list_create (ctx); + notmuch_message_t **message_array = talloc_zero_array (local, notmuch_message_t *, capacity); + + for (notmuch_messages_t *messages = _notmuch_messages_create (list); + notmuch_messages_valid (messages); + notmuch_messages_move_to_next (messages)) { + notmuch_message_t *root = notmuch_messages_get (messages); + if (count >= capacity) { + capacity *= 2; + message_array = talloc_realloc (local, message_array, notmuch_message_t *, capacity); + } + message_array[count++] = root; + root->replies = _notmuch_message_sort_subtrees (root, root->replies); + } + + qsort (message_array, count, sizeof (notmuch_message_t *), _cmpmsg); + for (size_t i = 0; i < count; i++) { + _notmuch_message_list_add_message (new_list, message_array[i]); + } + + talloc_free (local); + talloc_free (list); + return new_list; +} + notmuch_messages_t * notmuch_message_get_replies (notmuch_message_t *message) { return _notmuch_messages_create (message->replies); } -static void +void _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix) { Xapian::TermIterator i; - size_t prefix_len = strlen (prefix); + size_t prefix_len = 0; + + prefix_len = strlen (prefix); while (1) { i = message->doc.termlist_begin (); @@ -542,21 +714,87 @@ _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix) try { message->doc.remove_term ((*i)); - message->modified = TRUE; + message->modified = true; } catch (const Xapian::InvalidArgumentError) { /* Ignore failure to remove non-existent term. */ } } + + _notmuch_message_invalidate_metadata (message, "property"); } + +/* Remove all terms generated by indexing, i.e. not tags or + * properties, along with any automatic tags*/ +/* According to Xapian API docs, none of these calls throw + * exceptions */ +static notmuch_private_status_t +_notmuch_message_remove_indexed_terms (notmuch_message_t *message) +{ + Xapian::TermIterator i; + + const std::string + id_prefix = _find_prefix ("id"), + property_prefix = _find_prefix ("property"), + tag_prefix = _find_prefix ("tag"), + type_prefix = _find_prefix ("type"); + + /* Make sure we have the data to restore to Xapian*/ + _notmuch_message_ensure_metadata (message, NULL); + + /* Empirically, it turns out to be faster to remove all the terms, + * and add back the ones we want. */ + message->doc.clear_terms (); + message->modified = true; + + /* still a mail message */ + message->doc.add_term (type_prefix + "mail"); + + /* Put back message-id */ + message->doc.add_term (id_prefix + message->message_id); + + /* Put back non-automatic tags */ + for (notmuch_tags_t *tags = notmuch_message_get_tags (message); + notmuch_tags_valid (tags); + notmuch_tags_move_to_next (tags)) { + + const char *tag = notmuch_tags_get (tags); + + if (strcmp (tag, "encrypted") != 0 && + strcmp (tag, "signed") != 0 && + strcmp (tag, "attachment") != 0) { + std::string term = tag_prefix + tag; + message->doc.add_term (term); + } + } + + /* Put back properties */ + notmuch_message_properties_t *list; + + for (list = notmuch_message_get_properties (message, "", false); + notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) { + std::string term = property_prefix + + notmuch_message_properties_key (list) + "=" + + notmuch_message_properties_value (list); + + message->doc.add_term (term); + } + + notmuch_message_properties_destroy (list); + + return NOTMUCH_PRIVATE_STATUS_SUCCESS; +} + + /* Return true if p points at "new" or "cur". */ -static bool is_maildir (const char *p) +static bool +is_maildir (const char *p) { return strcmp (p, "cur") == 0 || strcmp (p, "new") == 0; } /* Add "folder:" term for directory. */ -static notmuch_status_t +NODISCARD static notmuch_status_t _notmuch_message_add_folder_terms (notmuch_message_t *message, const char *directory) { @@ -592,22 +830,31 @@ _notmuch_message_add_folder_terms (notmuch_message_t *message, *folder = '\0'; } - _notmuch_message_add_term (message, "folder", folder); + if (notmuch_status_t status = COERCE_STATUS (_notmuch_message_add_term (message, "folder", + folder), + "adding folder term")) + return status; talloc_free (folder); + message->modified = true; return NOTMUCH_STATUS_SUCCESS; } #define RECURSIVE_SUFFIX "/**" /* Add "path:" terms for directory. */ -static notmuch_status_t +NODISCARD static notmuch_status_t _notmuch_message_add_path_terms (notmuch_message_t *message, const char *directory) { + notmuch_status_t status; + /* Add exact "path:" term. */ - _notmuch_message_add_term (message, "path", directory); + status = COERCE_STATUS (_notmuch_message_add_term (message, "path", directory), + "adding path term"); + if (status) + return status; if (strlen (directory)) { char *path, *p; @@ -620,7 +867,10 @@ _notmuch_message_add_path_terms (notmuch_message_t *message, for (p = path + strlen (path) - 1; p > path; p--) { if (*p == '/') { strcpy (p, RECURSIVE_SUFFIX); - _notmuch_message_add_term (message, "path", path); + status = COERCE_STATUS (_notmuch_message_add_term (message, "path", path), + "adding path term"); + if (status) + return status; } } @@ -628,7 +878,10 @@ _notmuch_message_add_path_terms (notmuch_message_t *message, } /* Recursive all-matching path:** for consistency. */ - _notmuch_message_add_term (message, "path", "**"); + status = COERCE_STATUS (_notmuch_message_add_term (message, "path", "**"), + "adding path term"); + if (status) + return status; return NOTMUCH_STATUS_SUCCESS; } @@ -647,6 +900,7 @@ _notmuch_message_add_directory_terms (void *ctx, notmuch_message_t *message) const char *direntry, *directory; char *colon; const std::string &term = *i; + notmuch_status_t term_status; /* Terminate loop at first term without desired prefix. */ if (strncmp (term.c_str (), direntry_prefix, direntry_prefix_len)) @@ -667,8 +921,13 @@ _notmuch_message_add_directory_terms (void *ctx, notmuch_message_t *message) message->notmuch, directory_id); - _notmuch_message_add_folder_terms (message, directory); - _notmuch_message_add_path_terms (message, directory); + term_status = _notmuch_message_add_folder_terms (message, directory); + if (term_status) + return term_status; + + term_status = _notmuch_message_add_path_terms (message, directory); + if (term_status) + return term_status; } return status; @@ -684,6 +943,7 @@ _notmuch_message_add_filename (notmuch_message_t *message, { const char *relative, *directory; notmuch_status_t status; + notmuch_private_status_t private_status; void *local = talloc_new (message); char *direntry; @@ -707,10 +967,25 @@ _notmuch_message_add_filename (notmuch_message_t *message, /* New file-direntry allows navigating to this message with * notmuch_directory_get_child_files() . */ - _notmuch_message_add_term (message, "file-direntry", direntry); + private_status = _notmuch_message_add_term (message, "file-direntry", direntry); + switch (private_status) { + case NOTMUCH_PRIVATE_STATUS_SUCCESS: + break; + case NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG: + _notmuch_database_log (message->notmuch, "filename too long for file-direntry term: %s\n", + filename); + return NOTMUCH_STATUS_PATH_ERROR; + default: + return COERCE_STATUS (private_status, "adding file-direntry term"); + } - _notmuch_message_add_folder_terms (message, directory); - _notmuch_message_add_path_terms (message, directory); + status = _notmuch_message_add_folder_terms (message, directory); + if (status) + return status; + + status = _notmuch_message_add_path_terms (message, directory); + if (status) + return status; talloc_free (local); @@ -744,7 +1019,7 @@ _notmuch_message_remove_filename (notmuch_message_t *message, status = _notmuch_database_filename_to_direntry ( local, message->notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry); - if (status || !direntry) + if (status || ! direntry) return status; /* Unlink this file from its parent directory. */ @@ -797,7 +1072,7 @@ void _notmuch_message_clear_data (notmuch_message_t *message) { message->doc.set_data (""); - message->modified = TRUE; + message->modified = true; } static void @@ -808,22 +1083,21 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message) if (message->filename_list) return; - if (!message->filename_term_list) - _notmuch_message_ensure_metadata (message); + _notmuch_message_ensure_metadata (message, message->filename_term_list); message->filename_list = _notmuch_string_list_create (message); node = message->filename_term_list->head; - if (!node) { + if (! node) { /* A message document created by an old version of notmuch * (prior to rename support) will have the filename in the * data of the document rather than as a file-direntry term. * * It would be nice to do the upgrade of the document directly * here, but the database is likely open in read-only mode. */ - const char *data; - data = message->doc.get_data ().c_str (); + std::string datastr = message->doc.get_data (); + const char *data = datastr.c_str (); if (data == NULL) INTERNAL_ERROR ("message with no filename"); @@ -850,7 +1124,7 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message) *colon = '\0'; - db_path = notmuch_database_get_path (message->notmuch); + db_path = notmuch_config_get (message->notmuch, NOTMUCH_CONFIG_MAIL_ROOT); directory = _notmuch_database_get_directory_path (local, message->notmuch, @@ -875,14 +1149,18 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message) const char * notmuch_message_get_filename (notmuch_message_t *message) { - _notmuch_message_ensure_filename_list (message); + try { + _notmuch_message_ensure_filename_list (message); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NULL; + } if (message->filename_list == NULL) return NULL; if (message->filename_list->head == NULL || - message->filename_list->head->string == NULL) - { + message->filename_list->head->string == NULL) { INTERNAL_ERROR ("message with no filename"); } @@ -892,20 +1170,63 @@ notmuch_message_get_filename (notmuch_message_t *message) notmuch_filenames_t * notmuch_message_get_filenames (notmuch_message_t *message) { - _notmuch_message_ensure_filename_list (message); + try { + _notmuch_message_ensure_filename_list (message); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NULL; + } return _notmuch_filenames_create (message, message->filename_list); } +int +notmuch_message_count_files (notmuch_message_t *message) +{ + try { + _notmuch_message_ensure_filename_list (message); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return -1; + } + + return _notmuch_string_list_length (message->filename_list); +} + +notmuch_status_t +notmuch_message_get_flag_st (notmuch_message_t *message, + notmuch_message_flag_t flag, + notmuch_bool_t *is_set) +{ + if (! is_set) + return NOTMUCH_STATUS_NULL_POINTER; + + try { + if (flag == NOTMUCH_MESSAGE_FLAG_GHOST && + ! NOTMUCH_TEST_BIT (message->lazy_flags, flag)) + _notmuch_message_ensure_metadata (message, NULL); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; + } + + *is_set = NOTMUCH_TEST_BIT (message->flags, flag); + return NOTMUCH_STATUS_SUCCESS; +} + notmuch_bool_t notmuch_message_get_flag (notmuch_message_t *message, notmuch_message_flag_t flag) { - if (flag == NOTMUCH_MESSAGE_FLAG_GHOST && - ! NOTMUCH_TEST_BIT (message->lazy_flags, flag)) - _notmuch_message_ensure_metadata (message); + notmuch_bool_t is_set; + notmuch_status_t status; - return NOTMUCH_TEST_BIT (message->flags, flag); + status = notmuch_message_get_flag_st (message, flag, &is_set); + + if (status) + return FALSE; + else + return is_set; } void @@ -927,9 +1248,7 @@ notmuch_message_get_date (notmuch_message_t *message) try { value = message->doc.get_value (NOTMUCH_VALUE_TIMESTAMP); } catch (Xapian::Error &error) { - _notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred when reading date: %s\n", - error.get_msg().c_str()); - message->notmuch->exception_reported = TRUE; + LOG_XAPIAN_EXCEPTION (message, error); return 0; } @@ -944,8 +1263,12 @@ notmuch_message_get_tags (notmuch_message_t *message) { notmuch_tags_t *tags; - if (!message->tag_list) - _notmuch_message_ensure_metadata (message); + try { + _notmuch_message_ensure_metadata (message, message->tag_list); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NULL; + } tags = _notmuch_tags_create (message, message->tag_list); /* _notmuch_tags_create steals the reference to the tag_list, but @@ -954,7 +1277,7 @@ notmuch_message_get_tags (notmuch_message_t *message) * possible to modify the message tags (which talloc_unlink's the * current list from the message) while still iterating because * the iterator will keep the current list alive. */ - if (!talloc_reference (message, message->tag_list)) + if (! talloc_reference (message, message->tag_list)) return NULL; return tags; @@ -968,11 +1291,11 @@ _notmuch_message_get_author (notmuch_message_t *message) void _notmuch_message_set_author (notmuch_message_t *message, - const char *author) + const char *author) { if (message->author) - talloc_free(message->author); - message->author = talloc_strdup(message, author); + talloc_free (message->author); + message->author = talloc_strdup (message, author); return; } @@ -986,16 +1309,30 @@ _notmuch_message_set_header_values (notmuch_message_t *message, /* GMime really doesn't want to see a NULL date, so protect its * sensibilities. */ - if (date == NULL || *date == '\0') + if (date == NULL || *date == '\0') { time_value = 0; - else - time_value = g_mime_utils_header_decode_date (date, NULL); + } else { + time_value = g_mime_utils_header_decode_date_unix (date); + /* + * Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=779923 + */ + if (time_value < 0) + time_value = 0; + } message->doc.add_value (NOTMUCH_VALUE_TIMESTAMP, Xapian::sortable_serialise (time_value)); message->doc.add_value (NOTMUCH_VALUE_FROM, from); message->doc.add_value (NOTMUCH_VALUE_SUBJECT, subject); - message->modified = TRUE; + message->modified = true; +} + +void +_notmuch_message_update_subject (notmuch_message_t *message, + const char *subject) +{ + message->doc.add_value (NOTMUCH_VALUE_SUBJECT, subject); + message->modified = true; } /* Upgrade a message to support NOTMUCH_FEATURE_LAST_MOD. The caller @@ -1005,16 +1342,14 @@ _notmuch_message_upgrade_last_mod (notmuch_message_t *message) { /* _notmuch_message_sync will update the last modification * revision; we just have to ask it to. */ - message->modified = TRUE; + message->modified = true; } /* Synchronize changes made to message->doc out into the database. */ void _notmuch_message_sync (notmuch_message_t *message) { - Xapian::WritableDatabase *db; - - if (message->notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) + if (_notmuch_database_mode (message->notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY) return; if (! message->modified) @@ -1032,9 +1367,9 @@ _notmuch_message_sync (notmuch_message_t *message) _notmuch_database_new_revision ( message->notmuch))); - db = static_cast (message->notmuch->xapian_db); - db->replace_document (message->doc_id, message->doc); - message->modified = FALSE; + message->notmuch->writable_xapian_db-> + replace_document (message->doc_id, message->doc); + message->modified = false; } /* Delete a message document from the database, leaving a ghost @@ -1043,11 +1378,12 @@ notmuch_status_t _notmuch_message_delete (notmuch_message_t *message) { notmuch_status_t status; - Xapian::WritableDatabase *db; const char *mid, *tid; notmuch_message_t *ghost; notmuch_private_status_t private_status; notmuch_database_t *notmuch; + unsigned int count = 0; + bool is_ghost; mid = notmuch_message_get_message_id (message); tid = notmuch_message_get_thread_id (message); @@ -1057,24 +1393,78 @@ _notmuch_message_delete (notmuch_message_t *message) if (status) return status; - db = static_cast (notmuch->xapian_db); - db->delete_document (message->doc_id); - - /* and reintroduce a ghost in its place */ - ghost = _notmuch_message_create_for_message_id (notmuch, mid, &private_status); - if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) { - private_status = _notmuch_message_initialize_ghost (ghost, tid); - if (! private_status) - _notmuch_message_sync (ghost); - } else if (private_status == NOTMUCH_PRIVATE_STATUS_SUCCESS) { - /* this is deeply weird, and we should not have gotten into - this state. is there a better error message to return - here? */ - return NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID; + try { + Xapian::PostingIterator thread_doc, thread_doc_end; + Xapian::PostingIterator mail_doc, mail_doc_end; + + /* look for a non-ghost message in the same thread */ + /* if this was a ghost to begin with, we are done */ + private_status = _notmuch_message_has_term (message, "type", "ghost", &is_ghost); + if (private_status) + return COERCE_STATUS (private_status, + "Error trying to determine whether message was a ghost"); + + message->notmuch->writable_xapian_db->delete_document (message->doc_id); + + if (is_ghost) + return NOTMUCH_STATUS_SUCCESS; + + _notmuch_database_find_doc_ids (message->notmuch, "thread", tid, &thread_doc, + &thread_doc_end); + _notmuch_database_find_doc_ids (message->notmuch, "type", "mail", &mail_doc, &mail_doc_end); + + while (count == 0 && + thread_doc != thread_doc_end && + mail_doc != mail_doc_end) { + thread_doc.skip_to (*mail_doc); + if (thread_doc != thread_doc_end) { + if (*thread_doc == *mail_doc) { + count++; + } else { + mail_doc.skip_to (*thread_doc); + if (mail_doc != mail_doc_end && *thread_doc == *mail_doc) + count++; + } + } + } + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; } + if (count > 0) { + /* reintroduce a ghost in its place because there are still + * other active messages in this thread: */ + ghost = _notmuch_message_create_for_message_id (notmuch, mid, &private_status); + if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) { + private_status = _notmuch_message_initialize_ghost (ghost, tid); + if (! private_status) + _notmuch_message_sync (ghost); + } else if (private_status == NOTMUCH_PRIVATE_STATUS_SUCCESS) { + /* this is deeply weird, and we should not have gotten + * into this state. is there a better error message to + * return here? */ + status = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID; + } + + notmuch_message_destroy (ghost); + status = COERCE_STATUS (private_status, "Error converting to ghost message"); + } else { + /* the thread now contains only ghosts: delete them */ + try { + Xapian::PostingIterator doc, doc_end; + + _notmuch_database_find_doc_ids (message->notmuch, "thread", tid, &doc, &doc_end); - notmuch_message_destroy (ghost); - return COERCE_STATUS (private_status, "Error converting to ghost message"); + for (; doc != doc_end; doc++) { + message->notmuch->writable_xapian_db->delete_document (*doc); + } + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; + } + + } + return status; } /* Transform a blank message into a ghost message. The caller must @@ -1115,36 +1505,43 @@ _notmuch_message_close (notmuch_message_t *message) * * This change will not be reflected in the database until the next * call to _notmuch_message_sync. */ -notmuch_private_status_t +NODISCARD notmuch_private_status_t _notmuch_message_add_term (notmuch_message_t *message, const char *prefix_name, const char *value) { char *term; + notmuch_private_status_t status = NOTMUCH_PRIVATE_STATUS_SUCCESS; if (value == NULL) return NOTMUCH_PRIVATE_STATUS_NULL_POINTER; term = talloc_asprintf (message, "%s%s", _find_prefix (prefix_name), value); + if (strlen (term) > NOTMUCH_TERM_MAX) { + status = NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG; + goto DONE; + } - if (strlen (term) > NOTMUCH_TERM_MAX) - return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG; - - message->doc.add_term (term, 0); - message->modified = TRUE; + try { + message->doc.add_term (term, 0); + message->modified = true; + _notmuch_message_invalidate_metadata (message, prefix_name); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + status = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION; + } + DONE: talloc_free (term); - - _notmuch_message_invalidate_metadata (message, prefix_name); - - return NOTMUCH_PRIVATE_STATUS_SUCCESS; + return status; } /* Parse 'text' and add a term to 'message' for each parsed word. Each - * term will be added both prefixed (if prefix_name is not NULL) and - * also non-prefixed). */ + * term will be added with the appropriate prefix if prefix_name is + * non-NULL. + */ notmuch_private_status_t _notmuch_message_gen_terms (notmuch_message_t *message, const char *prefix_name, @@ -1156,22 +1553,21 @@ _notmuch_message_gen_terms (notmuch_message_t *message, return NOTMUCH_PRIVATE_STATUS_NULL_POINTER; term_gen->set_document (message->doc); + term_gen->set_termpos (message->termpos); if (prefix_name) { - const char *prefix = _find_prefix (prefix_name); - - term_gen->set_termpos (message->termpos); - term_gen->index_text (text, 1, prefix); - /* Create a gap between this an the next terms so they don't - * appear to be a phrase. */ - message->termpos = term_gen->get_termpos () + 100; + const char *prefix = _notmuch_database_prefix (message->notmuch, prefix_name); + if (prefix == NULL) + return NOTMUCH_PRIVATE_STATUS_BAD_PREFIX; _notmuch_message_invalidate_metadata (message, prefix_name); + term_gen->index_text (text, 1, prefix); + } else { + term_gen->index_text (text); } - term_gen->set_termpos (message->termpos); - term_gen->index_text (text); - /* Create a term gap, as above. */ + /* Create a gap between this an the next terms so they don't + * appear to be a phrase. */ message->termpos = term_gen->get_termpos () + 100; return NOTMUCH_PRIVATE_STATUS_SUCCESS; @@ -1184,7 +1580,7 @@ _notmuch_message_gen_terms (notmuch_message_t *message, * * This change will not be reflected in the database until the next * call to _notmuch_message_sync. */ -notmuch_private_status_t +NODISCARD notmuch_private_status_t _notmuch_message_remove_term (notmuch_message_t *message, const char *prefix_name, const char *value) @@ -1202,12 +1598,13 @@ _notmuch_message_remove_term (notmuch_message_t *message, try { message->doc.remove_term (term); - message->modified = TRUE; - } catch (const Xapian::InvalidArgumentError) { + message->modified = true; + } catch (const Xapian::InvalidArgumentError &error) { /* We'll let the philosophers try to wrestle with the * question of whether failing to remove that which was not * there in the first place is failure. For us, we'll silently * consider it all good. */ + LOG_XAPIAN_EXCEPTION (message, error); } talloc_free (term); @@ -1221,10 +1618,10 @@ notmuch_private_status_t _notmuch_message_has_term (notmuch_message_t *message, const char *prefix_name, const char *value, - notmuch_bool_t *result) + bool *result) { char *term; - notmuch_bool_t out = FALSE; + bool out = false; notmuch_private_status_t status = NOTMUCH_PRIVATE_STATUS_SUCCESS; if (value == NULL) @@ -1241,8 +1638,8 @@ _notmuch_message_has_term (notmuch_message_t *message, Xapian::TermIterator i = message->doc.termlist_begin (); i.skip_to (term); if (i != message->doc.termlist_end () && - !strcmp ((*i).c_str (), term)) - out = TRUE; + ! strcmp ((*i).c_str (), term)) + out = true; } catch (Xapian::Error &error) { status = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION; } @@ -1258,24 +1655,31 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag) notmuch_private_status_t private_status; notmuch_status_t status; - status = _notmuch_database_ensure_writable (message->notmuch); - if (status) - return status; + try { + status = _notmuch_database_ensure_writable (message->notmuch); + if (status) + return status; - if (tag == NULL) - return NOTMUCH_STATUS_NULL_POINTER; + if (tag == NULL) + return NOTMUCH_STATUS_NULL_POINTER; - if (strlen (tag) > NOTMUCH_TAG_MAX) - return NOTMUCH_STATUS_TAG_TOO_LONG; + if (strlen (tag) > NOTMUCH_TAG_MAX) + return NOTMUCH_STATUS_TAG_TOO_LONG; - private_status = _notmuch_message_add_term (message, "tag", tag); - if (private_status) { - INTERNAL_ERROR ("_notmuch_message_add_term return unexpected value: %d\n", - private_status); - } + private_status = _notmuch_message_add_term (message, "tag", tag); + if (private_status) { + return COERCE_STATUS (private_status, + "_notmuch_message_remove_term return unexpected value: %d\n", + private_status); + } - if (! message->frozen) - _notmuch_message_sync (message); + if (! message->frozen) + _notmuch_message_sync (message); + + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; + } return NOTMUCH_STATUS_SUCCESS; } @@ -1286,24 +1690,30 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag) notmuch_private_status_t private_status; notmuch_status_t status; - status = _notmuch_database_ensure_writable (message->notmuch); - if (status) - return status; + try { + status = _notmuch_database_ensure_writable (message->notmuch); + if (status) + return status; - if (tag == NULL) - return NOTMUCH_STATUS_NULL_POINTER; + if (tag == NULL) + return NOTMUCH_STATUS_NULL_POINTER; - if (strlen (tag) > NOTMUCH_TAG_MAX) - return NOTMUCH_STATUS_TAG_TOO_LONG; + if (strlen (tag) > NOTMUCH_TAG_MAX) + return NOTMUCH_STATUS_TAG_TOO_LONG; - private_status = _notmuch_message_remove_term (message, "tag", tag); - if (private_status) { - INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n", - private_status); - } + private_status = _notmuch_message_remove_term (message, "tag", tag); + if (private_status) { + return COERCE_STATUS (private_status, + "_notmuch_message_remove_term return unexpected value: %d\n", + private_status); + } - if (! message->frozen) - _notmuch_message_sync (message); + if (! message->frozen) + _notmuch_message_sync (message); + } catch (Xapian::Error &error) { + LOG_XAPIAN_EXCEPTION (message, error); + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; + } return NOTMUCH_STATUS_SUCCESS; } @@ -1341,29 +1751,34 @@ _filename_is_in_maildir (const char *filename) dir = slash + 1; if (STRNCMP_LITERAL (dir, "cur/") == 0 || - STRNCMP_LITERAL (dir, "new/") == 0) - { + STRNCMP_LITERAL (dir, "new/") == 0) { return dir; } return NULL; } -notmuch_status_t -notmuch_message_maildir_flags_to_tags (notmuch_message_t *message) +static notmuch_status_t +_ensure_maildir_flags (notmuch_message_t *message, bool force) { const char *flags; - notmuch_status_t status; notmuch_filenames_t *filenames; const char *filename, *dir; char *combined_flags = talloc_strdup (message, ""); - unsigned i; int seen_maildir_info = 0; - for (filenames = notmuch_message_get_filenames (message); + if (message->maildir_flags) { + if (force) { + talloc_free (message->maildir_flags); + message->maildir_flags = NULL; + } + } + filenames = notmuch_message_get_filenames (message); + if (! filenames) + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; + for (; notmuch_filenames_valid (filenames); - notmuch_filenames_move_to_next (filenames)) - { + notmuch_filenames_move_to_next (filenames)) { filename = notmuch_filenames_get (filenames); dir = _filename_is_in_maildir (filename); @@ -1385,22 +1800,65 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message) seen_maildir_info = 1; } } + if (seen_maildir_info) + message->maildir_flags = combined_flags; + return NOTMUCH_STATUS_SUCCESS; +} + +notmuch_bool_t +notmuch_message_has_maildir_flag (notmuch_message_t *message, char flag) +{ + notmuch_status_t status; + notmuch_bool_t ret; + + status = notmuch_message_has_maildir_flag_st (message, flag, &ret); + if (status) + return FALSE; + + return ret; +} + +notmuch_status_t +notmuch_message_has_maildir_flag_st (notmuch_message_t *message, + char flag, + notmuch_bool_t *is_set) +{ + notmuch_status_t status; + + if (! is_set) + return NOTMUCH_STATUS_NULL_POINTER; + + status = _ensure_maildir_flags (message, false); + if (status) + return status; + + *is_set = message->maildir_flags && (strchr (message->maildir_flags, flag) != NULL); + return NOTMUCH_STATUS_SUCCESS; +} + +notmuch_status_t +notmuch_message_maildir_flags_to_tags (notmuch_message_t *message) +{ + notmuch_status_t status; + unsigned i; + status = _ensure_maildir_flags (message, true); + if (status) + return status; /* If none of the filenames have any maildir info field (not even * an empty info with no flags set) then there's no information to * go on, so do nothing. */ - if (! seen_maildir_info) + if (! message->maildir_flags) return NOTMUCH_STATUS_SUCCESS; status = notmuch_message_freeze (message); if (status) return status; - for (i = 0; i < ARRAY_SIZE(flag2tag); i++) { - if ((strchr (combined_flags, flag2tag[i].flag) != NULL) - ^ - flag2tag[i].inverse) - { + for (i = 0; i < ARRAY_SIZE (flag2tag); i++) { + if ((strchr (message->maildir_flags, flag2tag[i].flag) != NULL) + ^ + flag2tag[i].inverse) { status = notmuch_message_add_tag (message, flag2tag[i].tag); } else { status = notmuch_message_remove_tag (message, flag2tag[i].tag); @@ -1410,8 +1868,6 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message) } status = notmuch_message_thaw (message); - talloc_free (combined_flags); - return status; } @@ -1437,8 +1893,7 @@ _get_maildir_flag_actions (notmuch_message_t *message, /* First, find flags for all set tags. */ for (tags = notmuch_message_get_tags (message); notmuch_tags_valid (tags); - notmuch_tags_move_to_next (tags)) - { + notmuch_tags_move_to_next (tags)) { tag = notmuch_tags_get (tags); for (i = 0; i < ARRAY_SIZE (flag2tag); i++) { @@ -1488,7 +1943,7 @@ _get_maildir_flag_actions (notmuch_message_t *message, * non-ASCII ordering of flags), this function will return NULL * (meaning that renaming would not be safe and should not occur). */ -static char* +static char * _new_maildir_filename (void *ctx, const char *filename, const char *flags_to_set, @@ -1499,7 +1954,7 @@ _new_maildir_filename (void *ctx, char *filename_new, *dir; char flag_map[128]; int flags_in_map = 0; - notmuch_bool_t flags_changed = FALSE; + bool flags_changed = false; unsigned int i; char *s; @@ -1508,13 +1963,12 @@ _new_maildir_filename (void *ctx, info = strstr (filename, ":2,"); if (info == NULL) { - info = filename + strlen(filename); + info = filename + strlen (filename); } else { /* Loop through existing flags in filename. */ for (flags = info + 3, last_flag = 0; *flags; - last_flag = flag, flags++) - { + last_flag = flag, flags++) { flag = *flags; /* Original flags not in ASCII order. Abort. */ @@ -1522,7 +1976,7 @@ _new_maildir_filename (void *ctx, return NULL; /* Non-ASCII flag. Abort. */ - if (flag > sizeof(flag_map) - 1) + if (flag > sizeof (flag_map) - 1) return NULL; /* Repeated flag value. Abort. */ @@ -1540,7 +1994,7 @@ _new_maildir_filename (void *ctx, if (flag_map[flag] == 0) { flag_map[flag] = 1; flags_in_map++; - flags_changed = TRUE; + flags_changed = true; } } @@ -1549,14 +2003,14 @@ _new_maildir_filename (void *ctx, if (flag_map[flag]) { flag_map[flag] = 0; flags_in_map--; - flags_changed = TRUE; + flags_changed = true; } } /* Messages in new/ without maildir info can be kept in new/ if no * flags have changed. */ dir = (char *) _filename_is_in_maildir (filename); - if (dir && STRNCMP_LITERAL (dir, "new/") == 0 && !*info && !flags_changed) + if (dir && STRNCMP_LITERAL (dir, "new/") == 0 && ! *info && ! flags_changed) return talloc_strdup (ctx, filename); filename_new = (char *) talloc_size (ctx, @@ -1571,8 +2025,7 @@ _new_maildir_filename (void *ctx, strcat (filename_new, ":2,"); s = filename_new + strlen (filename_new); - for (i = 0; i < sizeof (flag_map); i++) - { + for (i = 0; i < sizeof (flag_map); i++) { if (flag_map[i]) { *s = i; s++; @@ -1597,12 +2050,15 @@ notmuch_message_tags_to_maildir_flags (notmuch_message_t *message) char *to_set, *to_clear; notmuch_status_t status = NOTMUCH_STATUS_SUCCESS; + status = _notmuch_database_ensure_writable (message->notmuch); + if (status) + return status; + _get_maildir_flag_actions (message, &to_set, &to_clear); for (filenames = notmuch_message_get_filenames (message); notmuch_filenames_valid (filenames); - notmuch_filenames_move_to_next (filenames)) - { + notmuch_filenames_move_to_next (filenames)) { filename = notmuch_filenames_get (filenames); if (! _filename_is_in_maildir (filename)) @@ -1661,17 +2117,20 @@ notmuch_message_remove_all_tags (notmuch_message_t *message) status = _notmuch_database_ensure_writable (message->notmuch); if (status) return status; + tags = notmuch_message_get_tags (message); + if (! tags) + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; - for (tags = notmuch_message_get_tags (message); + for (; notmuch_tags_valid (tags); - notmuch_tags_move_to_next (tags)) - { + notmuch_tags_move_to_next (tags)) { tag = notmuch_tags_get (tags); private_status = _notmuch_message_remove_term (message, "tag", tag); if (private_status) { - INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n", - private_status); + return COERCE_STATUS (private_status, + "_notmuch_message_remove_term return unexpected value: %d\n", + private_status); } } @@ -1722,7 +2181,178 @@ notmuch_message_destroy (notmuch_message_t *message) } notmuch_database_t * -_notmuch_message_database (notmuch_message_t *message) +notmuch_message_get_database (const notmuch_message_t *message) { return message->notmuch; } + +static void +_notmuch_message_ensure_property_map (notmuch_message_t *message) +{ + notmuch_string_node_t *node; + + if (message->property_map) + return; + + _notmuch_message_ensure_metadata (message, message->property_term_list); + + message->property_map = _notmuch_string_map_create (message); + + for (node = message->property_term_list->head; node; node = node->next) { + const char *key; + char *value; + + value = strchr (node->string, '='); + if (! value) + INTERNAL_ERROR ("malformed property term"); + + *value = '\0'; + value++; + key = node->string; + + _notmuch_string_map_append (message->property_map, key, value); + + } + + talloc_free (message->property_term_list); + message->property_term_list = NULL; +} + +notmuch_string_map_t * +_notmuch_message_property_map (notmuch_message_t *message) +{ + _notmuch_message_ensure_property_map (message); + + return message->property_map; +} + +bool +_notmuch_message_frozen (notmuch_message_t *message) +{ + return message->frozen; +} + +notmuch_status_t +notmuch_message_reindex (notmuch_message_t *message, + notmuch_indexopts_t *indexopts) +{ + notmuch_database_t *notmuch = NULL; + notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS; + notmuch_private_status_t private_status; + notmuch_filenames_t *orig_filenames = NULL; + const char *orig_thread_id = NULL; + notmuch_message_file_t *message_file = NULL; + + int found = 0; + + if (message == NULL) + return NOTMUCH_STATUS_NULL_POINTER; + + /* Save in case we need to delete message */ + orig_thread_id = notmuch_message_get_thread_id (message); + if (! orig_thread_id) { + /* the following is correct as long as there is only one reason + * n_m_get_thread_id returns NULL + */ + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; + } + + /* strdup it because the metadata may be invalidated */ + orig_thread_id = talloc_strdup (message, orig_thread_id); + + notmuch = notmuch_message_get_database (message); + + ret = _notmuch_database_ensure_writable (notmuch); + if (ret) + return ret; + + orig_filenames = notmuch_message_get_filenames (message); + + private_status = _notmuch_message_remove_indexed_terms (message); + if (private_status) { + ret = COERCE_STATUS (private_status, "error removing terms"); + goto DONE; + } + + ret = notmuch_message_remove_all_properties_with_prefix (message, "index."); + if (ret) + goto DONE; /* XXX TODO: distinguish from other error returns above? */ + if (indexopts && notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_FALSE) { + ret = notmuch_message_remove_all_properties (message, "session-key"); + if (ret) + goto DONE; + } + + /* re-add the filenames with the associated indexopts */ + for (; notmuch_filenames_valid (orig_filenames); + notmuch_filenames_move_to_next (orig_filenames)) { + + const char *date; + const char *from, *to, *subject; + char *message_id = NULL; + const char *thread_id = NULL; + + const char *filename = notmuch_filenames_get (orig_filenames); + + message_file = _notmuch_message_file_open (notmuch, filename); + if (message_file == NULL) + continue; + + ret = _notmuch_message_file_get_headers (message_file, + &from, &subject, &to, &date, + &message_id); + if (ret) + goto DONE; + + /* XXX TODO: deal with changing message id? */ + + _notmuch_message_add_filename (message, filename); + + ret = _notmuch_database_link_message_to_parents (notmuch, message, + message_file, + &thread_id); + if (ret) + goto DONE; + + if (thread_id == NULL) + thread_id = orig_thread_id; + + ret = COERCE_STATUS (_notmuch_message_add_term (message, "thread", thread_id), + "adding thread term"); + if (ret) + goto DONE; + + /* Take header values only from first filename */ + if (found == 0) + _notmuch_message_set_header_values (message, date, from, subject); + + ret = _notmuch_message_index_file (message, indexopts, message_file); + + if (ret == NOTMUCH_STATUS_FILE_ERROR) + continue; + if (ret) + goto DONE; + + found++; + _notmuch_message_file_close (message_file); + message_file = NULL; + } + if (found == 0) { + /* put back thread id to help cleanup */ + ret = COERCE_STATUS (_notmuch_message_add_term (message, "thread", orig_thread_id), + "adding thread term"); + if (ret) + goto DONE; + + ret = _notmuch_message_delete (message); + } else { + _notmuch_message_sync (message); + } + + DONE: + if (message_file) + _notmuch_message_file_close (message_file); + + /* XXX TODO destroy orig_filenames? */ + return ret; +}