aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Carl Worth <cworth@cworth.org>2011-01-15 14:09:04 -0800
committerGravatar Carl Worth <cworth@cworth.org>2011-01-15 15:37:43 -0800
commit99cfa2703080c2474e6b8aec0cae86135ae23f1d (patch)
tree21d73e4d926f9277d6ece1f111259e9066f85bce
parent600f3761dc48c91a5ba9a18ddeb7fd8af22e71de (diff)
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.
-rw-r--r--lib/database.cc22
-rw-r--r--lib/message.cc107
-rw-r--r--lib/notmuch-private.h4
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
@@ -270,6 +270,10 @@ _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);