]> git.notmuchmail.org Git - notmuch/blobdiff - lib/message.cc
emacs: Add new option notmuch-search-hide-excluded
[notmuch] / lib / message.cc
index 7713050a78a9d97c3a8ac9043fe1e807ae9804af..46638f800997d4c5250b3592e4be8ee79abc2a21 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;
@@ -167,6 +169,7 @@ _notmuch_message_create_for_document (const void *talloc_owner,
 
     message->doc = doc;
     message->termpos = 0;
+    message->modified = false;
 
     return message;
 }
@@ -275,7 +278,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 +289,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 +322,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;
 
@@ -336,23 +341,6 @@ _notmuch_message_get_term (notmuch_message_t *message,
     return value;
 }
 
-/*
- * For special applications where we only want the thread id, reading
- * in all metadata is a heavy I/O penalty.
- */
-const char *
-_notmuch_message_get_thread_id_only (notmuch_message_t *message)
-{
-
-    Xapian::TermIterator i = message->doc.termlist_begin ();
-    Xapian::TermIterator end = message->doc.termlist_end ();
-
-    message->thread_id = _notmuch_message_get_term (message, i, end,
-                                                   _find_prefix ("thread"));
-    return message->thread_id;
-}
-
-
 static void
 _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field)
 {
@@ -455,7 +443,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));
@@ -730,6 +719,8 @@ _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
            /* Ignore failure to remove non-existent term. */
        }
     }
+
+    _notmuch_message_invalidate_metadata (message, "property");
 }
 
 
@@ -737,7 +728,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;
@@ -803,7 +794,7 @@ is_maildir (const char *p)
 }
 
 /* Add "folder:" term for directory. */
