]> git.notmuchmail.org Git - notmuch/blobdiff - lib/message.cc
emacs: use --exclude=false when counting total messages
[notmuch] / lib / message.cc
index 3ca7b902ef304e609e511ef3ce9fcb86c6338fb9..63b216b638127c3ccc1b92bfdfc5d89cbed5c753 100644 (file)
@@ -68,7 +68,7 @@ struct maildir_flag_tag {
 };
 
 /* ASCII ordered table of Maildir flags and associated tags */
-static struct maildir_flag_tag flag2tag[] = {
+static const struct maildir_flag_tag flag2tag[] = {
     { 'D', "draft",   false },
     { 'F', "flagged", false },
     { 'P', "passed",  false },
@@ -93,10 +93,12 @@ _notmuch_message_destructor (notmuch_message_t *message)
 #define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error)
 
 static void
-_log_xapian_exception (const char *where, notmuch_message_t *message,  const Xapian::Error error) {
+_log_xapian_exception (const char *where, notmuch_message_t *message,  const Xapian::Error error)
+{
     notmuch_database_t *notmuch = notmuch_message_get_database (message);
+
     _notmuch_database_log (notmuch,
-                          "A Xapian exception occurred %s retrieving %s : %s\n",
+                          "A Xapian exception occurred at %s: %s\n",
                           where,
                           error.get_msg ().c_str ());
     notmuch->exception_reported = true;
@@ -275,7 +277,7 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
        return NULL;
     }
 
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+    if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY)
        INTERNAL_ERROR ("Failure to ensure database is writable.");
 
     try {
@@ -286,7 +288,8 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
 
        doc_id = _notmuch_database_generate_doc_id (notmuch);
     } catch (const Xapian::Error &error) {
-       _notmuch_database_log (notmuch_message_get_database (message), "A Xapian exception occurred creating message: %s\n",
+       _notmuch_database_log (notmuch,
+                              "A Xapian exception occurred creating message: %s\n",
                               error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        *status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
@@ -318,6 +321,7 @@ _notmuch_message_get_term (notmuch_message_t *message,
        return NULL;
 
     const std::string &term = *i;
+
     if (strncmp (term.c_str (), prefix, prefix_len))
        return NULL;
 
@@ -455,7 +459,8 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field)
            /* all the way without an exception */
            break;
        } catch (const Xapian::DatabaseModifiedError &error) {
-           notmuch_status_t status = _notmuch_database_reopen (message->notmuch);
+           notmuch_status_t status = notmuch_database_reopen (message->notmuch,
+                                                              NOTMUCH_DATABASE_MODE_READ_ONLY);
            if (status != NOTMUCH_STATUS_SUCCESS)
                INTERNAL_ERROR ("unhandled error from notmuch_database_reopen: %s\n",
                                notmuch_status_to_string (status));
@@ -572,9 +577,7 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header)
                return talloc_strdup (message, value.c_str ());
 
        } catch (Xapian::Error &error) {
-           _notmuch_database_log (notmuch_message_get_database (message), "A Xapian exception occurred when reading header: %s\n",
-                                  error.get_msg ().c_str ());
-           message->notmuch->exception_reported = true;
+           LOG_XAPIAN_EXCEPTION (message, error);
            return NULL;
        }
     }
@@ -739,7 +742,7 @@ _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
  * properties, along with any automatic tags*/
 /* According to Xapian API docs, none of these calls throw
  * exceptions */
-notmuch_private_status_t
+static notmuch_private_status_t
 _notmuch_message_remove_indexed_terms (notmuch_message_t *message)
 {
     Xapian::TermIterator i;
@@ -1099,7 +1102,7 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message)
 
        *colon = '\0';
 
-       db_path = notmuch_database_get_path (message->notmuch);
+       db_path = notmuch_config_get (message->notmuch, NOTMUCH_CONFIG_MAIL_ROOT);
 
        directory = _notmuch_database_get_directory_path (local,
                                                          message->notmuch,
@@ -1124,7 +1127,12 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message)
 const char *
 notmuch_message_get_filename (notmuch_message_t *message)
 {
-    _notmuch_message_ensure_filename_list (message);
+    try {
+       _notmuch_message_ensure_filename_list (message);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NULL;
+    }
 
     if (message->filename_list == NULL)
        return NULL;
@@ -1140,7 +1148,12 @@ notmuch_message_get_filename (notmuch_message_t *message)
 notmuch_filenames_t *
 notmuch_message_get_filenames (notmuch_message_t *message)
 {
-    _notmuch_message_ensure_filename_list (message);
+    try {
+       _notmuch_message_ensure_filename_list (message);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NULL;
+    }
 
     return _notmuch_filenames_create (message, message->filename_list);
 }
@@ -1148,20 +1161,50 @@ notmuch_message_get_filenames (notmuch_message_t *message)
 int
 notmuch_message_count_files (notmuch_message_t *message)
 {
-    _notmuch_message_ensure_filename_list (message);
+    try {
+       _notmuch_message_ensure_filename_list (message);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return -1;
+    }
 
     return _notmuch_string_list_length (message->filename_list);
 }
 
+notmuch_status_t
+notmuch_message_get_flag_st (notmuch_message_t *message,
+                            notmuch_message_flag_t flag,
+                            notmuch_bool_t *is_set)
+{
+    if (! is_set)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    try {
+       if (flag == NOTMUCH_MESSAGE_FLAG_GHOST &&
+           ! NOTMUCH_TEST_BIT (message->lazy_flags, flag))
+           _notmuch_message_ensure_metadata (message, NULL);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+    *is_set = NOTMUCH_TEST_BIT (message->flags, flag);
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
 notmuch_bool_t
 notmuch_message_get_flag (notmuch_message_t *message,
                          notmuch_message_flag_t flag)
 {
-    if (flag == NOTMUCH_MESSAGE_FLAG_GHOST &&
-       ! NOTMUCH_TEST_BIT (message->lazy_flags, flag))
-       _notmuch_message_ensure_metadata (message, NULL);
+    notmuch_bool_t is_set;
+    notmuch_status_t status;
 
-    return NOTMUCH_TEST_BIT (message->flags, flag);
+    status = notmuch_message_get_flag_st (message, flag, &is_set);
+
+    if (status)
+       return FALSE;
+    else
+       return is_set;
 }
 
 void
@@ -1183,9 +1226,7 @@ notmuch_message_get_date (notmuch_message_t *message)
     try {
        value = message->doc.get_value (NOTMUCH_VALUE_TIMESTAMP);
     } catch (Xapian::Error &error) {
-       _notmuch_database_log (notmuch_message_get_database (message), "A Xapian exception occurred when reading date: %s\n",
-                              error.get_msg ().c_str ());
-       message->notmuch->exception_reported = true;
+       LOG_XAPIAN_EXCEPTION (message, error);
        return 0;
     }
 
@@ -1200,7 +1241,12 @@ notmuch_message_get_tags (notmuch_message_t *message)
 {
     notmuch_tags_t *tags;
 
-    _notmuch_message_ensure_metadata (message, message->tag_list);
+    try {
+       _notmuch_message_ensure_metadata (message, message->tag_list);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NULL;
+    }
 
     tags = _notmuch_tags_create (message, message->tag_list);
     /* _notmuch_tags_create steals the reference to the tag_list, but
@@ -1281,9 +1327,7 @@ _notmuch_message_upgrade_last_mod (notmuch_message_t *message)
 void
 _notmuch_message_sync (notmuch_message_t *message)
 {
-    Xapian::WritableDatabase *db;
-
-    if (message->notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+    if (_notmuch_database_mode (message->notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY)
        return;
 
     if (! message->modified)
@@ -1301,8 +1345,8 @@ _notmuch_message_sync (notmuch_message_t *message)
                                    _notmuch_database_new_revision (
                                        message->notmuch)));
 
-    db = static_cast <Xapian::WritableDatabase *> (message->notmuch->xapian_db);
-    db->replace_document (message->doc_id, message->doc);
+    message->notmuch->writable_xapian_db->
+       replace_document (message->doc_id, message->doc);
     message->modified = false;
 }
 
@@ -1312,12 +1356,10 @@ notmuch_status_t
 _notmuch_message_delete (notmuch_message_t *message)
 {
     notmuch_status_t status;
-    Xapian::WritableDatabase *db;
-    const char *mid, *tid, *query_string;
+    const char *mid, *tid;
     notmuch_message_t *ghost;
     notmuch_private_status_t private_status;
     notmuch_database_t *notmuch;
-    notmuch_query_t *query;
     unsigned int count = 0;
     bool is_ghost;
 
@@ -1329,8 +1371,7 @@ _notmuch_message_delete (notmuch_message_t *message)
     if (status)
        return status;
 
-    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-    db->delete_document (message->doc_id);
+    message->notmuch->writable_xapian_db->delete_document (message->doc_id);
 
     /* if this was a ghost to begin with, we are done */
     private_status = _notmuch_message_has_term (message, "type", "ghost", &is_ghost);
@@ -1340,16 +1381,33 @@ _notmuch_message_delete (notmuch_message_t *message)
     if (is_ghost)
        return NOTMUCH_STATUS_SUCCESS;
 
-    query_string = talloc_asprintf (message, "thread:%s", tid);
-    query = notmuch_query_create (notmuch, query_string);
-    if (query == NULL)
-       return NOTMUCH_STATUS_OUT_OF_MEMORY;
-    status = notmuch_query_count_messages (query, &count);
-    if (status) {
-       notmuch_query_destroy (query);
-       return status;
+    /* look for a non-ghost message in the same thread */
+    try {
+       Xapian::PostingIterator thread_doc, thread_doc_end;
+       Xapian::PostingIterator mail_doc, mail_doc_end;
+
+       _notmuch_database_find_doc_ids (message->notmuch, "thread", tid, &thread_doc,
+                                       &thread_doc_end);
+       _notmuch_database_find_doc_ids (message->notmuch, "type", "mail", &mail_doc, &mail_doc_end);
+
+       while (count == 0 &&
+              thread_doc != thread_doc_end &&
+              mail_doc != mail_doc_end) {
+           thread_doc.skip_to (*mail_doc);
+           if (thread_doc != thread_doc_end) {
+               if (*thread_doc == *mail_doc) {
+                   count++;
+               } else {
+                   mail_doc.skip_to (*thread_doc);
+                   if (mail_doc != mail_doc_end && *thread_doc == *mail_doc)
+                       count++;
+               }
+           }
+       }
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
-
     if (count > 0) {
        /* reintroduce a ghost in its place because there are still
         * other active messages in this thread: */
@@ -1368,27 +1426,21 @@ _notmuch_message_delete (notmuch_message_t *message)
        notmuch_message_destroy (ghost);
        status = COERCE_STATUS (private_status, "Error converting to ghost message");
     } else {
-       /* the thread is empty; drop all ghost messages from it */
-       notmuch_messages_t *messages;
-       status = _notmuch_query_search_documents (query,
-                                                 "ghost",
-                                                 &messages);
-       if (status == NOTMUCH_STATUS_SUCCESS) {
-           notmuch_status_t last_error = NOTMUCH_STATUS_SUCCESS;
-           while (notmuch_messages_valid (messages)) {
-               message = notmuch_messages_get (messages);
-               status = _notmuch_message_delete (message);
-               if (status) /* we'll report the last failure we see;
-                                        * if there is more than one failure, we
-                                        * forget about previous ones */
-                   last_error = status;
-               notmuch_message_destroy (message);
-               notmuch_messages_move_to_next (messages);
+       /* the thread now contains only ghosts: delete them */
+       try {
+           Xapian::PostingIterator doc, doc_end;
+
+           _notmuch_database_find_doc_ids (message->notmuch, "thread", tid, &doc, &doc_end);
+
+           for (; doc != doc_end; doc++) {
+               message->notmuch->writable_xapian_db->delete_document (*doc);
            }
-           status = last_error;
+       } catch (Xapian::Error &error) {
+           LOG_XAPIAN_EXCEPTION (message, error);
+           return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
        }
+
     }
-    notmuch_query_destroy (query);
     return status;
 }
 
@@ -1573,24 +1625,31 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
     notmuch_private_status_t private_status;
     notmuch_status_t status;
 
-    status = _notmuch_database_ensure_writable (message->notmuch);
-    if (status)
-       return status;
+    try {
+       status = _notmuch_database_ensure_writable (message->notmuch);
+       if (status)
+           return status;
 
-    if (tag == NULL)
-       return NOTMUCH_STATUS_NULL_POINTER;
+       if (tag == NULL)
+           return NOTMUCH_STATUS_NULL_POINTER;
 
-    if (strlen (tag) > NOTMUCH_TAG_MAX)
-       return NOTMUCH_STATUS_TAG_TOO_LONG;
+       if (strlen (tag) > NOTMUCH_TAG_MAX)
+           return NOTMUCH_STATUS_TAG_TOO_LONG;
 
-    private_status = _notmuch_message_add_term (message, "tag", tag);
-    if (private_status) {
-       INTERNAL_ERROR ("_notmuch_message_add_term return unexpected value: %d\n",
-                       private_status);
-    }
+       private_status = _notmuch_message_add_term (message, "tag", tag);
+       if (private_status) {
+           return COERCE_STATUS (private_status,
+                                 "_notmuch_message_remove_term return unexpected value: %d\n",
+                                 private_status);
+       }
 
-    if (! message->frozen)
-       _notmuch_message_sync (message);
+       if (! message->frozen)
+           _notmuch_message_sync (message);
+
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
 
     return NOTMUCH_STATUS_SUCCESS;
 }
@@ -1601,24 +1660,30 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)
     notmuch_private_status_t private_status;
     notmuch_status_t status;
 
-    status = _notmuch_database_ensure_writable (message->notmuch);
-    if (status)
-       return status;
+    try {
+       status = _notmuch_database_ensure_writable (message->notmuch);
+       if (status)
+           return status;
 
-    if (tag == NULL)
-       return NOTMUCH_STATUS_NULL_POINTER;
+       if (tag == NULL)
+           return NOTMUCH_STATUS_NULL_POINTER;
 
-    if (strlen (tag) > NOTMUCH_TAG_MAX)
-       return NOTMUCH_STATUS_TAG_TOO_LONG;
+       if (strlen (tag) > NOTMUCH_TAG_MAX)
+           return NOTMUCH_STATUS_TAG_TOO_LONG;
 
-    private_status = _notmuch_message_remove_term (message, "tag", tag);
-    if (private_status) {
-       INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
-                       private_status);
-    }
+       private_status = _notmuch_message_remove_term (message, "tag", tag);
+       if (private_status) {
+           return COERCE_STATUS (private_status,
+                                 "_notmuch_message_remove_term return unexpected value: %d\n",
+                                 private_status);
+       }
 
-    if (! message->frozen)
-       _notmuch_message_sync (message);
+       if (! message->frozen)
+           _notmuch_message_sync (message);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
 
     return NOTMUCH_STATUS_SUCCESS;
 }
@@ -1663,7 +1728,7 @@ _filename_is_in_maildir (const char *filename)
     return NULL;
 }
 
-static void
+static notmuch_status_t
 _ensure_maildir_flags (notmuch_message_t *message, bool force)
 {
     const char *flags;
@@ -1678,8 +1743,10 @@ _ensure_maildir_flags (notmuch_message_t *message, bool force)
            message->maildir_flags = NULL;
        }
     }
-
-    for (filenames = notmuch_message_get_filenames (message);
+    filenames = notmuch_message_get_filenames (message);
+    if (! filenames)
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    for (;
         notmuch_filenames_valid (filenames);
         notmuch_filenames_move_to_next (filenames)) {
        filename = notmuch_filenames_get (filenames);
@@ -1705,13 +1772,38 @@ _ensure_maildir_flags (notmuch_message_t *message, bool force)
     }
     if (seen_maildir_info)
        message->maildir_flags = combined_flags;
+    return NOTMUCH_STATUS_SUCCESS;
 }
 
 notmuch_bool_t
 notmuch_message_has_maildir_flag (notmuch_message_t *message, char flag)
 {
-    _ensure_maildir_flags (message, false);
-    return message->maildir_flags && (strchr (message->maildir_flags, flag) != NULL);
+    notmuch_status_t status;
+    notmuch_bool_t ret;
+
+    status = notmuch_message_has_maildir_flag_st (message, flag, &ret);
+    if (status)
+       return FALSE;
+
+    return ret;
+}
+
+notmuch_status_t
+notmuch_message_has_maildir_flag_st (notmuch_message_t *message,
+                                    char flag,
+                                    notmuch_bool_t *is_set)
+{
+    notmuch_status_t status;
+
+    if (! is_set)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    status = _ensure_maildir_flags (message, false);
+    if (status)
+       return status;
+
+    *is_set =  message->maildir_flags && (strchr (message->maildir_flags, flag) != NULL);
+    return NOTMUCH_STATUS_SUCCESS;
 }
 
 notmuch_status_t
@@ -1720,7 +1812,9 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
     notmuch_status_t status;
     unsigned i;
 
-    _ensure_maildir_flags (message, true);
+    status = _ensure_maildir_flags (message, true);
+    if (status)
+       return status;
     /* If none of the filenames have any maildir info field (not even
      * an empty info with no flags set) then there's no information to
      * go on, so do nothing. */
@@ -1989,16 +2083,20 @@ notmuch_message_remove_all_tags (notmuch_message_t *message)
     status = _notmuch_database_ensure_writable (message->notmuch);
     if (status)
        return status;
+    tags = notmuch_message_get_tags (message);
+    if (! tags)
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
 
-    for (tags = notmuch_message_get_tags (message);
+    for (;
         notmuch_tags_valid (tags);
         notmuch_tags_move_to_next (tags)) {
        tag = notmuch_tags_get (tags);
 
        private_status = _notmuch_message_remove_term (message, "tag", tag);
        if (private_status) {
-           INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
-                           private_status);
+           return COERCE_STATUS (private_status,
+                                 "_notmuch_message_remove_term return unexpected value: %d\n",
+                                 private_status);
        }
     }
 
@@ -2119,8 +2217,10 @@ notmuch_message_reindex (notmuch_message_t *message,
     /* Save in case we need to delete message */
     orig_thread_id = notmuch_message_get_thread_id (message);
     if (! orig_thread_id) {
-       /* XXX TODO: make up new error return? */
-       INTERNAL_ERROR ("message without thread-id");
+       /* the following is correct as long as there is only one reason
+        * n_m_get_thread_id returns NULL
+        */
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 
     /* strdup it because the metadata may be invalidated */