--- /dev/null
+Return-Path: <amthrax@drake.mit.edu>\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 28D41429E4A\r
+ for <notmuch@notmuchmail.org>; Thu, 17 Feb 2011 23:59:38 -0800 (PST)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: 0\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=0 tagged_above=-999 required=5\r
+ tests=[RCVD_IN_DNSWL_NONE=-0.0001] autolearn=disabled\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 ZT6zN79RrO8j for <notmuch@notmuchmail.org>;\r
+ Thu, 17 Feb 2011 23:59:36 -0800 (PST)\r
+Received: from dmz-mailsec-scanner-7.mit.edu (DMZ-MAILSEC-SCANNER-7.MIT.EDU\r
+ [18.7.68.36])\r
+ by olra.theworths.org (Postfix) with ESMTP id 33F61429E38\r
+ for <notmuch@notmuchmail.org>; Thu, 17 Feb 2011 23:59:29 -0800 (PST)\r
+X-AuditID: 12074424-b7b0bae000000a05-6d-4d5e26e04bad\r
+Received: from mailhub-auth-3.mit.edu ( [18.9.21.43])\r
+ by dmz-mailsec-scanner-7.mit.edu (Symantec Brightmail Gateway) with\r
+ SMTP id 8D.70.02565.0E62E5D4; Fri, 18 Feb 2011 02:59:28 -0500 (EST)\r
+Received: from outgoing.mit.edu (OUTGOING-AUTH.MIT.EDU [18.7.22.103])\r
+ by mailhub-auth-3.mit.edu (8.13.8/8.9.2) with ESMTP id p1I7xRwj029792; \r
+ Fri, 18 Feb 2011 02:59:27 -0500\r
+Received: from drake.mit.edu\r
+ (209-6-116-242.c3-0.arl-ubr1.sbo-arl.ma.cable.rcn.com\r
+ [209.6.116.242]) (authenticated bits=0)\r
+ (User authenticated as amdragon@ATHENA.MIT.EDU)\r
+ by outgoing.mit.edu (8.13.6/8.12.4) with ESMTP id p1I7xQp1001544\r
+ (version=TLSv1/SSLv3 cipher=AES256-SHA bits=256 verify=NOT);\r
+ Fri, 18 Feb 2011 02:59:27 -0500 (EST)\r
+Received: from amthrax by drake.mit.edu with local (Exim 4.72)\r
+ (envelope-from <amthrax@drake.mit.edu>)\r
+ id 1PqLFO-0008LK-Jg; Fri, 18 Feb 2011 02:59:26 -0500\r
+From: Austin Clements <amdragon@MIT.EDU>\r
+To: notmuch@notmuchmail.org\r
+Subject: [PATCH 06/10] lib: Add API's to find by filename and remove a\r
+ filename from a message.\r
+Date: Fri, 18 Feb 2011 02:58:56 -0500\r
+Message-Id: <1298015940-31986-7-git-send-email-amdragon@mit.edu>\r
+X-Mailer: git-send-email 1.7.2.3\r
+In-Reply-To: <1298015940-31986-1-git-send-email-amdragon@mit.edu>\r
+References: <1298015940-31986-1-git-send-email-amdragon@mit.edu>\r
+X-Brightmail-Tracker: AAAAAA==\r
+Cc: amdragon@mit.edu\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\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: Fri, 18 Feb 2011 07:59:38 -0000\r
+\r
+The two new API functions, notmuch_database_find_message_by_filename\r
+and notmuch_message_remove_filename give library users more control\r
+over the filename removal process. notmuch_database_remove_message\r
+has been reimplemented in terms of these new functions.\r
+\r
+notmuch_message_remove_filename acts much like\r
+notmuch_message_remove_tag in that it does not synchronize with the\r
+database if the message is frozen. Thus, callers can freeze a message\r
+to remove a filename and perform tag synchronization in one atomic\r
+operation.\r
+\r
+This new approach also naturally eliminates an atomicity violation in\r
+the old code. Previously, notmuch_database_remove_message would first\r
+update the database document to remove the filename and only then\r
+remove the document if it had no more filenames left. An interruption\r
+between these two steps resulted in a permanently un-removable zombie\r
+message that would produce errors and odd results in searches. Since\r
+this new approach delegates document deletion to\r
+_notmuch_message_sync, the document will be deleted without first\r
+being updated, eliminating this window.\r
+---\r
+ lib/database.cc | 58 +++++++++++++++++++-----------------------------------\r
+ lib/message.cc | 21 +++++++++++++++++++\r
+ lib/notmuch.h | 43 ++++++++++++++++++++++++++++++++++++++-\r
+ 3 files changed, 83 insertions(+), 39 deletions(-)\r
+\r
+diff --git a/lib/database.cc b/lib/database.cc\r
+index d88b168..bee1e96 100644\r
+--- a/lib/database.cc\r
++++ b/lib/database.cc\r
+@@ -1689,72 +1689,56 @@ notmuch_status_t\r
+ notmuch_database_remove_message (notmuch_database_t *notmuch,\r
+ const char *filename)\r
+ {\r
+- Xapian::WritableDatabase *db;\r
++ notmuch_message_t *message;\r
++ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;\r
++ message = notmuch_database_find_message_by_filename (notmuch, filename);\r
++ if (message) {\r
++ status = notmuch_message_remove_filename (message, filename);\r
++ notmuch_message_destroy (message);\r
++ }\r
++ return status;\r
++}\r
++\r
++notmuch_message_t *\r
++notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,\r
++ const char *filename)\r
++{\r
+ void *local;\r
+ const char *prefix = _find_prefix ("file-direntry");\r
+ char *direntry, *term;\r
+ Xapian::PostingIterator i, end;\r
+- Xapian::Document document;\r
++ notmuch_message_t *message = NULL;\r
+ notmuch_status_t status;\r
+ \r
+- status = _notmuch_database_ensure_writable (notmuch);\r
+- if (status)\r
+- return status;\r
+-\r
+ local = talloc_new (notmuch);\r
+ \r
+- db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);\r
+-\r
+ try {\r
+ \r
+ status = _notmuch_database_filename_to_direntry (local, notmuch,\r
+ filename, &direntry);\r
+ if (status)\r
+- return status;\r
++ return NULL;\r
+ \r
+ term = talloc_asprintf (local, "%s%s", prefix, direntry);\r
+ \r
+ find_doc_ids_for_term (notmuch, term, &i, &end);\r
+ \r
+- for ( ; i != end; i++) {\r
+- Xapian::TermIterator j;\r
+- notmuch_message_t *message;\r
++ if (i != end) {\r
+ notmuch_private_status_t private_status;\r
+ \r
+- message = _notmuch_message_create (local, notmuch,\r
++ message = _notmuch_message_create (notmuch, notmuch,\r
+ *i, &private_status);\r
+- if (message == NULL)\r
+- return COERCE_STATUS (private_status,\r
+- "Inconsistent document ID in datbase.");\r
+-\r
+- _notmuch_message_remove_filename (message, filename);\r
+- _notmuch_message_sync (message);\r
+-\r
+- /* Take care to find document after sync'ing filename removal. */\r
+- document = find_document_for_doc_id (notmuch, *i);\r
+- j = document.termlist_begin ();\r
+- j.skip_to (prefix);\r
+-\r
+- /* Was this the last file-direntry in the message? */\r
+- if (j == document.termlist_end () ||\r
+- strncmp ((*j).c_str (), prefix, strlen (prefix)))\r
+- {\r
+- db->delete_document (document.get_docid ());\r
+- status = NOTMUCH_STATUS_SUCCESS;\r
+- } else {\r
+- status = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;\r
+- }\r
+ }\r
+ } catch (const Xapian::Error &error) {\r
+- fprintf (stderr, "Error: A Xapian exception occurred removing message: %s\n",\r
++ fprintf (stderr, "Error: A Xapian exception occurred finding message by filename: %s\n",\r
+ error.get_msg().c_str());\r
+ notmuch->exception_reported = TRUE;\r
+- status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;\r
++ message = NULL;\r
+ }\r
+ \r
+ talloc_free (local);\r
+ \r
+- return status;\r
++ return message;\r
+ }\r
+ \r
+ notmuch_tags_t *\r
+diff --git a/lib/message.cc b/lib/message.cc\r
+index 635f5cf..b4adb5c 100644\r
+--- a/lib/message.cc\r
++++ b/lib/message.cc\r
+@@ -1300,6 +1300,27 @@ notmuch_message_remove_all_tags (notmuch_message_t *message)\r
+ }\r
+ \r
+ notmuch_status_t\r
++notmuch_message_remove_filename (notmuch_message_t *message,\r
++ const char *filename)\r
++{\r
++ notmuch_status_t status;\r
++\r
++ status = _notmuch_database_ensure_writable (message->notmuch);\r
++ if (status)\r
++ return status;\r
++\r
++ status = _notmuch_message_remove_filename (message, filename);\r
++ /* Was this the last file-direntry in the message? */\r
++ if (status == NOTMUCH_STATUS_SUCCESS)\r
++ message->deleted = TRUE;\r
++\r
++ if (! message->frozen)\r
++ _notmuch_message_sync (message);\r
++\r
++ return status;\r
++}\r
++\r
++notmuch_status_t\r
+ notmuch_message_freeze (notmuch_message_t *message)\r
+ {\r
+ notmuch_status_t status;\r
+diff --git a/lib/notmuch.h b/lib/notmuch.h\r
+index e508309..61030cb 100644\r
+--- a/lib/notmuch.h\r
++++ b/lib/notmuch.h\r
+@@ -316,6 +316,22 @@ notmuch_message_t *\r
+ notmuch_database_find_message (notmuch_database_t *database,\r
+ const char *message_id);\r
+ \r
++/* Find a message with the given filename.\r
++ *\r
++ * If the database contains a message with the given filename, then a\r
++ * new notmuch_message_t object is returned. The caller should call \r
++ * notmuch_message_destroy when done with the message.\r
++ *\r
++ * This function returns NULL in the following situations:\r
++ *\r
++ * * No message is found with the given filename\r
++ * * An out-of-memory situation occurs\r
++ * * A Xapian exception occurs\r
++ */\r
++notmuch_message_t *\r
++notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,\r
++ const char *filename);\r
++\r
+ /* Return a list of all tags found in the database.\r
+ *\r
+ * This function creates a list of all tags found in the database. The\r
+@@ -979,11 +995,34 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message);\r
+ notmuch_status_t\r
+ notmuch_message_tags_to_maildir_flags (notmuch_message_t *message);\r
+ \r
++/* Remove a filename from a message. If this is the last copy of this\r
++ * message, also delete it from the database.\r
++ *\r
++ * Much like notmuch_message_remove_tag, if message is frozen, it will\r
++ * not be removed from or updated in the database until thawed.\r
++ *\r
++ * Return value:\r
++ *\r
++ * NOTMUCH_STATUS_SUCCESS: The last filename was removed and the\r
++ * message was removed from the database.\r
++ *\r
++ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: This filename was removed but\r
++ * the message persists in the database with at least one other\r
++ * filename.\r
++ *\r
++ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only\r
++ * mode so no message can be removed.\r
++ */\r
++notmuch_status_t\r
++notmuch_message_remove_filename (notmuch_message_t *message,\r
++ const char *filename);\r
++\r
+ /* Freeze the current state of 'message' within the database.\r
+ *\r
+ * This means that changes to the message state, (via\r
+- * notmuch_message_add_tag, notmuch_message_remove_tag, and\r
+- * notmuch_message_remove_all_tags), will not be committed to the\r
++ * notmuch_message_add_tag, notmuch_message_remove_tag,\r
++ * notmuch_message_remove_all_tags, and\r
++ * notmuch_message_remove_filename), will not be committed to the\r
+ * database until the message is thawed with notmuch_message_thaw.\r
+ *\r
+ * Multiple calls to freeze/thaw are valid and these calls will\r
+-- \r
+1.7.2.3\r
+\r