X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=lib%2Fdatabase.cc;h=94022d7ce3ae9c060ecf18baaca1aa95afa11781;hp=9c2f4ecdc58ff2bcb6de93eba16b8ddc3dfd0b00;hb=6c0adab23eadb81bb1254f4b82175902df11bb3f;hpb=68a2c7a8b0f749cb33a8ce7cfa2aa7781d2529bb diff --git a/lib/database.cc b/lib/database.cc index 9c2f4ecd..94022d7c 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -26,6 +26,9 @@ #include #include /* g_free, GPtrArray, GHashTable */ +#include /* g_type_init */ + +#include /* g_mime_init */ using namespace std; @@ -80,13 +83,17 @@ typedef struct { * STRING is the name of a file within that * directory for this mail message. * - * A mail document also has two values: + * A mail document also has four values: * * TIMESTAMP: The time_t value corresponding to the message's * Date header. * * MESSAGE_ID: The unique ID of the mail mess (see "id" above) * + * FROM: The value of the "From" header + * + * SUBJECT: The value of the "Subject" header + * * In addition, terms from the content of the message are added with * "from", "to", "attachment", and "subject" prefixes for use by the * user in searching. Similarly, terms from the path of the mail @@ -209,21 +216,6 @@ static prefix_t PROBABILISTIC_PREFIX[]= { { "folder", "XFOLDER"} }; -int -_internal_error (const char *format, ...) -{ - va_list va_args; - - va_start (va_args, format); - - fprintf (stderr, "Internal error: "); - vfprintf (stderr, format, va_args); - - exit (1); - - return 1; -} - const char * _find_prefix (const char *name) { @@ -273,6 +265,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"; @@ -358,13 +352,17 @@ _message_id_compressed (void *ctx, const char *message_id) return compressed; } -notmuch_message_t * +notmuch_status_t notmuch_database_find_message (notmuch_database_t *notmuch, - const char *message_id) + const char *message_id, + notmuch_message_t **message_ret) { notmuch_private_status_t status; unsigned int doc_id; + if (message_ret == NULL) + return NOTMUCH_STATUS_NULL_POINTER; + if (strlen (message_id) > NOTMUCH_MESSAGE_ID_MAX) message_id = _message_id_compressed (notmuch, message_id); @@ -373,14 +371,21 @@ notmuch_database_find_message (notmuch_database_t *notmuch, message_id, &doc_id); if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) - return NULL; + *message_ret = NULL; + else { + *message_ret = _notmuch_message_create (notmuch, notmuch, doc_id, + NULL); + if (*message_ret == NULL) + return NOTMUCH_STATUS_OUT_OF_MEMORY; + } - return _notmuch_message_create (notmuch, notmuch, doc_id, NULL); + return NOTMUCH_STATUS_SUCCESS; } catch (const Xapian::Error &error) { fprintf (stderr, "A Xapian exception occurred finding message: %s.\n", error.get_msg().c_str()); notmuch->exception_reported = TRUE; - return NULL; + *message_ret = NULL; + return NOTMUCH_STATUS_XAPIAN_EXCEPTION; } } @@ -582,6 +587,7 @@ notmuch_database_open (const char *path, struct stat st; int err; unsigned int i, version; + static int initialized = 0; if (asprintf (¬much_path, "%s/%s", path, ".notmuch") == -1) { notmuch_path = NULL; @@ -602,7 +608,16 @@ notmuch_database_open (const char *path, goto DONE; } - notmuch = talloc (NULL, notmuch_database_t); + /* Initialize the GLib type system and threads */ + g_type_init (); + + /* Initialize gmime */ + if (! initialized) { + g_mime_init (0); + initialized = 1; + } + + notmuch = talloc_zero (NULL, notmuch_database_t); notmuch->exception_reported = FALSE; notmuch->path = talloc_strdup (notmuch, path); @@ -611,6 +626,7 @@ notmuch_database_open (const char *path, notmuch->needs_upgrade = FALSE; notmuch->mode = mode; + notmuch->atomic_nesting = 0; try { string last_thread_id; @@ -687,6 +703,7 @@ notmuch_database_open (const char *path, } catch (const Xapian::Error &error) { fprintf (stderr, "A Xapian exception occurred opening database: %s\n", error.get_msg().c_str()); + notmuch_database_close (notmuch); notmuch = NULL; } @@ -974,6 +991,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->flush (); + } 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. @@ -1253,7 +1325,9 @@ _get_metadata_thread_id_key (void *ctx, const char *message_id) /* Find the thread ID to which the message with 'message_id' belongs. * - * Always returns a newly talloced string belonging to 'ctx'. + * Note: 'thread_id_ret' must not be NULL! + * On success '*thread_id_ret' is set to a newly talloced string belonging to + * 'ctx'. * * Note: If there is no message in the database with the given * 'message_id' then a new thread_id will be allocated for this @@ -1261,25 +1335,30 @@ _get_metadata_thread_id_key (void *ctx, const char *message_id) * thread ID can be looked up if the message is added to the database * later). */ -static const char * +static notmuch_status_t _resolve_message_id_to_thread_id (notmuch_database_t *notmuch, void *ctx, - const char *message_id) + const char *message_id, + const char **thread_id_ret) { + notmuch_status_t status; notmuch_message_t *message; string thread_id_string; - const char *thread_id; char *metadata_key; Xapian::WritableDatabase *db; - message = notmuch_database_find_message (notmuch, message_id); + status = notmuch_database_find_message (notmuch, message_id, &message); + + if (status) + return status; if (message) { - thread_id = talloc_steal (ctx, notmuch_message_get_thread_id (message)); + *thread_id_ret = talloc_steal (ctx, + notmuch_message_get_thread_id (message)); notmuch_message_destroy (message); - return thread_id; + return NOTMUCH_STATUS_SUCCESS; } /* Message has not been seen yet. @@ -1293,15 +1372,16 @@ _resolve_message_id_to_thread_id (notmuch_database_t *notmuch, thread_id_string = notmuch->xapian_db->get_metadata (metadata_key); if (thread_id_string.empty()) { - thread_id = _notmuch_database_generate_thread_id (notmuch); - db->set_metadata (metadata_key, thread_id); + *thread_id_ret = talloc_strdup (ctx, + _notmuch_database_generate_thread_id (notmuch)); + db->set_metadata (metadata_key, *thread_id_ret); } else { - thread_id = thread_id_string.c_str(); + *thread_id_ret = talloc_strdup (ctx, thread_id_string.c_str()); } talloc_free (metadata_key); - return talloc_strdup (ctx, thread_id); + return NOTMUCH_STATUS_SUCCESS; } static notmuch_status_t @@ -1381,16 +1461,19 @@ _notmuch_database_link_message_to_parents (notmuch_database_t *notmuch, keys = g_hash_table_get_keys (parents); for (l = keys; l; l = l->next) { char *parent_message_id; - const char *parent_thread_id; + const char *parent_thread_id = NULL; parent_message_id = (char *) l->data; _notmuch_message_add_term (message, "reference", parent_message_id); - parent_thread_id = _resolve_message_id_to_thread_id (notmuch, - message, - parent_message_id); + ret = _resolve_message_id_to_thread_id (notmuch, + message, + parent_message_id, + &parent_thread_id); + if (ret) + goto DONE; if (*thread_id == NULL) { *thread_id = talloc_strdup (message, parent_thread_id); @@ -1543,7 +1626,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 +1644,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", @@ -1654,7 +1743,7 @@ notmuch_database_add_message (notmuch_database_t *notmuch, goto DONE; date = notmuch_message_file_get_header (message_file, "date"); - _notmuch_message_set_date (message, date); + _notmuch_message_set_header_values (message, date, from, subject); _notmuch_message_index_file (message, filename); } else { @@ -1682,6 +1771,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,71 +1784,73 @@ notmuch_status_t notmuch_database_remove_message (notmuch_database_t *notmuch, const char *filename) { - Xapian::WritableDatabase *db; + notmuch_status_t status; + notmuch_message_t *message; + + status = notmuch_database_find_message_by_filename (notmuch, filename, + &message); + + if (status == NOTMUCH_STATUS_SUCCESS && 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); + + notmuch_message_destroy (message); + } + + return status; +} + +notmuch_status_t +notmuch_database_find_message_by_filename (notmuch_database_t *notmuch, + const char *filename, + notmuch_message_t **message_ret) +{ void *local; const char *prefix = _find_prefix ("file-direntry"); char *direntry, *term; Xapian::PostingIterator i, end; - Xapian::Document document; notmuch_status_t status; - status = _notmuch_database_ensure_writable (notmuch); - if (status) - return status; + if (message_ret == NULL) + return NOTMUCH_STATUS_NULL_POINTER; 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; + goto DONE; 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, - *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; - } + *message_ret = _notmuch_message_create (notmuch, notmuch, *i, + &private_status); + if (*message_ret == NULL) + status = NOTMUCH_STATUS_OUT_OF_MEMORY; } } 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; } + DONE: talloc_free (local); + if (status && *message_ret) { + notmuch_message_destroy (*message_ret); + *message_ret = NULL; + } return status; }