--- /dev/null
+Return-Path: <tomc@caurea.org>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+ by olra.theworths.org (Postfix) with ESMTP id 8F53C431FBC\r
+ for <notmuch@notmuchmail.org>; Mon, 21 Dec 2009 18:57:06 -0800 (PST)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+ by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id v-NFs4nqX+IY for <notmuch@notmuchmail.org>;\r
+ Mon, 21 Dec 2009 18:57:06 -0800 (PST)\r
+X-Greylist: delayed 135368 seconds by postgrey-1.32 at olra;\r
+ Mon, 21 Dec 2009 18:57:05 PST\r
+Received: from hobbes.caurea.org\r
+ (gw.ptr-62-65-144-46.customer.ch.netstream.com [62.65.144.46])\r
+ by olra.theworths.org (Postfix) with ESMTP id DB350431FAE\r
+ for <notmuch@notmuchmail.org>; Mon, 21 Dec 2009 18:57:05 -0800 (PST)\r
+Received: by hobbes.caurea.org (Postfix, from userid 101)\r
+ id 255592A5F5; Tue, 22 Dec 2009 03:57:03 +0100 (CET)\r
+From: Tomas Carnecky <tom@dbservice.com>\r
+To: notmuch@notmuchmail.org\r
+Date: Tue, 22 Dec 2009 03:56:57 +0100\r
+Message-Id: <1261450617-24616-1-git-send-email-tom@dbservice.com>\r
+X-Mailer: git-send-email 1.6.6.rc3\r
+Subject: [notmuch] [PATCH] Add post-add and post-tag hooks\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.12\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Tue, 22 Dec 2009 02:57:06 -0000\r
+\r
+The post-add hook is run by 'notmuch new' after each new message is added,\r
+post-tag is run after a tag has been added or removed. The hooks are stored\r
+in the users home directory (~/.notmuch/hooks/).\r
+\r
+Since post-tag is run unconditionally every time a new tag is added or removed,\r
+that means it is also invoked when 'notmuch new' adds the two implicit\r
+tags (inbox, unread). So make sure your scripts don't choke on that and can\r
+be both executed in parallel.\r
+\r
+Signed-off-by: Tomas Carnecky <tom@dbservice.com>\r
+---\r
+ lib/message.cc | 45 ++++++++++++++++++++++++++++++++++++++\r
+ notmuch-new.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\r
+ 2 files changed, 111 insertions(+), 0 deletions(-)\r
+\r
+diff --git a/lib/message.cc b/lib/message.cc\r
+index 49519f1..bcd8abb 100644\r
+--- a/lib/message.cc\r
++++ b/lib/message.cc\r
+@@ -664,6 +664,47 @@ _notmuch_message_remove_term (notmuch_message_t *message,\r
+ return NOTMUCH_PRIVATE_STATUS_SUCCESS;\r
+ }\r
+ \r
++/* Run the post-tag hook */\r
++static void\r
++post_tag_hook (notmuch_message_t *message, const char *tag, int added)\r
++{\r
++ /* Skip tags that notmuch itself assigns to new messages */\r
++ const char *skip[] = {\r
++ "inbox", "unread"\r
++ };\r
++\r
++ for (int i = 0; i < sizeof (skip) / sizeof (skip[0]); ++i) {\r
++ if (strcmp(skip[i], tag) == 0)\r
++ return;\r
++ }\r
++\r
++ char proc[PATH_MAX];\r
++ snprintf (proc, PATH_MAX, "%s/.notmuch/hooks/post-tag", getenv("HOME"));\r
++ if (access (proc, X_OK))\r
++ return;\r
++\r
++ int pid = fork ();\r
++ if (pid == -1)\r
++ return;\r
++\r
++ /* Wait for the hook to finish. This behaviour might be changed in the\r
++ * future, but for now I think it's better to take the safe route. */\r
++ if (pid > 0) {\r
++ waitpid (0, NULL, 0);\r
++ return;\r
++ }\r
++\r
++ const char *filename = notmuch_message_get_filename (message);\r
++ const char *message_id = notmuch_message_get_message_id (message);\r
++\r
++ const char *args[] = {\r
++ proc, message_id, filename, tag, added ? "added" : "removed", NULL\r
++ };\r
++\r
++ execv (proc, (char *const *) &args);\r
++ exit (0);\r
++}\r
++\r
+ notmuch_status_t\r
+ notmuch_message_add_tag (notmuch_message_t *message, const char *tag)\r
+ {\r
+@@ -684,6 +725,8 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag)\r
+ if (! message->frozen)\r
+ _notmuch_message_sync (message);\r
+ \r
++ post_tag_hook (message, tag, 1);\r
++\r
+ return NOTMUCH_STATUS_SUCCESS;\r
+ }\r
+ \r
+@@ -707,6 +750,8 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)\r
+ if (! message->frozen)\r
+ _notmuch_message_sync (message);\r
+ \r
++ post_tag_hook (message, tag, 0);\r
++\r
+ return NOTMUCH_STATUS_SUCCESS;\r
+ }\r
+ \r
+diff --git a/notmuch-new.c b/notmuch-new.c\r
+index 837ae4f..d984aae 100644\r
+--- a/notmuch-new.c\r
++++ b/notmuch-new.c\r
+@@ -42,6 +42,71 @@ handle_sigint (unused (int sig))\r
+ interrupted = 1;\r
+ }\r
+ \r
++/* Run the post-add hook. The hook is given the chance to specify additional tags\r
++ * that should be added to the message. The hook writes the tags to its stdout,\r
++ * separated by a newline. The script's stdout is redirected to a pipe so that\r
++ * notmuch can process its output. The tags can be prefixed with '+' or '-' to\r
++ * indicate if the tag should be added or removed. Absence of one of these prefixes\r
++ * means that the tag will be added. */\r
++static void\r
++post_add_hook (notmuch_message_t *message)\r
++{\r
++ char proc[PATH_MAX];\r
++ snprintf (proc, PATH_MAX, "%s/.notmuch/hooks/post-add", getenv ("HOME"));\r
++ if (access (proc, X_OK))\r
++ return;\r
++\r
++ /* The pipe between the hook and the notmuch process. The script writes\r
++ * into fds[0], notmuch reads from fds[1]. */\r
++ int fds[2];\r
++ if (pipe (fds))\r
++ return;\r
++\r
++ int pid = fork ();\r
++ if (pid == -1) {\r
++ close (fds[0]);\r
++ close (fds[1]);\r
++ return;\r
++ } else if (pid > 0) {\r
++ close (fds[0]);\r
++ waitpid (0, NULL, 0);\r
++\r
++ char buffer[256] = { 0, };\r
++ read (fds[1], buffer, sizeof (buffer));\r
++\r
++ char *tag;\r
++ for (tag = buffer; tag && *tag; ) {\r
++ char *end = strchr (tag, '\n');\r
++ if (end)\r
++ *end = 0;\r
++\r
++ if (tag[0] == '+')\r
++ notmuch_message_add_tag (message, tag + 1);\r
++ else if (tag[0] == '-')\r
++ notmuch_message_remove_tag (message, tag + 1);\r
++ else\r
++ notmuch_message_add_tag (message, tag);\r
++\r
++ tag = end ? end + 1 : end;\r
++ }\r
++\r
++ return;\r
++ }\r
++\r
++ /* This is the child process (where the hook runs) */\r
++ close (fds[1]);\r
++ dup2 (fds[0], 1);\r
++\r
++ const char *filename = notmuch_message_get_filename (message);\r
++ const char *message_id = notmuch_message_get_message_id (message);\r
++ const char *args[] = {\r
++ proc, message_id, filename, NULL\r
++ };\r
++\r
++ execv (proc, (char *const *) &args);\r
++ exit (0);\r
++}\r
++\r
+ static void\r
+ tag_inbox_and_unread (notmuch_message_t *message)\r
+ {\r
+@@ -253,6 +318,7 @@ add_files_recursive (notmuch_database_t *notmuch,\r
+ case NOTMUCH_STATUS_SUCCESS:\r
+ state->added_messages++;\r
+ tag_inbox_and_unread (message);\r
++ post_add_hook (message);\r
+ break;\r
+ /* Non-fatal issues (go on to next file) */\r
+ case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:\r
+-- \r
+1.6.6.rc3\r
+\r