#include <gmime/gmime.h>
-struct visible _notmuch_message {
+struct _notmuch_message {
notmuch_database_t *notmuch;
Xapian::docid doc_id;
int frozen;
notmuch_string_list_t *tag_list;
notmuch_string_list_t *filename_term_list;
notmuch_string_list_t *filename_list;
+ char *maildir_flags;
char *author;
notmuch_message_file_t *message_file;
notmuch_string_list_t *property_term_list;
unsigned long lazy_flags;
/* Message document modified since last sync */
- notmuch_bool_t modified;
+ bool modified;
/* last view of database the struct is synced with */
unsigned long last_view;
struct maildir_flag_tag {
char flag;
const char *tag;
- notmuch_bool_t inverse;
+ bool inverse;
};
/* ASCII ordered table of Maildir flags and associated tags */
static struct maildir_flag_tag flag2tag[] = {
- { 'D', "draft", FALSE},
- { 'F', "flagged", FALSE},
- { 'P', "passed", FALSE},
- { 'R', "replied", FALSE},
- { 'S', "unread", TRUE }
+ { 'D', "draft", false},
+ { 'F', "flagged", false},
+ { 'P', "passed", false},
+ { 'R', "replied", false},
+ { 'S', "unread", true }
};
/* We end up having to call the destructor explicitly because we had
message->tag_list = NULL;
message->filename_term_list = NULL;
message->filename_list = NULL;
+ message->maildir_flags = NULL;
message->message_file = NULL;
message->author = NULL;
message->property_term_list = NULL;
doc_id = _notmuch_database_generate_doc_id (notmuch);
} catch (const Xapian::Error &error) {
- _notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred creating message: %s\n",
+ _notmuch_database_log(notmuch_message_get_database (message), "A Xapian exception occurred creating message: %s\n",
error.get_msg().c_str());
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
*status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
return NULL;
}
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)
{
return;
message->message_file = _notmuch_message_file_open_ctx (
- _notmuch_message_database (message), message, filename);
+ notmuch_message_get_database (message), message, filename);
}
const char *
return talloc_strdup (message, value.c_str ());
} catch (Xapian::Error &error) {
- _notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred when reading header: %s\n",
+ _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;
+ message->notmuch->exception_reported = true;
return NULL;
}
}
_notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
{
Xapian::TermIterator i;
- size_t prefix_len = strlen (prefix);
+ size_t prefix_len = 0;
+
+ prefix_len = strlen (prefix);
while (1) {
i = message->doc.termlist_begin ();
try {
message->doc.remove_term ((*i));
- message->modified = TRUE;
+ message->modified = true;
} catch (const Xapian::InvalidArgumentError) {
/* Ignore failure to remove non-existent term. */
}
}
}
+
+/* Remove all terms generated by indexing, i.e. not tags or
+ * properties, along with any automatic tags*/
+notmuch_private_status_t
+_notmuch_message_remove_indexed_terms (notmuch_message_t *message)
+{
+ Xapian::TermIterator i;
+
+ const std::string
+ id_prefix = _find_prefix ("id"),
+ property_prefix = _find_prefix ("property"),
+ tag_prefix = _find_prefix ("tag"),
+ type_prefix = _find_prefix ("type");
+
+ for (i = message->doc.termlist_begin ();
+ i != message->doc.termlist_end (); i++) {
+
+ const std::string term = *i;
+
+ if (term.compare (0, type_prefix.size (), type_prefix) == 0)
+ continue;
+
+ if (term.compare (0, id_prefix.size (), id_prefix) == 0)
+ continue;
+
+ if (term.compare (0, property_prefix.size (), property_prefix) == 0)
+ continue;
+
+ if (term.compare (0, tag_prefix.size (), tag_prefix) == 0 &&
+ term.compare (1, strlen("encrypted"), "encrypted") != 0 &&
+ term.compare (1, strlen("signed"), "signed") != 0 &&
+ term.compare (1, strlen("attachment"), "attachment") != 0)
+ continue;
+
+ try {
+ message->doc.remove_term ((*i));
+ message->modified = true;
+ } catch (const Xapian::InvalidArgumentError) {
+ /* Ignore failure to remove non-existent term. */
+ } catch (const Xapian::Error &error) {
+ notmuch_database_t *notmuch = message->notmuch;
+
+ if (!notmuch->exception_reported) {
+ _notmuch_database_log(notmuch_message_get_database (message), "A Xapian exception occurred creating message: %s\n",
+ error.get_msg().c_str());
+ notmuch->exception_reported = true;
+ }
+ return NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
+ }
+ }
+ return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
/* Return true if p points at "new" or "cur". */
static bool is_maildir (const char *p)
{
talloc_free (folder);
+ message->modified = true;
return NOTMUCH_STATUS_SUCCESS;
}
_notmuch_message_clear_data (notmuch_message_t *message)
{
message->doc.set_data ("");
- message->modified = TRUE;
+ message->modified = true;
}
static void
return _notmuch_filenames_create (message, message->filename_list);
}
+int
+notmuch_message_count_files (notmuch_message_t *message)
+{
+ _notmuch_message_ensure_filename_list (message);
+
+ return _notmuch_string_list_length (message->filename_list);
+}
+
notmuch_bool_t
notmuch_message_get_flag (notmuch_message_t *message,
notmuch_message_flag_t flag)
try {
value = message->doc.get_value (NOTMUCH_VALUE_TIMESTAMP);
} catch (Xapian::Error &error) {
- _notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred when reading date: %s\n",
+ _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;
+ message->notmuch->exception_reported = true;
return 0;
}
if (date == NULL || *date == '\0') {
time_value = 0;
} else {
- time_value = g_mime_utils_header_decode_date (date, NULL);
+ time_value = g_mime_utils_header_decode_date_unix (date);
/*
* Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=779923
*/
Xapian::sortable_serialise (time_value));
message->doc.add_value (NOTMUCH_VALUE_FROM, from);
message->doc.add_value (NOTMUCH_VALUE_SUBJECT, subject);
- message->modified = TRUE;
+ message->modified = true;
}
/* Upgrade a message to support NOTMUCH_FEATURE_LAST_MOD. The caller
{
/* _notmuch_message_sync will update the last modification
* revision; we just have to ask it to. */
- message->modified = TRUE;
+ message->modified = true;
}
/* Synchronize changes made to message->doc out into the database. */
db = static_cast <Xapian::WritableDatabase *> (message->notmuch->xapian_db);
db->replace_document (message->doc_id, message->doc);
- message->modified = FALSE;
+ message->modified = false;
}
/* Delete a message document from the database, leaving a ghost
notmuch_database_t *notmuch;
notmuch_query_t *query;
unsigned int count = 0;
- notmuch_bool_t is_ghost;
+ bool is_ghost;
mid = notmuch_message_get_message_id (message);
tid = notmuch_message_get_thread_id (message);
return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
message->doc.add_term (term, 0);
- message->modified = TRUE;
+ message->modified = true;
talloc_free (term);
try {
message->doc.remove_term (term);
- message->modified = TRUE;
+ message->modified = true;
} catch (const Xapian::InvalidArgumentError) {
/* We'll let the philosophers try to wrestle with the
* question of whether failing to remove that which was not
_notmuch_message_has_term (notmuch_message_t *message,
const char *prefix_name,
const char *value,
- notmuch_bool_t *result)
+ bool *result)
{
char *term;
- notmuch_bool_t out = FALSE;
+ bool out = false;
notmuch_private_status_t status = NOTMUCH_PRIVATE_STATUS_SUCCESS;
if (value == NULL)
i.skip_to (term);
if (i != message->doc.termlist_end () &&
!strcmp ((*i).c_str (), term))
- out = TRUE;
+ out = true;
} catch (Xapian::Error &error) {
status = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
}
return NULL;
}
-notmuch_status_t
-notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
+static void
+_ensure_maildir_flags (notmuch_message_t *message, bool force)
{
const char *flags;
- notmuch_status_t status;
notmuch_filenames_t *filenames;
const char *filename, *dir;
char *combined_flags = talloc_strdup (message, "");
- unsigned i;
int seen_maildir_info = 0;
+ if (message->maildir_flags) {
+ if (force) {
+ talloc_free (message->maildir_flags);
+ message->maildir_flags = NULL;
+ }
+ }
+
for (filenames = notmuch_message_get_filenames (message);
notmuch_filenames_valid (filenames);
notmuch_filenames_move_to_next (filenames))
seen_maildir_info = 1;
}
}
+ if (seen_maildir_info)
+ message->maildir_flags = combined_flags;
+}
+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
+notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
+{
+ notmuch_status_t status;
+ unsigned i;
+
+ _ensure_maildir_flags (message, true);
/* 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. */
- if (! seen_maildir_info)
+ if (! message->maildir_flags)
return NOTMUCH_STATUS_SUCCESS;
status = notmuch_message_freeze (message);
return status;
for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {
- if ((strchr (combined_flags, flag2tag[i].flag) != NULL)
+ if ((strchr (message->maildir_flags, flag2tag[i].flag) != NULL)
^
flag2tag[i].inverse)
{
}
status = notmuch_message_thaw (message);
- talloc_free (combined_flags);
-
return status;
}
char *filename_new, *dir;
char flag_map[128];
int flags_in_map = 0;
- notmuch_bool_t flags_changed = FALSE;
+ bool flags_changed = false;
unsigned int i;
char *s;
if (flag_map[flag] == 0) {
flag_map[flag] = 1;
flags_in_map++;
- flags_changed = TRUE;
+ flags_changed = true;
}
}
if (flag_map[flag]) {
flag_map[flag] = 0;
flags_in_map--;
- flags_changed = TRUE;
+ flags_changed = true;
}
}
}
notmuch_database_t *
-_notmuch_message_database (notmuch_message_t *message)
+notmuch_message_get_database (const notmuch_message_t *message)
{
return message->notmuch;
}
const char *key;
char *value;
- value = index(node->string, '=');
+ value = strchr(node->string, '=');
if (!value)
INTERNAL_ERROR ("malformed property term");
return message->property_map;
}
-notmuch_bool_t
+bool
_notmuch_message_frozen (notmuch_message_t *message)
{
return message->frozen;
}
+
+notmuch_status_t
+notmuch_message_reindex (notmuch_message_t *message,
+ notmuch_indexopts_t *indexopts)
+{
+ notmuch_database_t *notmuch = NULL;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+ notmuch_private_status_t private_status;
+ notmuch_filenames_t *orig_filenames = NULL;
+ const char *orig_thread_id = NULL;
+ notmuch_message_file_t *message_file = NULL;
+
+ int found = 0;
+
+ if (message == NULL)
+ return NOTMUCH_STATUS_NULL_POINTER;
+
+ /* 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");
+ }
+
+ /* strdup it because the metadata may be invalidated */
+ orig_thread_id = talloc_strdup (message, orig_thread_id);
+
+ notmuch = notmuch_message_get_database (message);
+
+ ret = _notmuch_database_ensure_writable (notmuch);
+ if (ret)
+ return ret;
+
+ orig_filenames = notmuch_message_get_filenames (message);
+
+ private_status = _notmuch_message_remove_indexed_terms (message);
+ if (private_status) {
+ ret = COERCE_STATUS(private_status, "error removing terms");
+ goto DONE;
+ }
+
+ ret = notmuch_message_remove_all_properties_with_prefix (message, "index.");
+ if (ret)
+ goto DONE; /* XXX TODO: distinguish from other error returns above? */
+ if (indexopts && notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_FALSE) {
+ ret = notmuch_message_remove_all_properties (message, "session-key");
+ if (ret)
+ goto DONE;
+ }
+
+ /* re-add the filenames with the associated indexopts */
+ for (; notmuch_filenames_valid (orig_filenames);
+ notmuch_filenames_move_to_next (orig_filenames)) {
+
+ const char *date;
+ const char *from, *to, *subject;
+ char *message_id = NULL;
+ const char *thread_id = NULL;
+
+ const char *filename = notmuch_filenames_get (orig_filenames);
+
+ message_file = _notmuch_message_file_open (notmuch, filename);
+ if (message_file == NULL)
+ continue;
+
+ ret = _notmuch_message_file_get_headers (message_file,
+ &from, &subject, &to, &date,
+ &message_id);
+ if (ret)
+ goto DONE;
+
+ /* XXX TODO: deal with changing message id? */
+
+ _notmuch_message_add_filename (message, filename);
+
+ ret = _notmuch_database_link_message_to_parents (notmuch, message,
+ message_file,
+ &thread_id);
+ if (ret)
+ goto DONE;
+
+ if (thread_id == NULL)
+ thread_id = orig_thread_id;
+
+ _notmuch_message_add_term (message, "thread", thread_id);
+ /* Take header values only from first filename */
+ if (found == 0)
+ _notmuch_message_set_header_values (message, date, from, subject);
+
+ ret = _notmuch_message_index_file (message, indexopts, message_file);
+
+ if (ret == NOTMUCH_STATUS_FILE_ERROR)
+ continue;
+ if (ret)
+ goto DONE;
+
+ found++;
+ _notmuch_message_file_close (message_file);
+ message_file = NULL;
+ }
+ if (found == 0) {
+ /* put back thread id to help cleanup */
+ _notmuch_message_add_term (message, "thread", orig_thread_id);
+ ret = _notmuch_message_delete (message);
+ } else {
+ _notmuch_message_sync (message);
+ }
+
+ DONE:
+ if (message_file)
+ _notmuch_message_file_close (message_file);
+
+ /* XXX TODO destroy orig_filenames? */
+ return ret;
+}