return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)";
case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
return "Unbalanced number of calls to notmuch_message_freeze/thaw";
+ case NOTMUCH_STATUS_UNBALANCED_ATOMIC:
+ return "Unbalanced number of calls to notmuch_database_begin_atomic/end_atomic";
default:
case NOTMUCH_STATUS_LAST_STATUS:
return "Unknown error status value";
notmuch->needs_upgrade = FALSE;
notmuch->mode = mode;
+ notmuch->atomic_nesting = 0;
try {
string last_thread_id;
return NOTMUCH_STATUS_SUCCESS;
}
+notmuch_status_t
+notmuch_database_begin_atomic (notmuch_database_t *notmuch)
+{
+ if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
+ notmuch->atomic_nesting > 0)
+ goto DONE;
+
+ try {
+ (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))->begin_transaction (false);
+ } catch (const Xapian::Error &error) {
+ fprintf (stderr, "A Xapian exception occurred beginning transaction: %s.\n",
+ error.get_msg().c_str());
+ notmuch->exception_reported = TRUE;
+ return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+ }
+
+DONE:
+ notmuch->atomic_nesting++;
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_database_end_atomic (notmuch_database_t *notmuch)
+{
+ Xapian::WritableDatabase *db;
+
+ if (notmuch->atomic_nesting == 0)
+ return NOTMUCH_STATUS_UNBALANCED_ATOMIC;
+
+ if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
+ notmuch->atomic_nesting > 1)
+ goto DONE;
+
+ db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+ try {
+ db->commit_transaction ();
+
+ /* This is a hack for testing. Xapian never flushes on a
+ * non-flushed commit, even if the flush threshold is 1.
+ * However, we rely on flushing to test atomicity. */
+ const char *thresh = getenv ("XAPIAN_FLUSH_THRESHOLD");
+ if (thresh && atoi (thresh) == 1)
+ db->commit ();
+ } catch (const Xapian::Error &error) {
+ fprintf (stderr, "A Xapian exception occurred committing transaction: %s.\n",
+ error.get_msg().c_str());
+ notmuch->exception_reported = TRUE;
+ return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+ }
+
+DONE:
+ notmuch->atomic_nesting--;
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
/* We allow the user to use arbitrarily long paths for directories. But
* we have a term-length limit. So if we exceed that, we'll use the
* SHA-1 of the path for the database term.
{
notmuch_message_file_t *message_file;
notmuch_message_t *message = NULL;
- notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS, ret2;
notmuch_private_status_t private_status;
const char *date, *header;
if (message_file == NULL)
return NOTMUCH_STATUS_FILE_ERROR;
+ /* Adding a message may change many documents. Do this all
+ * atomically. */
+ ret = notmuch_database_begin_atomic (notmuch);
+ if (ret)
+ goto DONE;
+
notmuch_message_file_restrict_headers (message_file,
"date",
"from",
if (message_file)
notmuch_message_file_close (message_file);
+ ret2 = notmuch_database_end_atomic (notmuch);
+ if ((ret == NOTMUCH_STATUS_SUCCESS ||
+ ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) &&
+ ret2 != NOTMUCH_STATUS_SUCCESS)
+ ret = ret2;
+
return ret;
}
notmuch_database_remove_message (notmuch_database_t *notmuch,
const char *filename)
{
- Xapian::WritableDatabase *db;
+ notmuch_message_t *message =
+ notmuch_database_find_message_by_filename (notmuch, filename);
+ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+
+ if (message) {
+ status = _notmuch_message_remove_filename (message, filename);
+ if (status == NOTMUCH_STATUS_SUCCESS)
+ _notmuch_message_delete (message);
+ else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
+ _notmuch_message_sync (message);
+ }
+
+ return status;
+}
+
+notmuch_message_t *
+notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
+ const char *filename)
+{
void *local;
const char *prefix = _find_prefix ("file-direntry");
char *direntry, *term;
Xapian::PostingIterator i, end;
- Xapian::Document document;
+ notmuch_message_t *message = NULL;
notmuch_status_t status;
- status = _notmuch_database_ensure_writable (notmuch);
- if (status)
- return status;
-
local = talloc_new (notmuch);
- db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-
try {
-
status = _notmuch_database_filename_to_direntry (local, notmuch,
filename, &direntry);
if (status)
- return status;
+ return NULL;
term = talloc_asprintf (local, "%s%s", prefix, direntry);
find_doc_ids_for_term (notmuch, term, &i, &end);
- for ( ; i != end; i++) {
- Xapian::TermIterator j;
- notmuch_message_t *message;
+ if (i != end) {
notmuch_private_status_t private_status;
- message = _notmuch_message_create (local, notmuch,
+ message = _notmuch_message_create (notmuch, notmuch,
*i, &private_status);
- if (message == NULL)
- return COERCE_STATUS (private_status,
- "Inconsistent document ID in datbase.");
-
- _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);
-
- /* Was this the last file-direntry in the message? */
- if (j == document.termlist_end () ||
- strncmp ((*j).c_str (), prefix, strlen (prefix)))
- {
- db->delete_document (document.get_docid ());
- status = NOTMUCH_STATUS_SUCCESS;
- } else {
- status = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
- }
}
} catch (const Xapian::Error &error) {
- fprintf (stderr, "Error: A Xapian exception occurred removing message: %s\n",
+ fprintf (stderr, "Error: A Xapian exception occurred finding message by filename: %s\n",
error.get_msg().c_str());
notmuch->exception_reported = TRUE;
- status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+ message = NULL;
}
talloc_free (local);
- return status;
+ return message;
}
notmuch_string_list_t *