+ directory_id = strtol (direntry, &colon, 10);
+
+ if (colon == NULL || *colon != ':')
+ INTERNAL_ERROR ("malformed direntry");
+
+ directory = _notmuch_database_get_directory_path (ctx,
+ message->notmuch,
+ directory_id);
+
+ _notmuch_message_add_folder_terms (message, directory);
+ _notmuch_message_add_path_terms (message, directory);
+ }
+
+ return status;
+}
+
+/* Add an additional 'filename' for 'message'.
+ *
+ * This change will not be reflected in the database until the next
+ * call to _notmuch_message_sync. */
+notmuch_status_t
+_notmuch_message_add_filename (notmuch_message_t *message,
+ const char *filename)
+{
+ const char *relative, *directory;
+ notmuch_status_t status;
+ void *local = talloc_new (message);
+ char *direntry;
+
+ if (filename == NULL)
+ INTERNAL_ERROR ("Message filename cannot be NULL.");
+
+ if (! (message->notmuch->features & NOTMUCH_FEATURE_FILE_TERMS) ||
+ ! (message->notmuch->features & NOTMUCH_FEATURE_BOOL_FOLDER))
+ return NOTMUCH_STATUS_UPGRADE_REQUIRED;
+
+ relative = _notmuch_database_relative_path (message->notmuch, filename);
+
+ status = _notmuch_database_split_path (local, relative, &directory, NULL);
+ if (status)
+ return status;
+
+ status = _notmuch_database_filename_to_direntry (
+ local, message->notmuch, filename, NOTMUCH_FIND_CREATE, &direntry);
+ if (status)
+ return status;
+
+ /* New file-direntry allows navigating to this message with
+ * notmuch_directory_get_child_files() . */
+ _notmuch_message_add_term (message, "file-direntry", direntry);
+
+ _notmuch_message_add_folder_terms (message, directory);
+ _notmuch_message_add_path_terms (message, directory);
+
+ talloc_free (local);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Remove a particular 'filename' from 'message'.
+ *
+ * This change will not be reflected in the database until the next
+ * call to _notmuch_message_sync.
+ *
+ * If this message still has other filenames, returns
+ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID.
+ *
+ * Note: This function does not remove a document from the database,
+ * even if the specified filename is the only filename for this
+ * message. For that functionality, see
+ * notmuch_database_remove_message. */
+notmuch_status_t
+_notmuch_message_remove_filename (notmuch_message_t *message,
+ const char *filename)
+{
+ void *local = talloc_new (message);
+ char *direntry;
+ notmuch_private_status_t private_status;
+ notmuch_status_t status;
+
+ if (! (message->notmuch->features & NOTMUCH_FEATURE_FILE_TERMS) ||
+ ! (message->notmuch->features & NOTMUCH_FEATURE_BOOL_FOLDER))
+ return NOTMUCH_STATUS_UPGRADE_REQUIRED;
+
+ status = _notmuch_database_filename_to_direntry (
+ local, message->notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry);
+ if (status || ! direntry)
+ return status;
+
+ /* Unlink this file from its parent directory. */
+ private_status = _notmuch_message_remove_term (message,
+ "file-direntry", direntry);
+ status = COERCE_STATUS (private_status,
+ "Unexpected error from _notmuch_message_remove_term");
+ if (status)
+ return status;
+
+ /* Re-synchronize "folder:" and "path:" terms for this message. */
+
+ /* Remove all "folder:" terms. */
+ _notmuch_message_remove_terms (message, _find_prefix ("folder"));
+
+ /* Remove all "path:" terms. */
+ _notmuch_message_remove_terms (message, _find_prefix ("path"));
+
+ /* Add back terms for all remaining filenames of the message. */
+ status = _notmuch_message_add_directory_terms (local, message);
+
+ talloc_free (local);
+
+ return status;
+}
+
+/* Upgrade the "folder:" prefix from V1 to V2. */
+#define FOLDER_PREFIX_V1 "XFOLDER"
+#define ZFOLDER_PREFIX_V1 "Z" FOLDER_PREFIX_V1
+void
+_notmuch_message_upgrade_folder (notmuch_message_t *message)
+{
+ /* Remove all old "folder:" terms. */
+ _notmuch_message_remove_terms (message, FOLDER_PREFIX_V1);
+
+ /* Remove all old "folder:" stemmed terms. */
+ _notmuch_message_remove_terms (message, ZFOLDER_PREFIX_V1);
+
+ /* Add new boolean "folder:" and "path:" terms. */
+ _notmuch_message_add_directory_terms (message, message);
+}
+
+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 ("");
+ message->modified = true;
+}
+
+static void
+_notmuch_message_ensure_filename_list (notmuch_message_t *message)
+{
+ notmuch_string_node_t *node;
+
+ if (message->filename_list)
+ return;
+
+ _notmuch_message_ensure_metadata (message, message->filename_term_list);
+
+ message->filename_list = _notmuch_string_list_create (message);
+ node = message->filename_term_list->head;
+
+ if (! node) {
+ /* 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. */
+
+ std::string datastr = message->doc.get_data ();
+ const char *data = datastr.c_str ();
+
+ if (data == NULL)
+ INTERNAL_ERROR ("message with no filename");
+
+ _notmuch_string_list_append (message->filename_list, data);
+
+ return;
+ }
+
+ for (; node; node = node->next) {
+ void *local = talloc_new (message);
+ const char *db_path, *directory, *basename, *filename;
+ char *colon, *direntry = NULL;
+ unsigned int directory_id;
+
+ direntry = node->string;
+
+ directory_id = strtol (direntry, &colon, 10);
+
+ if (colon == NULL || *colon != ':')
+ INTERNAL_ERROR ("malformed direntry");
+
+ basename = colon + 1;
+
+ *colon = '\0';
+
+ db_path = notmuch_config_get (message->notmuch, NOTMUCH_CONFIG_MAIL_ROOT);
+
+ directory = _notmuch_database_get_directory_path (local,
+ message->notmuch,
+ directory_id);
+
+ if (strlen (directory))
+ filename = talloc_asprintf (message, "%s/%s/%s",
+ db_path, directory, basename);
+ else
+ filename = talloc_asprintf (message, "%s/%s",
+ db_path, basename);
+
+ _notmuch_string_list_append (message->filename_list, filename);
+
+ talloc_free (local);
+ }
+
+ talloc_free (message->filename_term_list);
+ message->filename_term_list = NULL;
+}
+
+const char *
+notmuch_message_get_filename (notmuch_message_t *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;
+
+ if (message->filename_list->head == NULL ||
+ message->filename_list->head->string == NULL) {
+ INTERNAL_ERROR ("message with no filename");
+ }
+
+ return message->filename_list->head->string;
+}
+
+notmuch_filenames_t *
+notmuch_message_get_filenames (notmuch_message_t *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);
+}
+
+int
+notmuch_message_count_files (notmuch_message_t *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)
+{
+ notmuch_bool_t is_set;
+ notmuch_status_t status;
+
+ status = notmuch_message_get_flag_st (message, flag, &is_set);
+
+ if (status)
+ return FALSE;
+ else
+ return is_set;
+}
+
+void
+notmuch_message_set_flag (notmuch_message_t *message,
+ notmuch_message_flag_t flag, notmuch_bool_t enable)
+{
+ if (enable)
+ NOTMUCH_SET_BIT (&message->flags, flag);
+ else
+ NOTMUCH_CLEAR_BIT (&message->flags, flag);
+ NOTMUCH_SET_BIT (&message->lazy_flags, flag);
+}
+
+time_t
+notmuch_message_get_date (notmuch_message_t *message)
+{
+ std::string value;
+
+ try {