-static notmuch_status_t
+NODISCARD static notmuch_status_t
 _notmuch_message_add_folder_terms (notmuch_message_t *message,
                                   const char *directory)
 {
@@ -839,7 +830,10 @@ _notmuch_message_add_folder_terms (notmuch_message_t *message,
        *folder = '\0';
     }
 
-    _notmuch_message_add_term (message, "folder", folder);
+    if (notmuch_status_t status = COERCE_STATUS (_notmuch_message_add_term (message, "folder",
+                                                                           folder),
+                                                "adding folder term"))
+       return status;
 
     talloc_free (folder);
 
@@ -850,12 +844,17 @@ _notmuch_message_add_folder_terms (notmuch_message_t *message,
 #define RECURSIVE_SUFFIX "/**"
 
 /* Add "path:" terms for directory. */
-static notmuch_status_t
+NODISCARD static notmuch_status_t
 _notmuch_message_add_path_terms (notmuch_message_t *message,
                                 const char *directory)
 {
+    notmuch_status_t status;
+
     /* Add exact "path:" term. */
-    _notmuch_message_add_term (message, "path", directory);
+    status = COERCE_STATUS (_notmuch_message_add_term (message, "path", directory),
+                           "adding path term");
+    if (status)
+       return status;
 
     if (strlen (directory)) {
        char *path, *p;
@@ -868,7 +867,10 @@ _notmuch_message_add_path_terms (notmuch_message_t *message,
        for (p = path + strlen (path) - 1; p > path; p--) {
            if (*p == '/') {
                strcpy (p, RECURSIVE_SUFFIX);
-               _notmuch_message_add_term (message, "path", path);
+               status = COERCE_STATUS (_notmuch_message_add_term (message, "path", path),
+                                       "adding path term");
+               if (status)
+                   return status;
            }
        }
 
@@ -876,7 +878,10 @@ _notmuch_message_add_path_terms (notmuch_message_t *message,
     }
 
     /* Recursive all-matching path:** for consistency. */
-    _notmuch_message_add_term (message, "path", "**");
+    status = COERCE_STATUS (_notmuch_message_add_term (message, "path", "**"),
+                           "adding path term");
+    if (status)
+       return status;
 
     return NOTMUCH_STATUS_SUCCESS;
 }
@@ -895,6 +900,7 @@ _notmuch_message_add_directory_terms (void *ctx, notmuch_message_t *message)
        const char *direntry, *directory;
        char *colon;
        const std::string &term = *i;
+       notmuch_status_t term_status;
 
        /* Terminate loop at first term without desired prefix. */
        if (strncmp (term.c_str (), direntry_prefix, direntry_prefix_len))
@@ -915,8 +921,13 @@ _notmuch_message_add_directory_terms (void *ctx, notmuch_message_t *message)
                                                          message->notmuch,
                                                          directory_id);
 
-       _notmuch_message_add_folder_terms (message, directory);
-       _notmuch_message_add_path_terms (message, directory);
+       term_status = _notmuch_message_add_folder_terms (message, directory);
+       if (term_status)
+           return term_status;
+
+       term_status = _notmuch_message_add_path_terms (message, directory);
+       if (term_status)
+           return term_status;
     }
 
     return status;
@@ -932,6 +943,7 @@ _notmuch_message_add_filename (notmuch_message_t *message,
 {
     const char *relative, *directory;
     notmuch_status_t status;
+    notmuch_private_status_t private_status;
     void *local = talloc_new (message);
     char *direntry;
 
@@ -955,10 +967,25 @@ _notmuch_message_add_filename (notmuch_message_t *message,
 
     /* New file-direntry allows navigating to this message with
      * notmuch_directory_get_child_files() . */
-    _notmuch_message_add_term (message, "file-direntry", direntry);
+    private_status = _notmuch_message_add_term (message, "file-direntry", direntry);
+    switch (private_status) {
+    case NOTMUCH_PRIVATE_STATUS_SUCCESS:
+       break;
+    case NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG:
+       _notmuch_database_log (message->notmuch, "filename too long for file-direntry term: %s\n",
+                              filename);
+       return NOTMUCH_STATUS_PATH_ERROR;
+    default:
+       return COERCE_STATUS (private_status, "adding file-direntry term");
+    }
+
+    status = _notmuch_message_add_folder_terms (message, directory);
+    if (status)
+       return status;
 
-    _notmuch_message_add_folder_terms (message, directory);
-    _notmuch_message_add_path_terms (message, directory);
+    status = _notmuch_message_add_path_terms (message, directory);
+    if (status)
+       return status;
 
     talloc_free (local);
 
@@ -1097,7 +1124,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,
@@ -1166,15 +1193,40 @@ notmuch_message_count_files (notmuch_message_t *message)
     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
@@ -1297,9 +1349,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)
@@ -1317,8 +1367,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;
 }
 
@@ -1328,12 +1378,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;
 
@@ -1345,27 +1393,44 @@ _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);
-
-    /* if this was a ghost to begin with, we are done */
-    private_status = _notmuch_message_has_term (message, "type", "ghost", &is_ghost);
-    if (private_status)
-       return COERCE_STATUS (private_status,
-                             "Error trying to determine whether message was a ghost");
-    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;
+    try {
+       Xapian::PostingIterator thread_doc, thread_doc_end;
+       Xapian::PostingIterator mail_doc, mail_doc_end;
+
+       /* look for a non-ghost message in the same thread */
+       /* if this was a ghost to begin with, we are done */
+       private_status = _notmuch_message_has_term (message, "type", "ghost", &is_ghost);
+       if (private_status)
+           return COERCE_STATUS (private_status,
+                                 "Error trying to determine whether message was a ghost");
+
+       message->notmuch->writable_xapian_db->delete_document (message->doc_id);
+
+       if (is_ghost)
+           return NOTMUCH_STATUS_SUCCESS;
+
+       _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: */
@@ -1384,27 +1449,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;
 }
 
@@ -1446,31 +1505,37 @@ _notmuch_message_close (notmuch_message_t *message)
  *
  * This change will not be reflected in the database until the next
  * call to _notmuch_message_sync. */
-notmuch_private_status_t
+NODISCARD notmuch_private_status_t
 _notmuch_message_add_term (notmuch_message_t *message,
                           const char *prefix_name,
                           const char *value)
 {
 
     char *term;
+    notmuch_private_status_t status = NOTMUCH_PRIVATE_STATUS_SUCCESS;
 
     if (value == NULL)
        return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
 
     term = talloc_asprintf (message, "%s%s",
                            _find_prefix (prefix_name), value);
+    if (strlen (term) > NOTMUCH_TERM_MAX) {
+       status = NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
+       goto DONE;
+    }
 
-    if (strlen (term) > NOTMUCH_TERM_MAX)
-       return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
-
-    message->doc.add_term (term, 0);
-    message->modified = true;
+    try {
+       message->doc.add_term (term, 0);
+       message->modified = true;
+       _notmuch_message_invalidate_metadata (message, prefix_name);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       status = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
+    }
 
+  DONE:
     talloc_free (term);
-
-    _notmuch_message_invalidate_metadata (message, prefix_name);
-
-    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+    return status;
 }
 
 /* Parse 'text' and add a term to 'message' for each parsed word. Each
@@ -1515,7 +1580,7 @@ _notmuch_message_gen_terms (notmuch_message_t *message,
  *
  * This change will not be reflected in the database until the next
  * call to _notmuch_message_sync. */
-notmuch_private_status_t
+NODISCARD notmuch_private_status_t
 _notmuch_message_remove_term (notmuch_message_t *message,
                              const char *prefix_name,
                              const char *value)
@@ -1534,11 +1599,12 @@ _notmuch_message_remove_term (notmuch_message_t *message,
     try {
        message->doc.remove_term (term);
        message->modified = true;
-    } catch (const Xapian::InvalidArgumentError) {
+    } catch (const Xapian::InvalidArgumentError &error) {
        /* We'll let the philosophers try to wrestle with the
         * question of whether failing to remove that which was not
         * there in the first place is failure. For us, we'll silently
         * consider it all good. */
+       LOG_XAPIAN_EXCEPTION (message, error);
     }
 
     talloc_free (term);
@@ -1589,24 +1655,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;
 }
@@ -1617,24 +1690,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;
 }
@@ -1679,7 +1758,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;
@@ -1694,8 +1773,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);
@@ -1721,13 +1802,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
@@ -1736,7 +1842,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. */
@@ -1942,6 +2050,10 @@ notmuch_message_tags_to_maildir_flags (notmuch_message_t *message)
     char *to_set, *to_clear;
     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
 
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+       return status;
+
     _get_maildir_flag_actions (message, &to_set, &to_clear);
 
     for (filenames = notmuch_message_get_filenames (message);
@@ -2005,16 +2117,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);
        }
     }
 
@@ -2135,8 +2251,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 */
@@ -2199,7 +2317,11 @@ notmuch_message_reindex (notmuch_message_t *message,
        if (thread_id == NULL)
            thread_id = orig_thread_id;
 
-       _notmuch_message_add_term (message, "thread", thread_id);
+       ret = COERCE_STATUS (_notmuch_message_add_term (message, "thread", thread_id),
+                            "adding thread term");
+       if (ret)
+           goto DONE;
+
        /* Take header values only from first filename */
        if (found == 0)
            _notmuch_message_set_header_values (message, date, from, subject);
@@ -2217,7 +2339,11 @@ notmuch_message_reindex (notmuch_message_t *message,
     }
     if (found == 0) {
        /* put back thread id to help cleanup */
-       _notmuch_message_add_term (message, "thread", orig_thread_id);
+       ret = COERCE_STATUS (_notmuch_message_add_term (message, "thread", orig_thread_id),
+                            "adding thread term");
+       if (ret)
+           goto DONE;
+
        ret = _notmuch_message_delete (message);
     } else {
        _notmuch_message_sync (message);