+const char *
+notmuch_database_get_path (notmuch_database_t *notmuch)
+{
+ return notmuch->path;
+}
+
+unsigned int
+notmuch_database_get_version (notmuch_database_t *notmuch)
+{
+ unsigned int version;
+ string version_string;
+ const char *str;
+ char *end;
+
+ version_string = notmuch->xapian_db->get_metadata ("version");
+ if (version_string.empty ())
+ return 0;
+
+ str = version_string.c_str ();
+ if (str == NULL || *str == '\0')
+ return 0;
+
+ version = strtoul (str, &end, 10);
+ if (*end != '\0')
+ INTERNAL_ERROR ("Malformed database version: %s", str);
+
+ return version;
+}
+
+notmuch_bool_t
+notmuch_database_needs_upgrade (notmuch_database_t *notmuch)
+{
+ return notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&
+ ((NOTMUCH_FEATURES_CURRENT & ~notmuch->features) ||
+ (notmuch_database_get_version (notmuch) < NOTMUCH_DATABASE_VERSION));
+}
+
+static volatile sig_atomic_t do_progress_notify = 0;
+
+static void
+handle_sigalrm (unused (int signal))
+{
+ do_progress_notify = 1;
+}
+
+/* Upgrade the current database.
+ *
+ * After opening a database in read-write mode, the client should
+ * check if an upgrade is needed (notmuch_database_needs_upgrade) and
+ * if so, upgrade with this function before making any modifications.
+ *
+ * The optional progress_notify callback can be used by the caller to
+ * provide progress indication to the user. If non-NULL it will be
+ * called periodically with 'count' as the number of messages upgraded
+ * so far and 'total' the overall number of messages that will be
+ * converted.
+ */
+notmuch_status_t
+notmuch_database_upgrade (notmuch_database_t *notmuch,
+ void (*progress_notify) (void *closure,
+ double progress),
+ void *closure)
+{
+ void *local = talloc_new (NULL);
+ Xapian::TermIterator t, t_end;
+ Xapian::WritableDatabase *db;
+ struct sigaction action;
+ struct itimerval timerval;
+ notmuch_bool_t timer_is_active = FALSE;
+ enum _notmuch_features target_features, new_features;
+ notmuch_status_t status;
+ notmuch_private_status_t private_status;
+ notmuch_query_t *query = NULL;
+ unsigned int count = 0, total = 0;
+
+ status = _notmuch_database_ensure_writable (notmuch);
+ if (status)
+ return status;
+
+ db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+
+ target_features = notmuch->features | NOTMUCH_FEATURES_CURRENT;
+ new_features = NOTMUCH_FEATURES_CURRENT & ~notmuch->features;
+
+ if (! notmuch_database_needs_upgrade (notmuch))
+ return NOTMUCH_STATUS_SUCCESS;
+
+ if (progress_notify) {
+ /* Set up our handler for SIGALRM */
+ memset (&action, 0, sizeof (struct sigaction));
+ action.sa_handler = handle_sigalrm;
+ sigemptyset (&action.sa_mask);
+ action.sa_flags = SA_RESTART;
+ sigaction (SIGALRM, &action, NULL);
+
+ /* Then start a timer to send SIGALRM once per second. */
+ timerval.it_interval.tv_sec = 1;
+ timerval.it_interval.tv_usec = 0;
+ timerval.it_value.tv_sec = 1;
+ timerval.it_value.tv_usec = 0;
+ setitimer (ITIMER_REAL, &timerval, NULL);
+
+ timer_is_active = TRUE;
+ }
+
+ /* Figure out how much total work we need to do. */
+ if (new_features &
+ (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |
+ NOTMUCH_FEATURE_LAST_MOD)) {
+ query = notmuch_query_create (notmuch, "");
+ unsigned msg_count;
+
+ status = notmuch_query_count_messages_st (query, &msg_count);
+ if (status)
+ goto DONE;
+
+ 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)