]> git.notmuchmail.org Git - notmuch/blobdiff - lib/database.cc
lib: Correct out-of-date doc comment
[notmuch] / lib / database.cc
index 8103bd96ef88c0e30f256753145f17f5583118a2..5cc076587a88785a5801eafcc56df2abef997d20 100644 (file)
@@ -19,6 +19,7 @@
  */
 
 #include "database-private.h"
+#include "parse-time-vrp.h"
 
 #include <iostream>
 
@@ -500,8 +501,10 @@ _parse_message_id (void *ctx, const char *message_id, const char **next)
  * 'message_id' in the result (to avoid mass confusion when a single
  * message references itself cyclically---and yes, mail messages are
  * not infrequent in the wild that do this---don't ask me why).
-*/
-static void
+ *
+ * Return the last reference parsed, if it is not equal to message_id.
+ */
+static char *
 parse_references (void *ctx,
                  const char *message_id,
                  GHashTable *hash,
@@ -510,7 +513,7 @@ parse_references (void *ctx,
     char *ref;
 
     if (refs == NULL || *refs == '\0')
-       return;
+       return NULL;
 
     while (*refs) {
        ref = _parse_message_id (ctx, refs, &refs);
@@ -518,11 +521,23 @@ parse_references (void *ctx,
        if (ref && strcmp (ref, message_id))
            g_hash_table_insert (hash, ref, NULL);
     }
+
+    /* The return value of this function is used to add a parent
+     * reference to the database.  We should avoid making a message
+     * its own parent, thus the following check.
+     */
+
+    if (ref && strcmp(ref, message_id)) {
+       return ref;
+    } else {
+       return NULL;
+    }
 }
 
-notmuch_database_t *
-notmuch_database_create (const char *path)
+notmuch_status_t
+notmuch_database_create (const char *path, notmuch_database_t **database)
 {
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
     notmuch_database_t *notmuch = NULL;
     char *notmuch_path = NULL;
     struct stat st;
@@ -530,6 +545,7 @@ notmuch_database_create (const char *path)
 
     if (path == NULL) {
        fprintf (stderr, "Error: Cannot create a database for a NULL path.\n");
+       status = NOTMUCH_STATUS_NULL_POINTER;
        goto DONE;
     }
 
@@ -537,12 +553,14 @@ notmuch_database_create (const char *path)
     if (err) {
        fprintf (stderr, "Error: Cannot create database at %s: %s.\n",
                 path, strerror (errno));
+       status = NOTMUCH_STATUS_FILE_ERROR;
        goto DONE;
     }
 
     if (! S_ISDIR (st.st_mode)) {
        fprintf (stderr, "Error: Cannot create database at %s: Not a directory.\n",
                 path);
+       status = NOTMUCH_STATUS_FILE_ERROR;
        goto DONE;
     }
 
@@ -553,18 +571,30 @@ notmuch_database_create (const char *path)
     if (err) {
        fprintf (stderr, "Error: Cannot create directory %s: %s.\n",
                 notmuch_path, strerror (errno));
+       status = NOTMUCH_STATUS_FILE_ERROR;
        goto DONE;
     }
 
-    notmuch = notmuch_database_open (path,
-                                    NOTMUCH_DATABASE_MODE_READ_WRITE);
-    notmuch_database_upgrade (notmuch, NULL, NULL);
+    status = notmuch_database_open (path,
+                                   NOTMUCH_DATABASE_MODE_READ_WRITE,
+                                   &notmuch);
+    if (status)
+       goto DONE;
+    status = notmuch_database_upgrade (notmuch, NULL, NULL);
+    if (status) {
+       notmuch_database_close(notmuch);
+       notmuch = NULL;
+    }
 
   DONE:
     if (notmuch_path)
        talloc_free (notmuch_path);
 
-    return notmuch;
+    if (database)
+       *database = notmuch;
+    else
+       talloc_free (notmuch);
+    return status;
 }
 
 notmuch_status_t
@@ -578,20 +608,29 @@ _notmuch_database_ensure_writable (notmuch_database_t *notmuch)
     return NOTMUCH_STATUS_SUCCESS;
 }
 
-notmuch_database_t *
+notmuch_status_t
 notmuch_database_open (const char *path,
-                      notmuch_database_mode_t mode)
+                      notmuch_database_mode_t mode,
+                      notmuch_database_t **database)
 {
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+    void *local = talloc_new (NULL);
     notmuch_database_t *notmuch = NULL;
-    char *notmuch_path = NULL, *xapian_path = NULL;
+    char *notmuch_path, *xapian_path;
     struct stat st;
     int err;
     unsigned int i, version;
     static int initialized = 0;
 
-    if (asprintf (&notmuch_path, "%s/%s", path, ".notmuch") == -1) {
-       notmuch_path = NULL;
+    if (path == NULL) {
+       fprintf (stderr, "Error: Cannot open a database for a NULL path.\n");
+       status = NOTMUCH_STATUS_NULL_POINTER;
+       goto DONE;
+    }
+
+    if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
        fprintf (stderr, "Out of memory\n");
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
        goto DONE;
     }
 
@@ -599,17 +638,20 @@ notmuch_database_open (const char *path,
     if (err) {
        fprintf (stderr, "Error opening database at %s: %s\n",
                 notmuch_path, strerror (errno));
+       status = NOTMUCH_STATUS_FILE_ERROR;
        goto DONE;
     }
 
-    if (asprintf (&xapian_path, "%s/%s", notmuch_path, "xapian") == -1) {
-       xapian_path = NULL;
+    if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
        fprintf (stderr, "Out of memory\n");
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
        goto DONE;
     }
 
     /* Initialize the GLib type system and threads */
+#if !GLIB_CHECK_VERSION(2, 35, 1)
     g_type_init ();
+#endif
 
     /* Initialize gmime */
     if (! initialized) {
@@ -617,7 +659,7 @@ notmuch_database_open (const char *path,
        initialized = 1;
     }
 
-    notmuch = talloc (NULL, notmuch_database_t);
+    notmuch = talloc_zero (NULL, notmuch_database_t);
     notmuch->exception_reported = FALSE;
     notmuch->path = talloc_strdup (notmuch, path);
 
@@ -643,8 +685,9 @@ notmuch_database_open (const char *path,
                         "       read-write mode.\n",
                         notmuch_path, version, NOTMUCH_DATABASE_VERSION);
                notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
-               notmuch_database_close (notmuch);
+               notmuch_database_destroy (notmuch);
                notmuch = NULL;
+               status = NOTMUCH_STATUS_FILE_ERROR;
                goto DONE;
            }
 
@@ -683,12 +726,14 @@ notmuch_database_open (const char *path,
        notmuch->term_gen = new Xapian::TermGenerator;
        notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
        notmuch->value_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
+       notmuch->date_range_processor = new ParseTimeValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
 
        notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
        notmuch->query_parser->set_database (*notmuch->xapian_db);
        notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
        notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
        notmuch->query_parser->add_valuerangeprocessor (notmuch->value_range_processor);
+       notmuch->query_parser->add_valuerangeprocessor (notmuch->date_range_processor);
 
        for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_EXTERNAL); i++) {
            prefix_t *prefix = &BOOLEAN_PREFIX_EXTERNAL[i];
@@ -703,23 +748,27 @@ 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_destroy (notmuch);
        notmuch = NULL;
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 
   DONE:
-    if (notmuch_path)
-       free (notmuch_path);
-    if (xapian_path)
-       free (xapian_path);
+    talloc_free (local);
 
-    return notmuch;
+    if (database)
+       *database = notmuch;
+    else
+       talloc_free (notmuch);
+    return status;
 }
 
 void
 notmuch_database_close (notmuch_database_t *notmuch)
 {
     try {
-       if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE)
+       if (notmuch->xapian_db != NULL &&
+           notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE)
            (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))->flush ();
     } catch (const Xapian::Error &error) {
        if (! notmuch->exception_reported) {
@@ -728,10 +777,33 @@ notmuch_database_close (notmuch_database_t *notmuch)
        }
     }
 
+    /* Many Xapian objects (and thus notmuch objects) hold references to
+     * the database, so merely deleting the database may not suffice to
+     * close it.  Thus, we explicitly close it here. */
+    if (notmuch->xapian_db != NULL) {
+       try {
+           notmuch->xapian_db->close();
+       } catch (const Xapian::Error &error) {
+           /* do nothing */
+       }
+    }
+
     delete notmuch->term_gen;
+    notmuch->term_gen = NULL;
     delete notmuch->query_parser;
+    notmuch->query_parser = NULL;
     delete notmuch->xapian_db;
+    notmuch->xapian_db = NULL;
     delete notmuch->value_range_processor;
+    notmuch->value_range_processor = NULL;
+    delete notmuch->date_range_processor;
+    notmuch->date_range_processor = NULL;
+}
+
+void
+notmuch_database_destroy (notmuch_database_t *notmuch)
+{
+    notmuch_database_close (notmuch);
     talloc_free (notmuch);
 }
 
