]> git.notmuchmail.org Git - notmuch/blobdiff - database.cc
notmuch show: Fix to work with any search string rather than just a thread ID.
[notmuch] / database.cc
index d94755780b88eebdce68303e5980417883807dbe..604062835427f0c539dd16791b24d84aab7d1da0 100644 (file)
@@ -24,7 +24,7 @@
 
 #include <xapian.h>
 
-#include <glib.h> /* g_strdup_printf, g_free, GPtrArray, GHashTable */
+#include <glib.h> /* g_free, GPtrArray, GHashTable */
 
 using namespace std;
 
@@ -51,15 +51,18 @@ typedef struct {
  *     id:     Unique ID of mail, (from Message-ID header or generated
  *             as "notmuch-sha1-<sha1_sum_of_entire_file>.
  *
+ *     thread: The ID of the thread to which the mail belongs
+ *
  *    Multiple terms of given prefix:
  *
- *     ref:    The message IDs from all In-Reply-To and References
- *             headers in the message.
+ *     ref:    All unresolved message IDs from In-Reply-To and
+ *             References headers in the message. (Once a referenced
+ *             message is added to the database and the thread IDs
+ *             are linked the corresponding "ref" term is dropped
+ *             from the message document.)
  *
  *     tag:    Any tags associated with this message by the user.
  *
- *     thread: The thread ID of all threads to which the mail belongs
- *
  *    A mail document also has two values:
  *
  *     TIMESTAMP:      The time_t value corresponding to the message's
@@ -81,7 +84,7 @@ typedef struct {
  *
  * and has a single value:
  *
- *     TIMETAMPS:      The time_t value from the user.
+ *     TIMESTAMP:      The time_t value from the user.
  */
 
 /* With these prefix values we follow the conventions published here:
@@ -101,16 +104,39 @@ typedef struct {
 
 prefix_t BOOLEAN_PREFIX_INTERNAL[] = {
     { "type", "T" },
-    { "thread", "G" },
     { "ref", "XREFERENCE" },
     { "timestamp", "XTIMESTAMP" },
+    { "contact", "XCONTACT" }
 };
 
 prefix_t BOOLEAN_PREFIX_EXTERNAL[] = {
+    { "thread", "G" },
     { "tag", "K" },
     { "id", "Q" }
 };
 
+prefix_t PROBABILISTIC_PREFIX[]= {
+    { "from", "XFROM" },
+    { "to", "XTO" },
+    { "attachment", "XATTACHMENT" },
+    { "subject", "XSUBJECT"}
+};
+
+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)
 {
@@ -124,6 +150,10 @@ _find_prefix (const char *name)
        if (strcmp (name, BOOLEAN_PREFIX_EXTERNAL[i].name) == 0)
            return BOOLEAN_PREFIX_EXTERNAL[i].prefix;
 
+    for (i = 0; i < ARRAY_SIZE (PROBABILISTIC_PREFIX); i++)
+       if (strcmp (name, PROBABILISTIC_PREFIX[i].name) == 0)
+           return PROBABILISTIC_PREFIX[i].prefix;
+
     INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
 
     return "";
@@ -135,6 +165,8 @@ notmuch_status_to_string (notmuch_status_t status)
     switch (status) {
     case NOTMUCH_STATUS_SUCCESS:
        return "No error occurred";
+    case NOTMUCH_STATUS_OUT_OF_MEMORY:
+       return "Out of memory";
     case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
        return "A Xapian exception occurred";
     case NOTMUCH_STATUS_FILE_ERROR:
@@ -147,35 +179,14 @@ notmuch_status_to_string (notmuch_status_t status)
        return "Erroneous NULL pointer";
     case NOTMUCH_STATUS_TAG_TOO_LONG:
        return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)";
+    case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
+       return "Unblanced number of calls to notmuch_message_freeze/thaw";
     default:
     case NOTMUCH_STATUS_LAST_STATUS:
        return "Unknown error status value";
     }
 }
 
-/* XXX: We should drop this function and convert all callers to call
- * _notmuch_message_add_term instead. */
-static void
-add_term (Xapian::Document doc,
-         const char *prefix_name,
-         const char *value)
-{
-    const char *prefix;
-    char *term;
-
-    if (value == NULL)
-       return;
-
-    prefix = _find_prefix (prefix_name);
-
-    term = g_strdup_printf ("%s%s", prefix, value);
-
-    if (strlen (term) <= NOTMUCH_TERM_MAX)
-       doc.add_term (term);
-
-    g_free (term);
-}
-
 static void
 find_doc_ids (notmuch_database_t *notmuch,
              const char *prefix_name,
@@ -186,13 +197,14 @@ find_doc_ids (notmuch_database_t *notmuch,
     Xapian::PostingIterator i;
     char *term;
 
-    term = g_strdup_printf ("%s%s", _find_prefix (prefix_name), value);
+    term = talloc_asprintf (notmuch, "%s%s",
+                           _find_prefix (prefix_name), value);
 
     *begin = notmuch->xapian_db->postlist_begin (term);
 
     *end = notmuch->xapian_db->postlist_end (term);
 
-    free (term);
+    talloc_free (term);
 }
 
 static notmuch_private_status_t
@@ -240,37 +252,6 @@ find_unique_document (notmuch_database_t *notmuch,
     return NOTMUCH_PRIVATE_STATUS_SUCCESS;
 }
 
-/* XXX: Should rewrite this to accept a notmuch_message_t* instead of
- * a Xapian:Document and then we could just use
- * notmuch_message_get_thread_ids instead of duplicating its logic
- * here. */
-static void
-insert_thread_id (GHashTable *thread_ids, Xapian::Document doc)
-{
-    string value_string;
-    Xapian::TermIterator i;
-    const char *prefix_str = _find_prefix ("thread");
-    char prefix;
-
-    assert (strlen (prefix_str) == 1);
-
-    prefix = *prefix_str;
-
-    i = doc.termlist_begin ();
-    i.skip_to (prefix_str);
-
-    while (1) {
-       if (i == doc.termlist_end ())
-           break;
-       value_string = *i;
-       if (value_string.empty () || value_string[0] != prefix)
-           break;
-       g_hash_table_insert (thread_ids,
-                            strdup (value_string.c_str () + 1), NULL);
-       i++;
-    }
-}
-
 notmuch_message_t *
 notmuch_database_find_message (notmuch_database_t *notmuch,
                               const char *message_id)
@@ -286,75 +267,6 @@ notmuch_database_find_message (notmuch_database_t *notmuch,
     return _notmuch_message_create (notmuch, notmuch, doc_id, NULL);
 }
 
-/* Return one or more thread_ids, (as a GPtrArray of strings), for the
- * given message based on looking into the database for any messages
- * referenced in parents, and also for any messages in the database
- * referencing message_id.
- *
- * Caller should free all strings in the array and the array itself,
- * (g_ptr_array_free) when done. */
-static GPtrArray *
-find_thread_ids (notmuch_database_t *notmuch,
-                GPtrArray *parents,
-                const char *message_id)
-{
-    Xapian::PostingIterator child, children_end;
-    Xapian::Document doc;
-    GHashTable *thread_ids;
-    GList *keys, *l;
-    unsigned int i;
-    const char *parent_message_id;
-    GPtrArray *result;
-
-    thread_ids = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                       free, NULL);
-
-    find_doc_ids (notmuch, "ref", message_id, &child, &children_end);
-    for ( ; child != children_end; child++) {
-       doc = find_document_for_doc_id (notmuch, *child);
-       insert_thread_id (thread_ids, doc);
-    }
-
-    for (i = 0; i < parents->len; i++) {
-       notmuch_message_t *parent;
-       notmuch_thread_ids_t *ids;
-
-       parent_message_id = (char *) g_ptr_array_index (parents, i);
-       parent = notmuch_database_find_message (notmuch, parent_message_id);
-       if (parent == NULL)
-           continue;
-
-       for (ids = notmuch_message_get_thread_ids (parent);
-            notmuch_thread_ids_has_more (ids);
-            notmuch_thread_ids_advance (ids))
-       {
-           const char *id;
-
-           id = notmuch_thread_ids_get (ids);
-           g_hash_table_insert (thread_ids, strdup (id), NULL);
-       }
-
-       notmuch_message_destroy (parent);
-    }
-
-    result = g_ptr_array_new ();
-
-    keys = g_hash_table_get_keys (thread_ids);
-    for (l = keys; l; l = l->next) {
-       char *id = (char *) l->data;
-       g_ptr_array_add (result, id);
-    }
-    g_list_free (keys);
-
-    /* We're done with the hash table, but we've taken the pointers to
-     * the allocated strings and put them into our result array, so
-     * tell the hash not to free them on its way out. */
-    g_hash_table_steal_all (thread_ids);
-    g_hash_table_unref (thread_ids);
-
-    return result;
-}
-
 /* Advance 'str' past any whitespace or RFC 822 comments. A comment is
  * a (potentially nested) parenthesized sequence with '\' used to
  * escape any character (including parentheses).
@@ -397,12 +309,11 @@ skip_space_and_comments (const char **str)
  * If not NULL, then *next will be made to point to the first character
  * not parsed, (possibly pointing to the final '\0' terminator.
  *
- * Returns a newly allocated string which the caller should free()
- * when done with it.
+ * Returns a newly talloc'ed string belonging to 'ctx'.
  *
  * Returns NULL if there is any error parsing the message-id. */
 static char *
-parse_message_id (const char *message_id, const char **next)
+parse_message_id (void *ctx, const char *message_id, const char **next)
 {
     const char *s, *end;
     char *result;
@@ -443,7 +354,7 @@ parse_message_id (const char *message_id, const char **next)
     if (end <= s)
        return NULL;
 
-    result = strndup (s, end - s + 1);
+    result = talloc_strndup (ctx, s, end - s + 1);
 
     /* Finally, collapse any whitespace that is within the message-id
      * itself. */
@@ -459,10 +370,11 @@ parse_message_id (const char *message_id, const char **next)
     return result;
 }
 
-/* Parse a References header value, putting a copy of each referenced
- * message-id into 'array'. */
+/* Parse a References header value, putting a (talloc'ed under 'ctx')
+ * copy of each referenced message-id into 'hash'. */
 static void
-parse_references (GPtrArray *array,
+parse_references (void *ctx,
+                 GHashTable *hash,
                  const char *refs)
 {
     char *ref;
@@ -471,20 +383,27 @@ parse_references (GPtrArray *array,
        return;
 
     while (*refs) {
-       ref = parse_message_id (refs, &refs);
+       ref = parse_message_id (ctx, refs, &refs);
 
        if (ref)
-           g_ptr_array_add (array, ref);
+           g_hash_table_insert (hash, ref, NULL);
     }
 }
 
 char *
 notmuch_database_default_path (void)
 {
+    char *path;
+
     if (getenv ("NOTMUCH_BASE"))
        return strdup (getenv ("NOTMUCH_BASE"));
 
-    return g_strdup_printf ("%s/mail", getenv ("HOME"));
+    if (asprintf (&path, "%s/mail", getenv ("HOME")) == -1) {
+       fprintf (stderr, "Out of memory.\n");
+       return xstrdup("");
+    }
+
+    return path;
 }
 
 notmuch_database_t *
@@ -512,7 +431,7 @@ notmuch_database_create (const char *path)
        goto DONE;
     }
 
-    notmuch_path = g_strdup_printf ("%s/%s", path, ".notmuch");
+    notmuch_path = talloc_asprintf (NULL, "%s/%s", path, ".notmuch");
 
     err = mkdir (notmuch_path, 0755);
 
@@ -526,7 +445,7 @@ notmuch_database_create (const char *path)
 
   DONE:
     if (notmuch_path)
-       free (notmuch_path);
+       talloc_free (notmuch_path);
     if (local_path)
        free (local_path);
 
@@ -546,7 +465,11 @@ notmuch_database_open (const char *path)
     if (path == NULL)
        path = local_path = notmuch_database_default_path ();
 
-    notmuch_path = g_strdup_printf ("%s/%s", path, ".notmuch");
+    if (asprintf (&notmuch_path, "%s/%s", path, ".notmuch") == -1) {
+       notmuch_path = NULL;
+       fprintf (stderr, "Out of memory\n");
+       goto DONE;
+    }
 
     err = stat (notmuch_path, &st);
     if (err) {
@@ -555,26 +478,44 @@ notmuch_database_open (const char *path)
        goto DONE;
     }
 
-    xapian_path = g_strdup_printf ("%s/%s", notmuch_path, "xapian");
+    if (asprintf (&xapian_path, "%s/%s", notmuch_path, "xapian") == -1) {
+       xapian_path = NULL;
+       fprintf (stderr, "Out of memory\n");
+       goto DONE;
+    }
 
     notmuch = talloc (NULL, notmuch_database_t);
     notmuch->path = talloc_strdup (notmuch, path);
 
+    if (notmuch->path[strlen (notmuch->path) - 1] == '/')
+       notmuch->path[strlen (notmuch->path) - 1] = '\0';
+
     try {
        notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
                                                           Xapian::DB_CREATE_OR_OPEN);
        notmuch->query_parser = new Xapian::QueryParser;
+       notmuch->term_gen = new Xapian::TermGenerator;
+       notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
+
        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);
 
        for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_EXTERNAL); i++) {
            prefix_t *prefix = &BOOLEAN_PREFIX_EXTERNAL[i];
            notmuch->query_parser->add_boolean_prefix (prefix->name,
                                                       prefix->prefix);
        }
+
+       for (i = 0; i < ARRAY_SIZE (PROBABILISTIC_PREFIX); i++) {
+           prefix_t *prefix = &PROBABILISTIC_PREFIX[i];
+           notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
+       }
     } catch (const Xapian::Error &error) {
        fprintf (stderr, "A Xapian exception occurred: %s\n",
                 error.get_msg().c_str());
+       notmuch = NULL;
     }
     
   DONE:
