X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=lib%2Fdatabase.cc;h=9299c8d52e3c3e0a93c6906a64956236175abb60;hp=9c2f4ecdc58ff2bcb6de93eba16b8ddc3dfd0b00;hb=51c3c0b2d27c8d0ba8e1ae242fd907c59f82d992;hpb=22472d9def2f6525a9aac62e6481d8d4fa7db5d0 diff --git a/lib/database.cc b/lib/database.cc index 9c2f4ecd..9299c8d5 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -273,6 +273,8 @@ notmuch_status_to_string (notmuch_status_t status) 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"; @@ -611,6 +613,7 @@ notmuch_database_open (const char *path, notmuch->needs_upgrade = FALSE; notmuch->mode = mode; + notmuch->atomic_nesting = 0; try { string last_thread_id; @@ -974,6 +977,61 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, 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 (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 (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. @@ -1543,7 +1601,7 @@ notmuch_database_add_message (notmuch_database_t *notmuch, { 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; @@ -1561,6 +1619,12 @@ notmuch_database_add_message (notmuch_database_t *notmuch, 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", @@ -1682,6 +1746,12 @@ notmuch_database_add_message (notmuch_database_t *notmuch, 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; } @@ -1689,72 +1759,60 @@ notmuch_status_t 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 (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 *