unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
From: Michal Sojka <sojkam1@fel.cvut.cz>
To: notmuch@notmuchmail.org
Subject: [PATCH v3 3/6] Add maildir-based mailstore
Date: Sat, 27 Mar 2010 21:44:18 +0100	[thread overview]
Message-ID: <1269722661-6894-3-git-send-email-sojkam1@fel.cvut.cz> (raw)
In-Reply-To: <8739zlijhp.fsf@steelpick.2x.cz>

This mailstore allows bi-directional synchronization between maildir
flags and certain tags. The flag-to-tag mapping is defined by flag2tag
array.

The synchronization works this way:

1) Whenever notmuch new is executed, the following happens:
   o New messages are tagged with inbox.
   o New messages without maildir info in the file name (typically
     files in new/) are tagged with unread.
   o For new or renamed messages with maildir info present in the
     file name, the tags defined in flag2tag are either added or
     removed depending on the flags from the file name.

2) Whenever notmuch tag (or notmuch restore) is executed, a new set of
   flags based on the tags is constructed for every message and a new
   file name is prepared based on the old file name but with the new
   flags. If the old message file was in 'new' directory then this is
   replaced with 'cur' in the new file name. If the new and old file
   names differ, the file is renamed and notmuch database is updated
   accordingly.

   The rename happens before the database is updated. In case of crash
   between rename and database update, the next run of notmuch new
   brings the database in sync with the mail store again.

This mailstore is enabled by putting the following to your
.notmuch-config:

[mailstore]
type=maildir

Signed-off-by: Michal Sojka <sojkam1@fel.cvut.cz>
---
 lib/database.cc       |    7 ++
 lib/mailstore-files.c |  203 ++++++++++++++++++++++++++++++++++++++++++++++++-
 lib/mailstore.c       |    1 +
 lib/message.cc        |   41 ++++++++++-
 lib/notmuch-private.h |    4 +
 lib/notmuch.h         |    1 +
 6 files changed, 253 insertions(+), 4 deletions(-)

diff --git a/lib/database.cc b/lib/database.cc
index 93c8d0f..33ef889 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -1471,6 +1471,13 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
 
 	_notmuch_message_add_filename (message, filename);
 
+	/* This is a new message or it has a new filename and as such,
+	 * its tags in database either do not exists or might be out
+	 * of date. Mailstore assigns the tags later in index_new(),
+	 * but until then we should not synchronize the tags back to
+	 * the mailstore. */
+	notmuch_message_set_flag(message, NOTMUCH_MESSAGE_FLAG_TAGS_INVALID, TRUE);
+
 	/* Is this a newly created message object? */
 	if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
 	    _notmuch_message_add_term (message, "type", "mail");
diff --git a/lib/mailstore-files.c b/lib/mailstore-files.c
index e4086ba..b4e6e42 100644
--- a/lib/mailstore-files.c
+++ b/lib/mailstore-files.c
@@ -24,6 +24,30 @@
 #include "notmuch.h"
 #include "mailstore-private.h"
 #include <dirent.h>
+#include <stdbool.h>
+
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
+struct mailstore_priv {
+    void (*tag_new)(notmuch_message_t *message, const char *filename);
+    int  (*tag_renamed)(notmuch_message_t *message, const char *filename);
+};
+
+struct maildir_flag_tag {
+    char flag;
+    const char *tag;
+    bool inverse;
+};
+
+/* ASCII ordered table of Maildir flags and assiciated tags */
+struct maildir_flag_tag flag2tag[] = {
+    { 'D', "draft",   false},
+    { 'F', "flagged", false},
+    { 'P', "passed",  false},
+    { 'R', "replied", false},
+    { 'S', "unread",  true },
+    { 'T', "delete",  false},
+};
 
 typedef struct _filename_node {
     char *filename;
@@ -69,8 +93,9 @@ _filename_list_add (_filename_list_t *list,
 }
 
 static void
-tag_inbox_and_unread (notmuch_message_t *message)
+tag_inbox_and_unread (notmuch_message_t *message, const char *filename)
 {
+    (void)filename;
     notmuch_message_add_tag (message, "inbox");
     notmuch_message_add_tag (message, "unread");
 }
@@ -116,6 +141,156 @@ _entries_resemble_maildir (struct dirent **entries, int count)
     return 0;
 }
 