@@ -591,6 +532,9 @@ notmuch_database_open (const char *path)
 void
 notmuch_database_close (notmuch_database_t *notmuch)
 {
+    notmuch->xapian_db->flush ();
+
+    delete notmuch->term_gen;
     delete notmuch->query_parser;
     delete notmuch->xapian_db;
     talloc_free (notmuch);
@@ -602,7 +546,7 @@ notmuch_database_get_path (notmuch_database_t *notmuch)
     return notmuch->path;
 }
 
-notmuch_private_status_t
+static notmuch_private_status_t
 find_timestamp_document (notmuch_database_t *notmuch, const char *db_key,
                         Xapian::Document *doc, unsigned int *doc_id)
 {
@@ -699,6 +643,177 @@ notmuch_database_get_timestamp (notmuch_database_t *notmuch, const char *key)
     return ret;
 }
 
+/* Find the thread ID to which the message with 'message_id' belongs.
+ *
+ * Returns NULL if no message with message ID 'message_id' is in the
+ * database.
+ *
+ * Otherwise, returns a newly talloced string belonging to 'ctx'.
+ */
+static const char *
+_resolve_message_id_to_thread_id (notmuch_database_t *notmuch,
+                                 void *ctx,
+                                 const char *message_id)
+{
+    notmuch_message_t *message;
+    const char *ret = NULL;
+
+    message = notmuch_database_find_message (notmuch, message_id);
+    if (message == NULL)
+       goto DONE;
+
+    ret = talloc_steal (ctx, notmuch_message_get_thread_id (message));
+
+  DONE:
+    if (message)
+       notmuch_message_destroy (message);
+
+    return ret;
+}
+
+static notmuch_status_t
+_merge_threads (notmuch_database_t *notmuch,
+               const char *winner_thread_id,
+               const char *loser_thread_id)
+{
+    Xapian::PostingIterator loser, loser_end;
+    notmuch_message_t *message = NULL;
+    notmuch_private_status_t private_status;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+    find_doc_ids (notmuch, "thread", loser_thread_id, &loser, &loser_end);
+
+    for ( ; loser != loser_end; loser++) {
+       message = _notmuch_message_create (notmuch, notmuch,
+                                          *loser, &private_status);
+       if (message == NULL) {
+           ret = COERCE_STATUS (private_status,
+                                "Cannot find document for doc_id from query");
+           goto DONE;
+       }
+
+       _notmuch_message_remove_term (message, "thread", loser_thread_id);
+       _notmuch_message_add_term (message, "thread", winner_thread_id);
+       _notmuch_message_sync (message);
+
+       notmuch_message_destroy (message);
+       message = NULL;
+    }
+
+  DONE:
+    if (message)
+       notmuch_message_destroy (message);
+
+    return ret;
+}
+
+static void
+_my_talloc_free_for_g_hash (void *ptr)
+{
+    talloc_free (ptr);
+}
+
+static notmuch_status_t
+_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
+                                          notmuch_message_t *message,
+                                          notmuch_message_file_t *message_file,
+                                          const char **thread_id)
+{
+    GHashTable *parents = NULL;
+    const char *refs, *in_reply_to;
+    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);
+
+    refs = notmuch_message_file_get_header (message_file, "references");
+    parse_references (message, parents, refs);
+
+    in_reply_to = notmuch_message_file_get_header (message_file, "in-reply-to");
+    parse_references (message, parents, in_reply_to);
+
+    keys = g_hash_table_get_keys (parents);
+    for (l = keys; l; l = l->next) {
+       char *parent_message_id;
+       const char *parent_thread_id;
+
+       parent_message_id = (char *) l->data;
+       parent_thread_id = _resolve_message_id_to_thread_id (notmuch,
+                                                            message,
+                                                            parent_message_id);
+
+       if (parent_thread_id == NULL) {
+           _notmuch_message_add_term (message, "ref", parent_message_id);
+       } else {
+           if (*thread_id == NULL) {
+               *thread_id = talloc_strdup (message, parent_thread_id);
+               _notmuch_message_add_term (message, "thread", *thread_id);
+           } else if (strcmp (*thread_id, parent_thread_id)) {
+               ret = _merge_threads (notmuch, *thread_id, parent_thread_id);
+               if (ret)
+                   goto DONE;
+           }
+       }
+    }
+
+  DONE:
+    if (keys)
+       g_list_free (keys);
+    if (parents)
+       g_hash_table_unref (parents);
+
+    return ret;
+}
+
+static notmuch_status_t
+_notmuch_database_link_message_to_children (notmuch_database_t *notmuch,
+                                           notmuch_message_t *message,
+                                           const char **thread_id)
+{
+    const char *message_id = notmuch_message_get_message_id (message);
+    Xapian::PostingIterator child, children_end;
+    notmuch_message_t *child_message = NULL;
+    const char *child_thread_id;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    notmuch_private_status_t private_status;
+
+    find_doc_ids (notmuch, "ref", message_id, &child, &children_end);
+
+    for ( ; child != children_end; child++) {
+
+       child_message = _notmuch_message_create (message, notmuch,
+                                                *child, &private_status);
+       if (child_message == NULL) {
+           ret = COERCE_STATUS (private_status,
+                                "Cannot find document for doc_id from query");
+           goto DONE;
+       }
+
+       child_thread_id = notmuch_message_get_thread_id (child_message);
+       if (*thread_id == NULL) {
+           *thread_id = talloc_strdup (message, child_thread_id);
+           _notmuch_message_add_term (message, "thread", *thread_id);
+       } else if (strcmp (*thread_id, child_thread_id)) {
+           _notmuch_message_remove_term (child_message, "ref",
+                                         message_id);
+           _notmuch_message_sync (child_message);
+           ret = _merge_threads (notmuch, *thread_id, child_thread_id);
+           if (ret)
+               goto DONE;
+       }
+
+       notmuch_message_destroy (child_message);
+       child_message = NULL;
+    }
+
+  DONE:
+    if (child_message)
+       notmuch_message_destroy (child_message);
+
+    return ret;
+}
+
 /* Given a (mostly empty) 'message' and its corresponding
  * 'message_file' link it to existing threads in the database.
  *
@@ -716,65 +831,43 @@ _notmuch_database_link_message (notmuch_database_t *notmuch,
                                notmuch_message_t *message,
                                notmuch_message_file_t *message_file)
 {
-    GPtrArray *parents, *thread_ids;
-    const char *refs, *in_reply_to;
-    const char *message_id = notmuch_message_get_message_id (message);
-    unsigned int i;
-
-    parents = g_ptr_array_new ();
-
-    refs = notmuch_message_file_get_header (message_file, "references");
-    parse_references (parents, refs);
-
-    in_reply_to = notmuch_message_file_get_header (message_file, "in-reply-to");
-    parse_references (parents, in_reply_to);
+    notmuch_status_t status;
+    const char *thread_id = NULL;
 
-    for (i = 0; i < parents->len; i++)
-       _notmuch_message_add_term (message, "ref",
-                                  (char *) g_ptr_array_index (parents, i));
-
-    thread_ids = find_thread_ids (notmuch, parents, message_id);
-
-    for (i = 0; i < parents->len; i++)
-       g_free (g_ptr_array_index (parents, i));
-    g_ptr_array_free (parents, TRUE);
-
-    if (thread_ids->len) {
-       GString *thread_id;
-       char *id;
+    status = _notmuch_database_link_message_to_parents (notmuch, message,
+                                                       message_file,
+                                                       &thread_id);
+    if (status)
+       return status;
 
-       for (i = 0; i < thread_ids->len; i++) {
-           id = (char *) thread_ids->pdata[i];
-           _notmuch_message_add_thread_id (message, id);
-           if (i == 0)
-               thread_id = g_string_new (id);
-           else
-               g_string_append_printf (thread_id, ",%s", id);
+    status = _notmuch_database_link_message_to_children (notmuch, message,
+                                                        &thread_id);
+    if (status)
+       return status;
 
-           free (id);
-       }
-       g_string_free (thread_id, TRUE);
-    } else {
+    if (thread_id == NULL)
        _notmuch_message_ensure_thread_id (message);
-    }
-
-    g_ptr_array_free (thread_ids, TRUE);
 
     return NOTMUCH_STATUS_SUCCESS;
 }
 
 notmuch_status_t
 notmuch_database_add_message (notmuch_database_t *notmuch,
-                             const char *filename)
+                             const char *filename,
+                             notmuch_message_t **message_ret)
 {
     notmuch_message_file_t *message_file;
     notmuch_message_t *message;
     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    notmuch_private_status_t private_status;
 
     const char *date, *header;
-    const char *from, *to, *subject, *old_filename;
+    const char *from, *to, *subject;
     char *message_id;
 
+    if (message_ret)
+       *message_ret = NULL;
+
     message_file = notmuch_message_file_open (filename);
     if (message_file == NULL) {
        ret = NOTMUCH_STATUS_FILE_ERROR;
@@ -792,15 +885,33 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
                                           (char *) NULL);
 
     try {
-       /* The first order of business is to find/create a message ID. */
+       /* Before we do any real work, (especially before doing a
+        * potential SHA-1 computation on the entire file's contents),
+        * let's make sure that what we're looking at looks like an
+        * actual email message.
+        */
+       from = notmuch_message_file_get_header (message_file, "from");
+       subject = notmuch_message_file_get_header (message_file, "subject");
+       to = notmuch_message_file_get_header (message_file, "to");
+
+       if (from == NULL &&
+           subject == NULL &&
+           to == NULL)
+       {
+           ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;
+           goto DONE;
+       }
+
+       /* Now that we're sure it's mail, the first order of business
+        * is to find a message ID (or else create one ourselves). */
 
        header = notmuch_message_file_get_header (message_file, "message-id");
        if (header) {
-           message_id = parse_message_id (header, NULL);
+           message_id = parse_message_id (message_file, header, NULL);
            /* So the header value isn't RFC-compliant, but it's
             * better than no message-id at all. */
            if (message_id == NULL)
-               message_id = xstrdup (header);
+               message_id = talloc_strdup (message_file, header);
        } else {
            /* No message-id at all, let's generate one by taking a
             * hash over the file's contents. */
@@ -812,7 +923,8 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
                goto DONE;
            }
 
