[PATCH] notmuch: Add "maildir:" search option

Peter Zijlstra peterz at infradead.org
Wed Nov 13 12:08:52 PST 2013


On Tue, Nov 12, 2013 at 02:39:52PM -0500, Austin Clements wrote:
> On Tue, 12 Nov 2013, Austin Clements <aclements at csail.mit.edu> wrote:
> > I think this is a great idea.  Personally I think this is how folder:
> > should work.  I find the semantics of folder: to be useless except where
> > they happen to coincide with the boolean semantics used here.
> > Unfortunately, changing folder: would require versioning the database,
> > which we have only primordial support for right now.
> >
> > Various comments below, though nothing major.  Of course, we'd also need
> > some tests and man page updates for this.
> 
> Sorry, one important thing I missed: this doesn't correctly handle when
> file names are removed from a message
> (_notmuch_message_remove_filename).  Probably the simplest thing would
> be to follow the template for how folder: works by first removing *all*
> folder terms and then adding back the still-valid ones.  (Unfortunately,
> just removing the term for the removed filename's directory won't work
> because the message could have other filenames in the same directory,
> though maybe you could just scan for that possibility?)

Oh, right you are. A little something like the below? Its compile tested
only and I've not yet had time to look at how the test infrastructure
works.


---
 lib/database.cc |   3 +-
 lib/message.cc  | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 104 insertions(+), 10 deletions(-)

diff --git a/lib/database.cc b/lib/database.cc
index a021bf17253c..e43e17dffcd0 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -208,7 +208,8 @@ static prefix_t BOOLEAN_PREFIX_EXTERNAL[] = {
     { "thread",			"G" },
     { "tag",			"K" },
     { "is",			"K" },
-    { "id",			"Q" }
+    { "id",			"Q" },
+    { "maildir",		"XMAILDIR:" },
 };
 
 static prefix_t PROBABILISTIC_PREFIX[]= {
diff --git a/lib/message.cc b/lib/message.cc
index 1b4637950f8e..73d3bb65ab67 100644
--- a/lib/message.cc
+++ b/lib/message.cc
@@ -22,6 +22,7 @@
 #include "database-private.h"
 
 #include <stdint.h>
+#include <string.h>
 
 #include <gmime/gmime.h>
 
@@ -473,6 +474,71 @@ notmuch_message_get_replies (notmuch_message_t *message)
     return _notmuch_messages_create (message->replies);
 }
 
+/* Construct a proper 'maildir' from 'directory'
+ *
+ * Takes the relative directory component inside the maildir pathname and
+ * construct a maildir path from it.
+ *
+ * For filesystem layout Maildir we use the regular filesystem path except the
+ * trailing "cur"/"new" component.
+ *
+ * For Maildir++ we strip the leading '.' and replace subsequent '.'s with '/'s
+ */
+static char *
+_notmuch_message_maildir (void *ctx, const char *directory)
+{
+    char *maildir;
+    int i;
+
+    maildir = talloc_strdup (ctx, directory);
+    i = strlen (maildir);
+
+    /* Strip trailing '/' */
+    while (i && maildir[i - 1] == '/') {
+	maildir[i - 1] = '\0';
+	i--;
+    }
+
+    /* Strip leading '/' */
+    while (maildir[0] == '/') {
+	maildir++;
+	i--;
+    }
+
+    if (i >= 3) {
+	/* Consume trailing maildir directory entries */
+	if (STRNCMP_LITERAL (maildir, "cur") == 0 ||
+	    STRNCMP_LITERAL (maildir, "new") == 0)
+	{
+	    maildir[i - 3] = '\0';
+	    i -= 3;
+	}
+
+	/* Strip trailing '/' */
+	while (i && maildir[i - 1] == '/') {
+	    maildir[i-1] = '\0';
+	    i--;
+	}
+    }
+
+    /* Maildir++ */
+    if (maildir[0] == '.') {
+	maildir++;
+
+	/* Replace all remaining '.' with '/' */
+	for (i = 0; maildir[i]; i++) {
+	    if (maildir[i] == '.')
+		maildir[i] = '/';
+	}
+    }
+
+    /* If there's no string left, we're the "INBOX" */
+    if (maildir[0] == '\0')
+	    maildir = talloc_strdup (ctx, "INBOX");
+
+    return maildir;
+}
+
 /* Add an additional 'filename' for 'message'.
  *
  * This change will not be reflected in the database until the next
@@ -485,6 +551,7 @@ _notmuch_message_add_filename (notmuch_message_t *message,
     notmuch_status_t status;
     void *local = talloc_new (message);
     char *direntry;
+    char *maildir;
 
     if (filename == NULL)
 	INTERNAL_ERROR ("Message filename cannot be NULL.");
@@ -507,6 +574,10 @@ _notmuch_message_add_filename (notmuch_message_t *message,
     /* New terms allow user to search with folder: specification. */
     _notmuch_message_gen_terms (message, "folder", directory);
 
