+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) {
+ _notmuch_database_log (notmuch, "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->flush ();
+ } catch (const Xapian::Error &error) {
+ _notmuch_database_log (notmuch, "A Xapian exception occurred committing transaction: %s.\n",
+ error.get_msg().c_str());
+ notmuch->exception_reported = TRUE;
+ return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+ }
+
+ if (notmuch->atomic_dirty) {
+ ++notmuch->revision;
+ notmuch->atomic_dirty = FALSE;
+ }
+
+DONE:
+ notmuch->atomic_nesting--;
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+unsigned long
+notmuch_database_get_revision (notmuch_database_t *notmuch,
+ const char **uuid)
+{
+ if (uuid)
+ *uuid = notmuch->uuid;
+ return notmuch->revision;
+}
+
+/* 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.
+ *
+ * Note: This function may return the original value of 'path'. If it
+ * does not, then the caller is responsible to free() the returned
+ * value.
+ */
+const char *
+_notmuch_database_get_directory_db_path (const char *path)
+{
+ int term_len = strlen (_find_prefix ("directory")) + strlen (path);
+
+ if (term_len > NOTMUCH_TERM_MAX)
+ return _notmuch_sha1_of_string (path);
+ else
+ return path;
+}
+
+/* Given a path, split it into two parts: the directory part is all
+ * components except for the last, and the basename is that last
+ * component. Getting the return-value for either part is optional
+ * (the caller can pass NULL).
+ *
+ * The original 'path' can represent either a regular file or a
+ * directory---the splitting will be carried out in the same way in
+ * either case. Trailing slashes on 'path' will be ignored, and any
+ * cases of multiple '/' characters appearing in series will be
+ * treated as a single '/'.
+ *
+ * Allocation (if any) will have 'ctx' as the talloc owner. But
+ * pointers will be returned within the original path string whenever
+ * possible.
+ *
+ * Note: If 'path' is non-empty and contains no non-trailing slash,
+ * (that is, consists of a filename with no parent directory), then
+ * the directory returned will be an empty string. However, if 'path'
+ * is an empty string, then both directory and basename will be
+ * returned as NULL.
+ */
+notmuch_status_t
+_notmuch_database_split_path (void *ctx,
+ const char *path,
+ const char **directory,
+ const char **basename)
+{
+ const char *slash;
+
+ if (path == NULL || *path == '\0') {
+ if (directory)
+ *directory = NULL;
+ if (basename)
+ *basename = NULL;
+ return NOTMUCH_STATUS_SUCCESS;
+ }
+
+ /* Find the last slash (not counting a trailing slash), if any. */
+
+ slash = path + strlen (path) - 1;
+
+ /* First, skip trailing slashes. */
+ while (slash != path) {
+ if (*slash != '/')
+ break;
+
+ --slash;
+ }
+
+ /* Then, find a slash. */
+ while (slash != path) {
+ if (*slash == '/')
+ break;
+
+ if (basename)
+ *basename = slash;
+
+ --slash;
+ }
+
+ /* Finally, skip multiple slashes. */
+ while (slash != path) {
+ if (*slash != '/')
+ break;
+
+ --slash;
+ }
+
+ if (slash == path) {
+ if (directory)
+ *directory = talloc_strdup (ctx, "");
+ if (basename)
+ *basename = path;
+ } else {
+ if (directory)
+ *directory = talloc_strndup (ctx, path, slash - path + 1);
+ }
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Find the document ID of the specified directory.
+ *
+ * If (flags & NOTMUCH_FIND_CREATE), a new directory document will be
+ * created if one does not exist for 'path'. Otherwise, if the
+ * directory document does not exist, this sets *directory_id to
+ * ((unsigned int)-1) and returns NOTMUCH_STATUS_SUCCESS.
+ */
+notmuch_status_t
+_notmuch_database_find_directory_id (notmuch_database_t *notmuch,
+ const char *path,
+ notmuch_find_flags_t flags,
+ unsigned int *directory_id)
+{
+ notmuch_directory_t *directory;
+ notmuch_status_t status;
+
+ if (path == NULL) {
+ *directory_id = 0;
+ return NOTMUCH_STATUS_SUCCESS;
+ }
+
+ directory = _notmuch_directory_create (notmuch, path, flags, &status);
+ if (status || !directory) {
+ *directory_id = -1;
+ return status;
+ }
+
+ *directory_id = _notmuch_directory_get_document_id (directory);
+
+ notmuch_directory_destroy (directory);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+const char *
+_notmuch_database_get_directory_path (void *ctx,
+ notmuch_database_t *notmuch,
+ unsigned int doc_id)
+{
+ Xapian::Document document;
+
+ document = find_document_for_doc_id (notmuch, doc_id);
+
+ return talloc_strdup (ctx, document.get_data ().c_str ());
+}
+
+/* Given a legal 'filename' for the database, (either relative to
+ * database path or absolute with initial components identical to
+ * database path), return a new string (with 'ctx' as the talloc
+ * owner) suitable for use as a direntry term value.
+ *
+ * If (flags & NOTMUCH_FIND_CREATE), the necessary directory documents
+ * will be created in the database as needed. Otherwise, if the
+ * necessary directory documents do not exist, this sets
+ * *direntry to NULL and returns NOTMUCH_STATUS_SUCCESS.
+ */
+notmuch_status_t
+_notmuch_database_filename_to_direntry (void *ctx,
+ notmuch_database_t *notmuch,
+ const char *filename,
+ notmuch_find_flags_t flags,
+ char **direntry)
+{
+ const char *relative, *directory, *basename;
+ Xapian::docid directory_id;
+ notmuch_status_t status;
+
+ relative = _notmuch_database_relative_path (notmuch, filename);
+
+ status = _notmuch_database_split_path (ctx, relative,
+ &directory, &basename);
+ if (status)
+ return status;
+
+ status = _notmuch_database_find_directory_id (notmuch, directory, flags,
+ &directory_id);
+ if (status || directory_id == (unsigned int)-1) {
+ *direntry = NULL;
+ return status;
+ }
+
+ *direntry = talloc_asprintf (ctx, "%u:%s", directory_id, basename);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+