]> git.notmuchmail.org Git - notmuch/blobdiff - lib/message.cc
Switch from random to sequential thread identifiers.
[notmuch] / lib / message.cc
index 41dddd077ea9939d703e95f0606dc1bfc023f053..01950505900f8d161701883b5b7a5e7dc5e67d26 100644 (file)
@@ -37,17 +37,11 @@ struct _notmuch_message {
     char *filename;
     notmuch_message_file_t *message_file;
     notmuch_message_list_t *replies;
+    unsigned long flags;
 
     Xapian::Document doc;
 };
 
-/* "128 bits of thread-id ought to be enough for anybody" */
-#define NOTMUCH_THREAD_ID_BITS  128
-#define NOTMUCH_THREAD_ID_DIGITS (NOTMUCH_THREAD_ID_BITS / 4)
-typedef struct _thread_id {
-    char str[NOTMUCH_THREAD_ID_DIGITS + 1];
-} thread_id_t;
-
 /* We end up having to call the destructor explicitly because we had
  * to use "placement new" in order to initialize C++ objects within a
  * block that we allocated with talloc. So C++ is making talloc
@@ -108,6 +102,7 @@ _notmuch_message_create (const void *talloc_owner,
     message->doc_id = doc_id;
 
     message->frozen = 0;
+    message->flags = 0;
 
     /* Each of these will be lazily created as needed. */
     message->message_id = NULL;
@@ -168,6 +163,7 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
 {
     notmuch_message_t *message;
     Xapian::Document doc;
+    Xapian::WritableDatabase *db;
     unsigned int doc_id;
     char *term;
 
@@ -184,14 +180,21 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
        return NULL;
     }
 
+    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+       INTERNAL_ERROR ("Failure to ensure database is writable.");
+
+    db = static_cast<Xapian::WritableDatabase *> (notmuch->xapian_db);
     try {
-       doc.add_term (term);
+       doc.add_term (term, 0);
        talloc_free (term);
 
        doc.add_value (NOTMUCH_VALUE_MESSAGE_ID, message_id);
 
-       doc_id = notmuch->xapian_db->add_document (doc);
+       doc_id = db->add_document (doc);
     } catch (const Xapian::Error &error) {
+       fprintf (stderr, "A Xapian exception occurred creating message: %s\n",
+                error.get_msg().c_str());
+       notmuch->exception_reported = TRUE;
        *status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
        return NULL;
     }