+static int
+tag_from_maildir_flags (notmuch_message_t *message, const char *filename)
+{
+    const char *flags, *p;
+    char f;
+    bool valid, unread;
+    unsigned i;
+
+    flags = strstr(filename, ":2,");
+    if (!flags)
+	return -1;
+    flags += 3;
+
+    /*  Check that the letters are valid Maildir flags */
+    f = 0;
+    valid = true;
+    for (p=flags; valid && *p; p++) {
+	switch (*p) {
+	case 'P':
+	case 'R':
+	case 'S':
+	case 'T':
+	case 'D':
+	case 'F':
+	    if (*p > f) f=*p;
+	    else valid = false;
+	break;
+	default:
+	    valid = false;
+	}
+    }
+    if (!valid) {
+	fprintf (stderr, "Warning: Invalid maildir flags in filename %s\n", filename);
+	return -1;
+    }
+
+    notmuch_message_freeze(message);
+    unread = true;
+    for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {
+	if ((strchr(flags, flag2tag[i].flag) != NULL) ^ flag2tag[i].inverse) {
+	    notmuch_message_add_tag (message, flag2tag[i].tag);
+	} else {
+	    notmuch_message_remove_tag (message, flag2tag[i].tag);
+	}
+    }
+    notmuch_message_thaw(message);
+
+    /* From now on, we can synchronize the tags from the database to
+     * the mailstore. */
+    notmuch_message_set_flag(message, NOTMUCH_MESSAGE_FLAG_TAGS_INVALID, FALSE);
+    return 0;
+}
+
+static void
+get_new_flags(notmuch_message_t *message, char *flags)
+{
+    notmuch_tags_t *tags;
+    const char *tag;
+    unsigned i;
+    char *p;
+
+    for (i = 0; i < ARRAY_SIZE(flag2tag); i++)
+	flags[i] = flag2tag[i].inverse ? flag2tag[i].flag : '\0';
+
+    for (tags = notmuch_message_get_tags (message);
+	 notmuch_tags_valid (tags);
+	 notmuch_tags_move_to_next (tags))
+    {
+	tag = notmuch_tags_get (tags);
+	for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {
+	    if (strcmp(tag, flag2tag[i].tag) == 0)
+		flags[i] = flag2tag[i].inverse ? '\0' : flag2tag[i].flag;
+	}
+    }
+
+    p = flags;
+    for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {
+	if (flags[i])
+	    *p++ = flags[i];
+    }
+    *p = '\0';
+}
+
+static char *
+get_subdir (char *filename)
+{
+    char *p, *subdir = NULL;
+
+    p = filename + strlen (filename) - 1;
+    while (p > filename + 3 && *p != '/')
+	p--;
+    if (*p == '/') {
+	subdir = p - 3;
+	if (subdir > filename && *(subdir - 1) != '/')
+	    subdir = NULL;
+    }
+    return subdir;
+}
+
+/* Store maildir-related tags as maildir flags */
+static notmuch_private_status_t
+maildir_sync_tags (notmuch_mailstore_t *mailstore,
+		   notmuch_message_t *message)
+{
+    char flags[ARRAY_SIZE(flag2tag)+1];
+    const char *filename, *p;
+    char *filename_new, *subdir = NULL;
+    int ret;
+
+    (void)mailstore;
+    get_new_flags (message, flags);
+
+    filename = notmuch_message_get_filename (message);
+    p = strstr(filename, ":2,");
+    if (!p)
+	p = filename + strlen(filename);
+
+    filename_new = talloc_size(message, (p-filename) + 3 + sizeof(flags));
+    if (unlikely (filename_new == NULL))
+	return NOTMUCH_STATUS_OUT_OF_MEMORY;
+    memcpy(filename_new, filename, p-filename);
+    filename_new[p-filename] = '\0';
+
+    /* If message is in new/ move it under cur/. */
+    subdir = get_subdir (filename_new);
+    if (subdir && memcmp (subdir, "new/", 4) == 0)
+	memcpy (subdir, "cur/", 4);
+
+    strcpy (filename_new+(p-filename), ":2,");
+    strcpy (filename_new+(p-filename)+3, flags);
+
+    if (strcmp (filename, filename_new) != 0) {
+	ret = rename (filename, filename_new);
+	if (ret == -1) {
+	    perror (talloc_asprintf (message, "rename of %s to %s failed", filename, filename_new));
+	    exit (1);
+	}
+	return _notmuch_message_rename (message, filename_new);
+	/* _notmuch_message_sync is our caller. Do not call it here. */
+    }
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+static void
+tag_inbox_and_maildir_flags (notmuch_message_t *message, const char *filename)
+{
+    notmuch_message_add_tag (message, "inbox");
+    if (tag_from_maildir_flags(message, filename) != 0)
+	notmuch_message_add_tag (message, "unread");
+}
 
 /* Examine 'path' recursively as follows:
  *
@@ -171,6 +346,7 @@ add_files_recursive (notmuch_mailstore_t *mailstore,
     struct stat st;
     notmuch_bool_t is_maildir, new_directory;
     _indexing_context_priv_t *priv = state->priv;
+    struct mailstore_priv *mailstore_priv = mailstore->priv;
     notmuch_database_t *notmuch = mailstore->notmuch;
 
     if (stat (path, &st)) {
@@ -352,11 +528,12 @@ add_files_recursive (notmuch_mailstore_t *mailstore,
 	/* success */
 	case NOTMUCH_STATUS_SUCCESS:
 	    state->added_messages++;
-	    tag_inbox_and_unread (message);
+	    mailstore_priv->tag_new (message, next);
 	    break;
 	/* Non-fatal issues (go on to next file) */
 	case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
-	    /* Stay silent on this one. */
+	    if (mailstore_priv->tag_renamed)
+		mailstore_priv->tag_renamed (message, next);
 	    break;
 	case NOTMUCH_STATUS_FILE_NOT_EMAIL:
 	    fprintf (stderr, "Note: Ignoring non-mail file: %s\n",
@@ -614,6 +791,10 @@ open_file(notmuch_mailstore_t *mailstore, const char *filename)
     return fopen (abs_filename, "r");
 }
 
+struct mailstore_priv files_priv = {
+    .tag_new = tag_inbox_and_unread,
+};
+
 /* Original notmuch mail store */
 struct _notmuch_mailstore mailstore_files = {
     .type = "files",
@@ -621,4 +802,20 @@ struct _notmuch_mailstore mailstore_files = {
     .index_new = index_new,
     .sync_tags = NULL,
     .open_file = open_file,
+    .priv = &files_priv,
+};
+
+struct mailstore_priv maildir_priv = {
+    .tag_new = tag_inbox_and_maildir_flags,
+    .tag_renamed = tag_from_maildir_flags,
+};
+
+/* Similar to mailstore_files, but does bi-directional synchronization between certain tags and maildir flags */
+struct _notmuch_mailstore mailstore_maildir = {
+    .type = "maildir",
+    .count_files = count_files,
+    .index_new = index_new,
+    .sync_tags = maildir_sync_tags,
+    .open_file = open_file,
+    .priv = &maildir_priv,
 };
diff --git a/lib/mailstore.c b/lib/mailstore.c
index cdac9a6..7dc55ad 100644
--- a/lib/mailstore.c
+++ b/lib/mailstore.c
@@ -25,6 +25,7 @@
 
 static notmuch_mailstore_t *available[] = {
     &mailstore_files,
+    &mailstore_maildir,
 };
 
 notmuch_mailstore_t *
diff --git a/lib/message.cc b/lib/message.cc
index c32ee7d..51208ba 100644
--- a/lib/message.cc
+++ b/lib/message.cc
@@ -561,7 +561,8 @@ _notmuch_message_sync (notmuch_message_t *message)
     if (message->notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
 	return;
 
-    if (message->notmuch->mailstore->sync_tags) {
+    if (message->notmuch->mailstore->sync_tags &&
+	!notmuch_message_get_flag(message, NOTMUCH_MESSAGE_FLAG_TAGS_INVALID)) {
 	status = message->notmuch->mailstore->sync_tags (message->notmuch->mailstore,
 							 message);
 	if (status != NOTMUCH_PRIVATE_STATUS_SUCCESS) {
@@ -683,6 +684,44 @@ _notmuch_message_remove_term (notmuch_message_t *message,
     return NOTMUCH_PRIVATE_STATUS_SUCCESS;
 }
 
+/* Change the message filename stored in the database.
+ *
+ * This change will not be reflected in the database until the next
+ * call to _notmuch_message_sync.
+ */
+notmuch_private_status_t
+_notmuch_message_rename (notmuch_message_t *message,
+			 const char *new_filename)
+{
+    void *local = talloc_new (message);
+    char *direntry;
+    Xapian::PostingIterator i, end;
+    Xapian::Document document;
+    notmuch_private_status_t pstatus;
+    notmuch_status_t status;
+    const char *old_filename;
+
+    old_filename = notmuch_message_get_filename(message);
+    old_filename = talloc_reference(local, old_filename);
+    if (unlikely(!old_filename))
+	return NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
+
+    status = _notmuch_message_add_filename (message, new_filename);
+    if (status)
+	return (notmuch_private_status_t)status;
+
+    status = _notmuch_database_filename_to_direntry (local, message->notmuch,
+						     old_filename, &direntry);
+    if (status)
+	return (notmuch_private_status_t)status;
+
+    pstatus = _notmuch_message_remove_term (message, "file-direntry", direntry);
+
+    talloc_free (local);
+
+    return pstatus;
+}
+
 notmuch_status_t
 notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
 {
diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h
index d52d84d..63e75ec 100644
--- a/lib/notmuch-private.h
+++ b/lib/notmuch-private.h
@@ -245,6 +245,10 @@ notmuch_status_t
 _notmuch_message_add_filename (notmuch_message_t *message,
 			       const char *filename);
 
+notmuch_private_status_t
+_notmuch_message_rename (notmuch_message_t *message,
+			 const char *new_filename);
+
 void
 _notmuch_message_ensure_thread_id (notmuch_message_t *message);
 
diff --git a/lib/notmuch.h b/lib/notmuch.h
index 8fa7b31..c554f6d 100644
--- a/lib/notmuch.h
+++ b/lib/notmuch.h
@@ -760,6 +760,7 @@ notmuch_message_get_filename (notmuch_message_t *message);
 /* Message flags */
 typedef enum _notmuch_message_flag {
     NOTMUCH_MESSAGE_FLAG_MATCH,
+    NOTMUCH_MESSAGE_FLAG_TAGS_INVALID,
 } notmuch_message_flag_t;
 
 /* Get a value of a flag for the email corresponding to 'message'. */
-- 
1.7.0.2

  parent reply	other threads:[~2010-03-27 20:44 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-03-18 15:39 Mailstore abstraction & maildir synchronization Michal Sojka
2010-03-18 15:39 ` [PATCH 1/4] Mailstore abstraction interface Michal Sojka
2010-03-18 15:39 ` [PATCH 2/4] Convert mailstore abstraction Michal Sojka
2010-03-18 15:39 ` [PATCH 3/4] Add maildir-based mailstore Michal Sojka
2010-03-23 10:56   ` Ruben Pollan
2010-03-23 13:35     ` Michal Sojka
2010-03-23 14:37       ` Ruben Pollan
2010-03-26 21:41         ` Michal Sojka
2010-03-26 21:42           ` [PATCH v2 1/4] Mailstore abstraction interface Michal Sojka
2010-03-26 21:42           ` [PATCH v2 2/4] Conversion to mailstore abstraction Michal Sojka
2010-03-26 21:42           ` [PATCH v2 3/4] Add maildir-based mailstore Michal Sojka
2010-03-26 21:42           ` [PATCH v2 4/4] Tests for " Michal Sojka
2010-03-18 15:39 ` [PATCH " Michal Sojka
2010-03-23 10:59   ` Ruben Pollan
2010-03-23 13:47     ` Michal Sojka
2010-03-24  5:06 ` Mailstore abstraction & maildir synchronization Stewart Smith
2010-03-26 22:08   ` Michal Sojka
2010-03-27 20:42     ` Michal Sojka
2010-03-27 20:44       ` [PATCH v3 1/6] Mailstore abstraction interface Michal Sojka
2010-03-27 20:44       ` [PATCH v3 2/6] Conversion to mailstore abstraction Michal Sojka
2010-03-27 20:44       ` Michal Sojka [this message]
2010-03-27 20:44       ` [PATCH v3 4/6] Tests for maildir-based mailstore Michal Sojka
2010-03-27 20:44       ` [PATCH v3 5/6] Access messages through mail store interface Michal Sojka
2010-03-27 20:44       ` [PATCH v3 6/6] Add 'cat' subcommand Michal Sojka
2010-03-27 20:49       ` Mailstore abstraction & maildir synchronization Michal Sojka

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://notmuchmail.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1269722661-6894-3-git-send-email-sojkam1@fel.cvut.cz \
    --to=sojkam1@fel.cvut.cz \
    --cc=notmuch@notmuchmail.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).