+    /* New terms allow user to serarch with maildir: specification. */
+    maildir = _notmuch_message_maildir (local, directory);
+    _notmuch_message_add_term (message, "maildir", maildir);
+
     talloc_free (local);
 
     return NOTMUCH_STATUS_SUCCESS;
@@ -535,11 +606,18 @@ _notmuch_message_remove_filename (notmuch_message_t *message,
     void *local = talloc_new (message);
     char *zfolder_prefix = talloc_asprintf(local, "Z%s", folder_prefix);
     int zfolder_prefix_len = strlen (zfolder_prefix);
-    char *direntry;
+    const char *relative, *directory;
+    char *direntry, *maildir;
     notmuch_private_status_t private_status;
     notmuch_status_t status;
     Xapian::TermIterator i, last;
 
+    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, filename, NOTMUCH_FIND_LOOKUP, &direntry);
     if (status || !direntry)
@@ -553,12 +631,21 @@ _notmuch_message_remove_filename (notmuch_message_t *message,
     if (status)
 	return status;
 
-    /* Re-synchronize "folder:" terms for this message. This requires:
-     *  1. removing all "folder:" terms
-     *  2. removing all "folder:" stemmed terms
-     *  3. adding back terms for all remaining filenames of the message. */
-
-    /* 1. removing all "folder:" terms */
+    /* Re-synchronize "folder:" and "maildir:" terms for this message. This
+     * requires:
+     *  1. removing "maildir:" for this filename
+     *  2. removing all "folder:" terms
+     *  3. removing all "folder:" stemmed terms
+     *
+     * For all remaining filenames of the message:
+     *  4. adding back "folder:" terms
+     *  5. adding back "maildir:" */
+
+    /* 1. remove "maildir:" for this message */
+    maildir = _notmuch_message_maildir (local, directory);
+    _notmuch_message_remove_term (message, "maildir", maildir);
+
+    /* 2. removing all "folder:" terms */
     while (1) {
 	i = message->doc.termlist_begin ();
 	i.skip_to (folder_prefix);
@@ -577,7 +664,7 @@ _notmuch_message_remove_filename (notmuch_message_t *message,
 	}
     }
 
-    /* 2. removing all "folder:" stemmed terms */
+    /* 3. removing all "folder:" stemmed terms */
     while (1) {
 	i = message->doc.termlist_begin ();
 	i.skip_to (zfolder_prefix);
@@ -596,7 +683,7 @@ _notmuch_message_remove_filename (notmuch_message_t *message,
 	}
     }
 
-    /* 3. adding back terms for all remaining filenames of the message. */
+    /* for all remaining filenames of the message */
     i = message->doc.termlist_begin ();
     i.skip_to (direntry_prefix);
 
@@ -623,8 +710,14 @@ _notmuch_message_remove_filename (notmuch_message_t *message,
 	directory = _notmuch_database_get_directory_path (local,
 							  message->notmuch,
 							  directory_id);
+
+	/* 4. adding back "folder:" terms */
 	if (strlen (directory))
 	    _notmuch_message_gen_terms (message, "folder", directory);
+
+	/* 5. adding back "maildir:" */
+	maildir = _notmuch_message_maildir (local, directory);
+	_notmuch_message_add_term (message, "maildir", maildir);
     }
 
     talloc_free (local);


More information about the notmuch mailing list