@@ -306,9 +309,9 @@ _notmuch_message_get_in_reply_to (notmuch_message_t *message)
     in_reply_to = *i;
 
     if (i != message->doc.termlist_end () &&
-       strncmp ((*i).c_str (), prefix, prefix_len))
+       strncmp ((*i).c_str (), prefix, prefix_len) == 0)
     {
-       INTERNAL_ERROR ("Message %s has duplicate In-Reply-To IDs: %s and %s\n"
+       INTERNAL_ERROR ("Message %s has duplicate In-Reply-To IDs: %s and %s\n",
                        notmuch_message_get_message_id (message),
                        message->in_reply_to,
                        (*i).c_str () + prefix_len);
@@ -373,20 +376,17 @@ notmuch_message_get_replies (notmuch_message_t *message)
     return _notmuch_messages_create (message->replies);
 }
 
-/* Set the filename for 'message' to 'filename'.
- *
- * XXX: We should still figure out if we think it's important to store
- * multiple filenames for email messages with identical message IDs.
+/* Add an additional 'filename' for 'message'.
  *
  * This change will not be reflected in the database until the next
  * call to _notmuch_message_set_sync. */
-void
-_notmuch_message_set_filename (notmuch_message_t *message,
+notmuch_status_t
+_notmuch_message_add_filename (notmuch_message_t *message,
                               const char *filename)
 {
-    const char *s;
-    const char *db_path;
-    unsigned int db_path_len;
+    notmuch_status_t status;
+    void *local = talloc_new (message);
+    char *direntry;
 
     if (message->filename) {
        talloc_free (message->filename);
@@ -396,41 +396,119 @@ _notmuch_message_set_filename (notmuch_message_t *message,
     if (filename == NULL)
        INTERNAL_ERROR ("Message filename cannot be NULL.");
 
-    s = filename;
+    status = _notmuch_database_filename_to_direntry (local,
+                                                    message->notmuch,
+                                                    filename, &direntry);
+    if (status)
+       return status;
 
-    db_path = notmuch_database_get_path (message->notmuch);
-    db_path_len = strlen (db_path);
+    _notmuch_message_add_term (message, "file-direntry", direntry);
 
-    if (*s == '/' && strncmp (s, db_path, db_path_len) == 0
-       && strlen (s) > db_path_len)
-    {
-       s += db_path_len + 1;
-    }
+    talloc_free (local);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
 
-    message->doc.set_data (s);
+char *
+_notmuch_message_talloc_copy_data (notmuch_message_t *message)
+{
+    return talloc_strdup (message, message->doc.get_data ().c_str ());
+}
+
+void
+_notmuch_message_clear_data (notmuch_message_t *message)
+{
+    message->doc.set_data ("");
 }
 
 const char *
 notmuch_message_get_filename (notmuch_message_t *message)
 {
-    std::string filename_str;
-    const char *db_path;
+    const char *prefix = _find_prefix ("file-direntry");
+    int prefix_len = strlen (prefix);
+    Xapian::TermIterator i;
+    char *direntry, *colon;
+    const char *db_path, *directory, *basename;
+    unsigned int directory_id;
+    void *local = talloc_new (message);
 
     if (message->filename)
        return message->filename;
 
-    filename_str = message->doc.get_data ();
+    i = message->doc.termlist_begin ();
+    i.skip_to (prefix);
+
+    if (i != message->doc.termlist_end ())
+       direntry = talloc_strdup (local, (*i).c_str ());
+
+    if (i == message->doc.termlist_end () ||
+       strncmp (direntry, prefix, prefix_len))
+    {
+       /* A message document created by an old version of notmuch
+        * (prior to rename support) will have the filename in the
+        * data of the document rather than as a file-direntry term.
+        *
+        * It would be nice to do the upgrade of the document directly
+        * here, but the database is likely open in read-only mode. */
+       const char *data;
+
+       data = message->doc.get_data ().c_str ();
+
+       if (data == NULL)
+           INTERNAL_ERROR ("message with no filename");
+
+       message->filename = talloc_strdup (message, data);
+
+       return message->filename;
+    }
+
+    direntry += prefix_len;
+
+    directory_id = strtol (direntry, &colon, 10);
+
+    if (colon == NULL || *colon != ':')
+       INTERNAL_ERROR ("malformed direntry");
+
+    basename = colon + 1;
+
+    *colon = '\0';
+
     db_path = notmuch_database_get_path (message->notmuch);
 
-    if (filename_str[0] != '/')
-       message->filename = talloc_asprintf (message, "%s/%s", db_path,
-                                            filename_str.c_str ());
+    directory = _notmuch_database_get_directory_path (local,
+                                                     message->notmuch,
+                                                     directory_id);
+
+    if (strlen (directory))
+       message->filename = talloc_asprintf (message, "%s/%s/%s",
+                                            db_path, directory, basename);
     else
-       message->filename = talloc_strdup (message, filename_str.c_str ());
+       message->filename = talloc_asprintf (message, "%s/%s",
+                                            db_path, basename);
+    talloc_free ((void *) directory);
+
+    talloc_free (local);
 
     return message->filename;
 }
 
+notmuch_bool_t
+notmuch_message_get_flag (notmuch_message_t *message,
+                         notmuch_message_flag_t flag)
+{
+    return message->flags & (1 << flag);
+}
+
+void
+notmuch_message_set_flag (notmuch_message_t *message,
+                         notmuch_message_flag_t flag, notmuch_bool_t enable)
+{
+    if (enable)
+       message->flags |= (1 << flag);
+    else
+       message->flags &= ~(1 << flag);
+}
+
 time_t
 notmuch_message_get_date (notmuch_message_t *message)
 {
@@ -449,38 +527,10 @@ notmuch_message_get_date (notmuch_message_t *message)
 notmuch_tags_t *
 notmuch_message_get_tags (notmuch_message_t *message)
 {
-    const char *prefix = _find_prefix ("tag");
     Xapian::TermIterator i, end;
-    notmuch_tags_t *tags;
-    std::string tag;
-
-    /* Currently this iteration is written with the assumption that
-     * "tag" has a single-character prefix. */
-    assert (strlen (prefix) == 1);
-
-    tags = _notmuch_tags_create (message);
-    if (unlikely (tags == NULL))
-       return NULL;
-
-    i = message->doc.termlist_begin ();
-    end = message->doc.termlist_end ();
-
-    i.skip_to (prefix);
-
-    while (i != end) {
-       tag = *i;
-
-       if (tag.empty () || tag[0] != *prefix)
-           break;
-
-       _notmuch_tags_add_tag (tags, tag.c_str () + 1);
-
-       i++;
-    }
-
-    _notmuch_tags_prepare_iterator (tags);
-
-    return tags;
+    i = message->doc.termlist_begin();
+    end = message->doc.termlist_end();
+    return _notmuch_convert_tags(message, i, end);
 }
 
 void
@@ -491,7 +541,7 @@ _notmuch_message_set_date (notmuch_message_t *message,
 
     /* GMime really doesn't want to see a NULL date, so protect its
      * sensibilities. */
-    if (date == NULL)
+    if (date == NULL || *date == '\0')
        time_value = 0;
     else
        time_value = g_mime_utils_header_decode_date (date, NULL);
@@ -500,51 +550,16 @@ _notmuch_message_set_date (notmuch_message_t *message,
                            Xapian::sortable_serialise (time_value));
 }
 
-static void
-thread_id_generate (thread_id_t *thread_id)
-{
-    static int seeded = 0;
-    FILE *dev_random;
-    uint32_t value;
-    char *s;
-    int i;
-
-    if (! seeded) {
-       dev_random = fopen ("/dev/random", "r");
-       if (dev_random == NULL) {
-           srand (time (NULL));
-       } else {
-           fread ((void *) &value, sizeof (value), 1, dev_random);
-           srand (value);
-           fclose (dev_random);
-       }
-       seeded = 1;
-    }
-
-    s = thread_id->str;
-    for (i = 0; i < NOTMUCH_THREAD_ID_DIGITS; i += 8) {
-       value = rand ();
-       sprintf (s, "%08x", value);
-       s += 8;
-    }
-}
-
-void
-_notmuch_message_ensure_thread_id (notmuch_message_t *message)
-{
-    /* If not part of any existing thread, generate a new thread_id. */
-    thread_id_t thread_id;
-
-    thread_id_generate (&thread_id);
-    _notmuch_message_add_term (message, "thread", thread_id.str);
-}
-
 /* Synchronize changes made to message->doc out into the database. */
 void
 _notmuch_message_sync (notmuch_message_t *message)
 {
-    Xapian::WritableDatabase *db = message->notmuch->xapian_db;
+    Xapian::WritableDatabase *db;
+
+    if (message->notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+       return;
 
+    db = static_cast <Xapian::WritableDatabase *> (message->notmuch->xapian_db);
     db->replace_document (message->doc_id, message->doc);
 }
 
@@ -585,7 +600,7 @@ _notmuch_message_add_term (notmuch_message_t *message,
     if (strlen (term) > NOTMUCH_TERM_MAX)
        return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
 
-    message->doc.add_term (term);
+    message->doc.add_term (term, 0);
 
     talloc_free (term);
 
@@ -658,7 +673,12 @@ _notmuch_message_remove_term (notmuch_message_t *message,
 notmuch_status_t
 notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
 {
-    notmuch_private_status_t status;
+    notmuch_private_status_t private_status;
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+       return status;
 
     if (tag == NULL)
        return NOTMUCH_STATUS_NULL_POINTER;
@@ -666,10 +686,10 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
     if (strlen (tag) > NOTMUCH_TAG_MAX)
        return NOTMUCH_STATUS_TAG_TOO_LONG;
 
-    status = _notmuch_message_add_term (message, "tag", tag);
-    if (status) {
+    private_status = _notmuch_message_add_term (message, "tag", tag);
+    if (private_status) {
        INTERNAL_ERROR ("_notmuch_message_add_term return unexpected value: %d\n",
-                       status);
+                       private_status);
     }
 
     if (! message->frozen)
@@ -681,7 +701,12 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
 notmuch_status_t
 notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)
 {
-    notmuch_private_status_t status;
+    notmuch_private_status_t private_status;
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+       return status;
 
     if (tag == NULL)
        return NOTMUCH_STATUS_NULL_POINTER;
@@ -689,10 +714,10 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)
     if (strlen (tag) > NOTMUCH_TAG_MAX)
        return NOTMUCH_STATUS_TAG_TOO_LONG;
 
-    status = _notmuch_message_remove_term (message, "tag", tag);
-    if (status) {
+    private_status = _notmuch_message_remove_term (message, "tag", tag);
+    if (private_status) {
        INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
-                       status);
+                       private_status);
     }
 
     if (! message->frozen)
@@ -701,39 +726,60 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)
     return NOTMUCH_STATUS_SUCCESS;
 }
 
-void
+notmuch_status_t
 notmuch_message_remove_all_tags (notmuch_message_t *message)
 {
-    notmuch_private_status_t status;
+    notmuch_private_status_t private_status;
+    notmuch_status_t status;
     notmuch_tags_t *tags;
     const char *tag;
 
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+       return status;
+
     for (tags = notmuch_message_get_tags (message);
         notmuch_tags_has_more (tags);
         notmuch_tags_advance (tags))
     {
        tag = notmuch_tags_get (tags);
 
-       status = _notmuch_message_remove_term (message, "tag", tag);
-       if (status) {
+       private_status = _notmuch_message_remove_term (message, "tag", tag);
+       if (private_status) {
            INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
-                           status);
+                           private_status);
        }
     }
 
     if (! message->frozen)
        _notmuch_message_sync (message);
+
+    return NOTMUCH_STATUS_SUCCESS;
 }
 
-void
+notmuch_status_t
 notmuch_message_freeze (notmuch_message_t *message)
 {
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+       return status;
+
     message->frozen++;
+
+    return NOTMUCH_STATUS_SUCCESS;
 }
 
 notmuch_status_t
 notmuch_message_thaw (notmuch_message_t *message)
 {
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+       return status;
+
     if (message->frozen > 0) {
        message->frozen--;
        if (message->frozen == 0)