-           message_id = g_strdup_printf ("notmuch-sha1-%s", sha1);
+           message_id = talloc_asprintf (message_file,
+                                         "notmuch-sha1-%s", sha1);
            free (sha1);
        }
 
@@ -824,20 +936,20 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
        message = _notmuch_message_create_for_message_id (NULL,
                                                          notmuch,
                                                          message_id,
-                                                         &ret);
-       free (message_id);
+                                                         &private_status);
+
+       talloc_free (message_id);
 
        if (message == NULL)
            goto DONE;
 
-       /* Has a message previously been added with the same ID? */
-       old_filename = notmuch_message_get_filename (message);
-       if (old_filename && strlen (old_filename)) {
-           ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
-           goto DONE;
-       } else {
+       /* Is this a newly created message object? */
+       if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
            _notmuch_message_set_filename (message, filename);
            _notmuch_message_add_term (message, "type", "mail");
+       } else {
+           ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
+           goto DONE;
        }
 
        ret = _notmuch_database_link_message (notmuch, message, message_file);
@@ -847,19 +959,9 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
        date = notmuch_message_file_get_header (message_file, "date");
        _notmuch_message_set_date (message, date);
 
-       from = notmuch_message_file_get_header (message_file, "from");
-       subject = notmuch_message_file_get_header (message_file, "subject");
-       to = notmuch_message_file_get_header (message_file, "to");
+       _notmuch_message_index_file (message, filename);
 
-       if (from == NULL &&
-           subject == NULL &&
-           to == NULL)
-       {
-           ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;
-           goto DONE;
-       } else {
-           _notmuch_message_sync (message);
-       }
+       _notmuch_message_sync (message);
     } catch (const Xapian::Error &error) {
        fprintf (stderr, "A Xapian exception occurred: %s.\n",
                 error.get_msg().c_str());
@@ -868,8 +970,13 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
     }
 
   DONE:
-    if (message)
-       notmuch_message_destroy (message);
+    if (message) {
+       if (ret == NOTMUCH_STATUS_SUCCESS && message_ret)
+           *message_ret = message;
+       else
+           notmuch_message_destroy (message);
+    }
+
     if (message_file)
        notmuch_message_file_close (message_file);