From 99cfa2703080c2474e6b8aec0cae86135ae23f1d Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Sat, 15 Jan 2011 14:09:04 -0800 Subject: [PATCH] Add support for folder-based searching. A new "folder:" prefix in the query string can now be used to match the directories in which mail files are stored. The addition of this feature causes the recently added search-by-folder tests to now pass. --- lib/database.cc | 22 ++++++--- lib/message.cc | 107 ++++++++++++++++++++++++++++++++++++------ lib/notmuch-private.h | 4 ++ 3 files changed, 112 insertions(+), 21 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index 289e41c4..d88b168b 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -89,8 +89,9 @@ typedef struct { * * In addition, terms from the content of the message are added with * "from", "to", "attachment", and "subject" prefixes for use by the - * user in searching. But the database doesn't really care itself - * about any of these. + * user in searching. Similarly, terms from the path of the mail + * message are added with a "folder" prefix. But the database doesn't + * really care itself about any of these. * * The data portion of a mail document is empty. * @@ -204,7 +205,8 @@ static prefix_t PROBABILISTIC_PREFIX[]= { { "from", "XFROM" }, { "to", "XTO" }, { "attachment", "XATTACHMENT" }, - { "subject", "XSUBJECT"} + { "subject", "XSUBJECT"}, + { "folder", "XFOLDER"} }; int @@ -1716,11 +1718,20 @@ notmuch_database_remove_message (notmuch_database_t *notmuch, for ( ; i != end; i++) { Xapian::TermIterator j; + notmuch_message_t *message; + notmuch_private_status_t private_status; - document = find_document_for_doc_id (notmuch, *i); + message = _notmuch_message_create (local, notmuch, + *i, &private_status); + if (message == NULL) + return COERCE_STATUS (private_status, + "Inconsistent document ID in datbase."); - document.remove_term (term); + _notmuch_message_remove_filename (message, filename); + _notmuch_message_sync (message); + /* Take care to find document after sync'ing filename removal. */ + document = find_document_for_doc_id (notmuch, *i); j = document.termlist_begin (); j.skip_to (prefix); @@ -1731,7 +1742,6 @@ notmuch_database_remove_message (notmuch_database_t *notmuch, db->delete_document (document.get_docid ()); status = NOTMUCH_STATUS_SUCCESS; } else { - db->replace_document (document.get_docid (), document); status = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID; } } diff --git a/lib/message.cc b/lib/message.cc index ad220897..0d9542e6 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -434,17 +434,24 @@ notmuch_status_t _notmuch_message_add_filename (notmuch_message_t *message, const char *filename) { + const char *relative, *directory; notmuch_status_t status; void *local = talloc_new (message); char *direntry; + if (filename == NULL) + INTERNAL_ERROR ("Message filename cannot be NULL."); + if (message->filename_list) { _notmuch_filename_list_destroy (message->filename_list); message->filename_list = NULL; } - if (filename == NULL) - INTERNAL_ERROR ("Message filename cannot be NULL."); + relative = _notmuch_database_relative_path (message->notmuch, filename); + + status = _notmuch_database_split_path (local, relative, &directory, NULL); + if (status) + return status; status = _notmuch_database_filename_to_direntry (local, message->notmuch, @@ -452,43 +459,105 @@ _notmuch_message_add_filename (notmuch_message_t *message, if (status) return status; + /* New file-direntry allows navigating to this message with + * notmuch_directory_get_child_files() . */ _notmuch_message_add_term (message, "file-direntry", direntry); + /* New terms allow user to search with folder: specification. */ + _notmuch_message_gen_terms (message, "folder", directory); + talloc_free (local); return NOTMUCH_STATUS_SUCCESS; } -/* Change a particular filename for 'message' from 'old_filename' to - * 'new_filename' +/* Remove a particular 'filename' from 'message'. * * This change will not be reflected in the database until the next * call to _notmuch_message_sync. - */ -static notmuch_status_t -_notmuch_message_rename (notmuch_message_t *message, - const char *old_filename, - const char *new_filename) + * + * Note: This function does not remove a document from the database, + * even if the specified filename is the only filename for this + * message. For that functionality, see + * _notmuch_database_remove_message. */ +notmuch_status_t +_notmuch_message_remove_filename (notmuch_message_t *message, + const char *filename) { + const char *direntry_prefix = _find_prefix ("file-direntry"); + int direntry_prefix_len = strlen (direntry_prefix); + const char *folder_prefix = _find_prefix ("folder"); + int folder_prefix_len = strlen (folder_prefix); void *local = talloc_new (message); char *direntry; notmuch_private_status_t private_status; notmuch_status_t status; + Xapian::TermIterator i, last; - status = _notmuch_message_add_filename (message, new_filename); - if (status) - return status; + if (message->filename_list) { + _notmuch_filename_list_destroy (message->filename_list); + message->filename_list = NULL; + } status = _notmuch_database_filename_to_direntry (local, message->notmuch, - old_filename, &direntry); + filename, &direntry); if (status) return status; + /* Unlink this file from its parent directory. */ private_status = _notmuch_message_remove_term (message, "file-direntry", direntry); status = COERCE_STATUS (private_status, "Unexpected error from _notmuch_message_remove_term"); + /* Re-synchronize "folder:" terms for this message. This requires + * first removing all "folder:" terms, then adding back terms for + * all remaining filenames of the message. */ + while (1) { + i = message->doc.termlist_begin (); + i.skip_to (folder_prefix); + + /* Terminate loop when no terms remain with desired prefix. */ + if (i == message->doc.termlist_end () || + strncmp ((*i).c_str (), folder_prefix, folder_prefix_len)) + { + break; + } + + try { + message->doc.remove_term ((*i)); + } catch (const Xapian::InvalidArgumentError) { + /* Ignore failure to remove non-existent term. */ + } + } + + i = message->doc.termlist_begin (); + i.skip_to (direntry_prefix); + + for (; i != message->doc.termlist_end (); i++) { + unsigned int directory_id; + const char *direntry, *directory; + char *colon; + + /* Terminate loop at first term without desired prefix. */ + if (strncmp ((*i).c_str (), direntry_prefix, direntry_prefix_len)) + break; + + direntry = (*i).c_str (); + direntry += direntry_prefix_len; + + directory_id = strtol (direntry, &colon, 10); + + if (colon == NULL || *colon != ':') + INTERNAL_ERROR ("malformed direntry"); + + directory = _notmuch_database_get_directory_path (local, + message->notmuch, + directory_id); + if (strlen (directory)) + _notmuch_message_gen_terms (message, "folder", directory); + } + talloc_free (local); return status; @@ -1154,8 +1223,16 @@ notmuch_message_tags_to_maildir_flags (notmuch_message_t *message) if (err) continue; - new_status = _notmuch_message_rename (message, - filename, filename_new); + new_status = _notmuch_message_remove_filename (message, + filename); + /* Hold on to only the first error. */ + if (! status && new_status) { + status = new_status; + continue; + } + + new_status = _notmuch_message_add_filename (message, + filename_new); /* Hold on to only the first error. */ if (! status && new_status) { status = new_status; diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index 303aeb30..a1b82b3e 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -269,6 +269,10 @@ notmuch_status_t _notmuch_message_add_filename (notmuch_message_t *message, const char *filename); +notmuch_status_t +_notmuch_message_remove_filename (notmuch_message_t *message, + const char *filename); + notmuch_status_t _notmuch_message_rename (notmuch_message_t *message, const char *new_filename); -- 2.26.2