@@ -903,8 +975,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
                mtime = Xapian::sortable_unserialise (
                    document.get_value (NOTMUCH_VALUE_TIMESTAMP));
 
-               directory = notmuch_database_get_directory (notmuch,
-                                                           term.c_str() + 10);
+               directory = _notmuch_directory_create (notmuch, term.c_str() + 10,
+                                                      NOTMUCH_FIND_CREATE, &status);
                notmuch_directory_set_mtime (directory, mtime);
                notmuch_directory_destroy (directory);
            }
@@ -1145,9 +1217,17 @@ _notmuch_database_split_path (void *ctx,
     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;
@@ -1158,8 +1238,8 @@ _notmuch_database_find_directory_id (notmuch_database_t *notmuch,
        return NOTMUCH_STATUS_SUCCESS;
     }
 
-    directory = _notmuch_directory_create (notmuch, path, &status);
-    if (status) {
+    directory = _notmuch_directory_create (notmuch, path, flags, &status);
+    if (status || !directory) {
        *directory_id = -1;
        return status;
     }
@@ -1188,13 +1268,16 @@ _notmuch_database_get_directory_path (void *ctx,
  * database path), return a new string (with 'ctx' as the talloc
  * owner) suitable for use as a direntry term value.
  *
- * The necessary directory documents will be created in the database
- * as needed.
+ * 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;
@@ -1208,10 +1291,12 @@ _notmuch_database_filename_to_direntry (void *ctx,
     if (status)
        return status;
 
-    status = _notmuch_database_find_directory_id (notmuch, directory,
+    status = _notmuch_database_find_directory_id (notmuch, directory, flags,
                                                  &directory_id);
-    if (status)
+    if (status || directory_id == (unsigned int)-1) {
+       *direntry = NULL;
        return status;
+    }
 
     *direntry = talloc_asprintf (ctx, "%u:%s", directory_id, basename);
 
@@ -1252,20 +1337,27 @@ _notmuch_database_relative_path (notmuch_database_t *notmuch,
     return relative;
 }
 
-notmuch_directory_t *
+notmuch_status_t
 notmuch_database_get_directory (notmuch_database_t *notmuch,
-                               const char *path)
+                               const char *path,
+                               notmuch_directory_t **directory)
 {
     notmuch_status_t status;
 
+    if (directory == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+    *directory = NULL;
+
     try {
-       return _notmuch_directory_create (notmuch, path, &status);
+       *directory = _notmuch_directory_create (notmuch, path,
+                                               NOTMUCH_FIND_LOOKUP, &status);
     } catch (const Xapian::Error &error) {
        fprintf (stderr, "A Xapian exception occurred getting directory: %s.\n",
                 error.get_msg().c_str());
        notmuch->exception_reported = TRUE;
-       return NULL;
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
+    return status;
 }
 
 /* Allocate a document ID that satisfies the following criteria:
@@ -1433,28 +1525,33 @@ _notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
 {
     GHashTable *parents = NULL;
     const char *refs, *in_reply_to, *in_reply_to_message_id;
+    const char *last_ref_message_id, *this_message_id;
     GList *l, *keys = NULL;
     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
 
     parents = g_hash_table_new_full (g_str_hash, g_str_equal,
                                     _my_talloc_free_for_g_hash, NULL);
+    this_message_id = notmuch_message_get_message_id (message);
 
     refs = notmuch_message_file_get_header (message_file, "references");
-    parse_references (message, notmuch_message_get_message_id (message),
-                     parents, refs);
+    last_ref_message_id = parse_references (message,
+                                           this_message_id,
+                                           parents, refs);
 
     in_reply_to = notmuch_message_file_get_header (message_file, "in-reply-to");
-    parse_references (message, notmuch_message_get_message_id (message),
-                     parents, in_reply_to);
-
-    /* Carefully avoid adding any self-referential in-reply-to term. */
-    in_reply_to_message_id = _parse_message_id (message, in_reply_to, NULL);
-    if (in_reply_to_message_id &&
-       strcmp (in_reply_to_message_id,
-               notmuch_message_get_message_id (message)))
-    {
+    in_reply_to_message_id = parse_references (message,
+                                              this_message_id,
+                                              parents, in_reply_to);
+
+    /* For the parent of this message, use the last message ID of the
+     * References header, if available.  If not, fall back to the
+     * first message ID in the In-Reply-To header. */
+    if (last_ref_message_id) {
+        _notmuch_message_add_term (message, "replyto",
+                                   last_ref_message_id);
+    } else if (in_reply_to_message_id) {
        _notmuch_message_add_term (message, "replyto",
-                            _parse_message_id (message, in_reply_to, NULL));
+                            in_reply_to_message_id);
     }
 
     keys = g_hash_table_get_keys (parents);
@@ -1744,7 +1841,9 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
            date = notmuch_message_file_get_header (message_file, "date");
            _notmuch_message_set_header_values (message, date, from, subject);
 
-           _notmuch_message_index_file (message, filename);
+           ret = _notmuch_message_index_file (message, filename);
+           if (ret)
+               goto DONE;
        } else {
            ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
        }
@@ -1816,12 +1915,15 @@ notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
     if (message_ret == NULL)
        return NOTMUCH_STATUS_NULL_POINTER;
 
+    /* return NULL on any failure */
+    *message_ret = NULL;
+
     local = talloc_new (notmuch);
 
     try {
-       status = _notmuch_database_filename_to_direntry (local, notmuch,
-                                                        filename, &direntry);
-       if (status)
+       status = _notmuch_database_filename_to_direntry (
+           local, notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry);
+       if (status || !direntry)
            goto DONE;
 
        term = talloc_asprintf (local, "%s%s", prefix, direntry);