+ total += msg_count;
+ notmuch_query_destroy (query);
+ query = NULL;
+ }
+ if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) {
+ t_end = db->allterms_end ("XTIMESTAMP");
+ for (t = db->allterms_begin ("XTIMESTAMP"); t != t_end; t++)
+ ++total;
+ }
+ if (new_features & NOTMUCH_FEATURE_GHOSTS) {
+ /* The ghost message upgrade converts all thread_id_*
+ * metadata values into ghost message documents. */
+ t_end = db->metadata_keys_end ("thread_id_");
+ for (t = db->metadata_keys_begin ("thread_id_"); t != t_end; ++t)
+ ++total;
+ }
+
+ /* Perform the upgrade in a transaction. */
+ db->begin_transaction (true);
+
+ /* Set the target features so we write out changes in the desired
+ * format. */
+ notmuch->features = target_features;
+
+ /* Perform per-message upgrades. */
+ if (new_features &
+ (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |
+ NOTMUCH_FEATURE_LAST_MOD)) {
+ notmuch_messages_t *messages;
+ notmuch_message_t *message;
+ char *filename;
+
+ query = notmuch_query_create (notmuch, "");
+
+ status = notmuch_query_search_messages_st (query, &messages);
+ if (status)
+ goto DONE;
+ for (;
+ notmuch_messages_valid (messages);
+ notmuch_messages_move_to_next (messages))
+ {
+ if (do_progress_notify) {
+ progress_notify (closure, (double) count / total);
+ do_progress_notify = 0;
+ }
+
+ message = notmuch_messages_get (messages);
+
+ /* Before version 1, each message document had its
+ * filename in the data field. Copy that into the new
+ * format by calling notmuch_message_add_filename.
+ */
+ if (new_features & NOTMUCH_FEATURE_FILE_TERMS) {
+ filename = _notmuch_message_talloc_copy_data (message);
+ if (filename && *filename != '\0') {
+ _notmuch_message_add_filename (message, filename);
+ _notmuch_message_clear_data (message);
+ }
+ talloc_free (filename);
+ }
+
+ /* Prior to version 2, the "folder:" prefix was
+ * probabilistic and stemmed. Change it to the current
+ * boolean prefix. Add "path:" prefixes while at it.
+ */
+ if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER)
+ _notmuch_message_upgrade_folder (message);
+
+ /* Prior to NOTMUCH_FEATURE_LAST_MOD, messages did not
+ * track modification revisions. Give all messages the
+ * next available revision; since we just started tracking
+ * revisions for this database, that will be 1.
+ */
+ if (new_features & NOTMUCH_FEATURE_LAST_MOD)
+ _notmuch_message_upgrade_last_mod (message);
+
+ _notmuch_message_sync (message);
+
+ notmuch_message_destroy (message);
+
+ count++;
+ }
+
+ notmuch_query_destroy (query);
+ query = NULL;
+ }
+
+ /* Perform per-directory upgrades. */
+
+ /* Before version 1 we stored directory timestamps in
+ * XTIMESTAMP documents instead of the current XDIRECTORY
+ * documents. So copy those as well. */
+ if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) {
+ t_end = notmuch->xapian_db->allterms_end ("XTIMESTAMP");
+
+ for (t = notmuch->xapian_db->allterms_begin ("XTIMESTAMP");
+ t != t_end;
+ t++)
+ {
+ Xapian::PostingIterator p, p_end;
+ std::string term = *t;
+
+ p_end = notmuch->xapian_db->postlist_end (term);
+
+ for (p = notmuch->xapian_db->postlist_begin (term);
+ p != p_end;
+ p++)
+ {
+ Xapian::Document document;
+ time_t mtime;
+ notmuch_directory_t *directory;
+
+ if (do_progress_notify) {
+ progress_notify (closure, (double) count / total);
+ do_progress_notify = 0;
+ }
+
+ document = find_document_for_doc_id (notmuch, *p);
+ mtime = Xapian::sortable_unserialise (
+ document.get_value (NOTMUCH_VALUE_TIMESTAMP));
+
+ directory = _notmuch_directory_create (notmuch, term.c_str() + 10,
+ NOTMUCH_FIND_CREATE, &status);
+ notmuch_directory_set_mtime (directory, mtime);
+ notmuch_directory_destroy (directory);
+
+ db->delete_document (*p);
+ }
+
+ ++count;
+ }
+ }
+
+ /* Perform metadata upgrades. */
+
+ /* Prior to NOTMUCH_FEATURE_GHOSTS, thread IDs for missing
+ * messages were stored as database metadata. Change these to
+ * ghost messages.
+ */
+ if (new_features & NOTMUCH_FEATURE_GHOSTS) {
+ notmuch_message_t *message;
+ std::string message_id, thread_id;
+
+ t_end = db->metadata_keys_end (NOTMUCH_METADATA_THREAD_ID_PREFIX);
+ for (t = db->metadata_keys_begin (NOTMUCH_METADATA_THREAD_ID_PREFIX);
+ t != t_end; ++t) {
+ if (do_progress_notify) {
+ progress_notify (closure, (double) count / total);
+ do_progress_notify = 0;
+ }
+
+ message_id = (*t).substr (
+ strlen (NOTMUCH_METADATA_THREAD_ID_PREFIX));
+ thread_id = db->get_metadata (*t);
+
+ /* Create ghost message */
+ message = _notmuch_message_create_for_message_id (
+ notmuch, message_id.c_str (), &private_status);
+ if (private_status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
+ /* Document already exists; ignore the stored thread ID */
+ } else if (private_status ==
+ NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+ private_status = _notmuch_message_initialize_ghost (
+ message, thread_id.c_str ());
+ if (! private_status)
+ _notmuch_message_sync (message);
+ }
+
+ if (private_status) {
+ _notmuch_database_log (notmuch,
+ "Upgrade failed while creating ghost messages.\n");
+ status = COERCE_STATUS (private_status, "Unexpected status from _notmuch_message_initialize_ghost");
+ goto DONE;
+ }
+
+ /* Clear saved metadata thread ID */
+ db->set_metadata (*t, "");
+
+ ++count;
+ }
+ }
+
+ status = NOTMUCH_STATUS_SUCCESS;
+ db->set_metadata ("features", _print_features (local, notmuch->features));
+ db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION));
+
+ DONE:
+ if (status == NOTMUCH_STATUS_SUCCESS)
+ db->commit_transaction ();
+ else
+ db->cancel_transaction ();
+
+ if (timer_is_active) {
+ /* Now stop the timer. */
+ timerval.it_interval.tv_sec = 0;
+ timerval.it_interval.tv_usec = 0;
+ timerval.it_value.tv_sec = 0;
+ timerval.it_value.tv_usec = 0;
+ setitimer (ITIMER_REAL, &timerval, NULL);
+
+ /* And disable the signal handler. */
+ action.sa_handler = SIG_IGN;
+ sigaction (SIGALRM, &action, NULL);
+ }
+
+ if (query)
+ notmuch_query_destroy (query);
+
+ talloc_free (local);
+ return status;
+}
+
+notmuch_status_t
+notmuch_database_begin_atomic (notmuch_database_t *notmuch)
+{
+ if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
+ notmuch->atomic_nesting > 0)