]> git.notmuchmail.org Git - notmuch/blobdiff - lib/message.cc
Rename NOTMUCH_DATABASE_MODE_WRITABLE to NOTMUCH_DATABASE_MODE_READ_WRITE
[notmuch] / lib / message.cc
index 28f19a88502dc79ff29210297abfbed4f5ef2562..069cedb29bb6183459461cb47e8c588d4be72500 100644 (file)
@@ -21,6 +21,8 @@
 #include "notmuch-private.h"
 #include "database-private.h"
 
+#include <stdint.h>
+
 #include <gmime/gmime.h>
 
 #include <xapian.h>
@@ -31,8 +33,11 @@ struct _notmuch_message {
     int frozen;
     char *message_id;
     char *thread_id;
+    char *in_reply_to;
     char *filename;
     notmuch_message_file_t *message_file;
+    notmuch_message_list_t *replies;
+
     Xapian::Document doc;
 };
 
@@ -107,9 +112,17 @@ _notmuch_message_create (const void *talloc_owner,
     /* Each of these will be lazily created as needed. */
     message->message_id = NULL;
     message->thread_id = NULL;
+    message->in_reply_to = NULL;
     message->filename = NULL;
     message->message_file = NULL;
 
+    message->replies = _notmuch_message_list_create (message);
+    if (unlikely (message->replies == NULL)) {
+       if (status)
+           *status = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
+       return NULL;
+    }
+
     /* This is C++'s creepy "placement new", which is really just an
      * ugly way to call a constructor for a pre-allocated object. So
      * it's really not an error to not be checking for OUT_OF_MEMORY
@@ -133,17 +146,10 @@ _notmuch_message_create (const void *talloc_owner,
 }
 
 /* Create a new notmuch_message_t object for a specific message ID,
- * (which may or may not already exist in the databas).
+ * (which may or may not already exist in the database).
  *
- * Here, 'talloc owner' is an optional talloc context to which the new
- * message will belong. This allows for the caller to not bother
- * calling notmuch_message_destroy on the message, and no that all
- * memory will be reclaimed with 'talloc_owner' is free. The caller
- * still can call notmuch_message_destroy when finished with the
- * message if desired.
- *
- * The 'talloc_owner' argument can also be NULL, in which case the
- * caller *is* responsible for calling notmuch_message_destroy.
+ * The 'notmuch' database will be the talloc owner of the returned
+ * message.
  *
  * If there is already a document with message ID 'message_id' in the
  * database, then the returned message can be used to query/modify the
@@ -156,21 +162,26 @@ _notmuch_message_create (const void *talloc_owner,
  * not be NULL.)
  */
 notmuch_message_t *
-_notmuch_message_create_for_message_id (const void *talloc_owner,
-                                       notmuch_database_t *notmuch,
+_notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
                                        const char *message_id,
                                        notmuch_private_status_t *status_ret)
 {
     notmuch_message_t *message;
     Xapian::Document doc;
+    Xapian::WritableDatabase *db;
     unsigned int doc_id;
     char *term;
 
+    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
+       *status_ret = NOTMUCH_PRIVATE_STATUS_READONLY_DATABASE;
+       return NULL;
+    }
+
     *status_ret = NOTMUCH_PRIVATE_STATUS_SUCCESS;
 
     message = notmuch_database_find_message (notmuch, message_id);
     if (message)
-       return talloc_steal (talloc_owner, message);
+       return talloc_steal (notmuch, message);
 
     term = talloc_asprintf (NULL, "%s%s",
                            _find_prefix ("id"), message_id);
@@ -179,19 +190,20 @@ _notmuch_message_create_for_message_id (const void *talloc_owner,
        return NULL;
     }
 
+    db = static_cast<Xapian::WritableDatabase *> (notmuch->xapian_db);
     try {
        doc.add_term (term);
        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) {
        *status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
        return NULL;
     }
 
-    message = _notmuch_message_create (talloc_owner, notmuch,
+    message = _notmuch_message_create (notmuch, notmuch,
                                       doc_id, status_ret);
 
     /* We want to inform the caller that we had to create a new
@@ -259,40 +271,115 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header)
     return notmuch_message_file_get_header (message->message_file, header);
 }
 
+/* Return the message ID from the In-Reply-To header of 'message'.
+ *
+ * Returns an empty string ("") if 'message' has no In-Reply-To
+ * header.
+ *
+ * Returns NULL if any error occurs.
+ */
+const char *
+_notmuch_message_get_in_reply_to (notmuch_message_t *message)
+{
+    const char *prefix = _find_prefix ("replyto");
+    int prefix_len = strlen (prefix);
+    Xapian::TermIterator i;
+    std::string in_reply_to;
+
+    if (message->in_reply_to)
+       return message->in_reply_to;
+
+    i = message->doc.termlist_begin ();
+    i.skip_to (prefix);
+
+    if (i != message->doc.termlist_end ())
+       in_reply_to = *i;
+
+    /* It's perfectly valid for a message to have no In-Reply-To
+     * header. For these cases, we return an empty string. */
+    if (i == message->doc.termlist_end () ||
+       strncmp (in_reply_to.c_str (), prefix, prefix_len))
+    {
+       message->in_reply_to = talloc_strdup (message, "");
+       return message->in_reply_to;
+    }
+
+    message->in_reply_to = talloc_strdup (message,
+                                         in_reply_to.c_str () + prefix_len);
+
+#if DEBUG_DATABASE_SANITY
+    i++;
+
+    in_reply_to = *i;
+
+    if (i != message->doc.termlist_end () &&
+       strncmp ((*i).c_str (), prefix, prefix_len))
+    {
+       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);
+    }
+#endif
+
+    return message->in_reply_to;
+}
+
 const char *
 notmuch_message_get_thread_id (notmuch_message_t *message)
 {
+    const char *prefix = _find_prefix ("thread");
     Xapian::TermIterator i;
+    std::string id;
+
+    /* This code is written with the assumption that "thread" has a
+     * single-character prefix. */
+    assert (strlen (prefix) == 1);
 
     if (message->thread_id)
        return message->thread_id;
 
     i = message->doc.termlist_begin ();
-    i.skip_to (_find_prefix ("thread"));
+    i.skip_to (prefix);
 
-    if (i == message->doc.termlist_end ())
+    if (i != message->doc.termlist_end ())
+       id = *i;
+
+    if (i == message->doc.termlist_end () || id[0] != *prefix)
        INTERNAL_ERROR ("Message with document ID of %d has no thread ID.\n",
                        message->doc_id);
 
-    message->thread_id = talloc_strdup (message, (*i).c_str () + 1);
+    message->thread_id = talloc_strdup (message, id.c_str () + 1);
 
 #if DEBUG_DATABASE_SANITY
     i++;
+    id = *i;
 
-    if (i != message->doc.termlist_end () &&
-       strncmp ((*i).c_str (), _find_prefix ("thread"),
-                strlen (_find_prefix ("thread"))) == 0)
+    if (i != message->doc.termlist_end () && id[0] == *prefix)
     {
        INTERNAL_ERROR ("Message %s has duplicate thread IDs: %s and %s\n",
                        notmuch_message_get_message_id (message),
                        message->thread_id,
-                       (*i).c_str () + 1);
+                       id.c_str () + 1);
     }
 #endif
 
     return message->thread_id;
 }
 
+void
+_notmuch_message_add_reply (notmuch_message_t *message,
+                           notmuch_message_node_t *reply)
+{
+    _notmuch_message_list_append (message->replies, reply);
+}
+
+notmuch_messages_t *
+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
@@ -387,7 +474,7 @@ notmuch_message_get_tags (notmuch_message_t *message)
 
     i.skip_to (prefix);
 
-    while (1) {
+    while (i != end) {
        tag = *i;
 
        if (tag.empty () || tag[0] != *prefix)
@@ -411,7 +498,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);
@@ -463,11 +550,28 @@ _notmuch_message_ensure_thread_id (notmuch_message_t *message)
 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);
 }
 
+/* Ensure that 'message' is not holding any file object open. Future
+ * calls to various functions will still automatically open the
+ * message file as needed.
+ */
+void
+_notmuch_message_close (notmuch_message_t *message)
+{
+    if (message->message_file) {
+       notmuch_message_file_close (message->message_file);
+       message->message_file = NULL;
+    }
+}
+
 /* Add a name:value term to 'message', (the actual term will be
  * encoded by prefixing the value with a short prefix). See
  * NORMAL_PREFIX and BOOLEAN_PREFIX arrays for the mapping of term