$(dir)/directory.cc \
$(dir)/index.cc \
$(dir)/message.cc \
+ $(dir)/message-property.cc \
$(dir)/query.cc \
$(dir)/query-fp.cc \
$(dir)/config.cc \
--- /dev/null
+#ifndef MESSAGE_PRIVATE_H
+#define MESSAGE_PRIVATE_H
+
+notmuch_string_map_t *
+_notmuch_message_property_map (notmuch_message_t *message);
+
+notmuch_bool_t
+_notmuch_message_frozen (notmuch_message_t *message);
+
+void
+_notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix);
+
+void
+_notmuch_message_invalidate_metadata (notmuch_message_t *message, const char *prefix_name);
+
+#endif
--- /dev/null
+/* message-property.cc - Properties are like tags, but (key,value) pairs.
+ * keys are allowed to repeat.
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright © 2016 David Bremner
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include "notmuch-private.h"
+#include "database-private.h"
+#include "message-private.h"
+
+notmuch_status_t
+notmuch_message_get_property (notmuch_message_t *message, const char *key, const char **value)
+{
+ if (! value)
+ return NOTMUCH_STATUS_NULL_POINTER;
+
+ *value = _notmuch_string_map_get (_notmuch_message_property_map (message), key);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_notmuch_message_modify_property (notmuch_message_t *message, const char *key, const char *value,
+ notmuch_bool_t delete_it)
+{
+ notmuch_private_status_t private_status;
+ notmuch_status_t status;
+ char *term = NULL;
+
+ status = _notmuch_database_ensure_writable (_notmuch_message_database (message));
+ if (status)
+ return status;
+
+ if (key == NULL || value == NULL)
+ return NOTMUCH_STATUS_NULL_POINTER;
+
+ if (index (key, '='))
+ return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
+
+ term = talloc_asprintf (message, "%s=%s", key, value);
+
+ if (delete_it)
+ private_status = _notmuch_message_remove_term (message, "property", term);
+ else
+ private_status = _notmuch_message_add_term (message, "property", term);
+
+ if (private_status)
+ return COERCE_STATUS (private_status,
+ "Unhandled error modifying message property");
+ if (! _notmuch_message_frozen (message))
+ _notmuch_message_sync (message);
+
+ if (term)
+ talloc_free (term);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_message_add_property (notmuch_message_t *message, const char *key, const char *value)
+{
+ return _notmuch_message_modify_property (message, key, value, FALSE);
+}
+
+notmuch_status_t
+notmuch_message_remove_property (notmuch_message_t *message, const char *key, const char *value)
+{
+ return _notmuch_message_modify_property (message, key, value, TRUE);
+}
+
+notmuch_status_t
+notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key)
+{
+ notmuch_status_t status;
+ const char * term_prefix;
+
+ status = _notmuch_database_ensure_writable (_notmuch_message_database (message));
+ if (status)
+ return status;
+
+ _notmuch_message_invalidate_metadata (message, "property");
+ if (key)
+ term_prefix = talloc_asprintf (message, "%s%s=", _find_prefix ("property"), key);
+ else
+ term_prefix = _find_prefix ("property");
+
+ /* XXX better error reporting ? */
+ _notmuch_message_remove_terms (message, term_prefix);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
#include "notmuch-private.h"
#include "database-private.h"
+#include "message-private.h"
#include <stdint.h>
message->in_reply_to = talloc_strdup (message, "");
}
-static void
+void
_notmuch_message_invalidate_metadata (notmuch_message_t *message,
const char *prefix_name)
{
return _notmuch_messages_create (message->replies);
}
-static void
+void
_notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
{
Xapian::TermIterator i;
{
return message->notmuch;
}
+
+void
+_notmuch_message_ensure_property_map (notmuch_message_t *message)
+{
+ notmuch_string_node_t *node;
+
+ if (message->property_map)
+ return;
+
+ if (!message->property_term_list)
+ _notmuch_message_ensure_metadata (message);
+
+ message->property_map = _notmuch_string_map_create (message);
+
+ for (node = message->property_term_list->head; node; node = node->next) {
+ const char *key;
+ char *value;
+
+ value = index(node->string, '=');
+ if (!value)
+ INTERNAL_ERROR ("malformed property term");
+
+ *value = '\0';
+ value++;
+ key = node->string;
+
+ _notmuch_string_map_append (message->property_map, key, value);
+
+ }
+
+ talloc_free (message->property_term_list);
+ message->property_term_list = NULL;
+}
+
+notmuch_string_map_t *
+_notmuch_message_property_map (notmuch_message_t *message)
+{
+ _notmuch_message_ensure_property_map (message);
+
+ return message->property_map;
+}
+
+notmuch_bool_t
+_notmuch_message_frozen (notmuch_message_t *message)
+{
+ return message->frozen;
+}
* passed to a function expecting an absolute path.
*/
NOTMUCH_STATUS_PATH_ERROR,
+ /**
+ * One of the arguments violates the preconditions for the
+ * function, in a way not covered by a more specific argument.
+ */
+ NOTMUCH_STATUS_ILLEGAL_ARGUMENT,
/**
* Not an actual status value. Just a way to find out how many
* valid status values there are.
void
notmuch_message_destroy (notmuch_message_t *message);
+/**
+ * @name Message Properties
+ *
+ * This interface provides the ability to attach arbitrary (key,value)
+ * string pairs to a message, to remove such pairs, and to iterate
+ * over them. The caller should take some care as to what keys they
+ * add or delete values for, as other subsystems or extensions may
+ * depend on these properties.
+ *
+ */
+/**@{*/
+/**
+ * Retrieve the value for a single property key
+ *
+ * *value* is set to a string owned by the message or NULL if there is
+ * no such key. In the case of multiple values for the given key, the
+ * first one is retrieved.
+ *
+ * @returns
+ * - NOTMUCH_STATUS_NULL_POINTER: *value* may not be NULL.
+ * - NOTMUCH_STATUS_SUCCESS: No error occured.
+
+ */
+notmuch_status_t
+notmuch_message_get_property (notmuch_message_t *message, const char *key, const char **value);
+
+/**
+ * Add a (key,value) pair to a message
+ *
+ * @returns
+ * - NOTMUCH_STATUS_ILLEGAL_ARGUMENT: *key* may not contain an '=' character.
+ * - NOTMUCH_STATUS_NULL_POINTER: Neither *key* nor *value* may be NULL.
+ * - NOTMUCH_STATUS_SUCCESS: No error occured.
+ */
+notmuch_status_t
+notmuch_message_add_property (notmuch_message_t *message, const char *key, const char *value);
+
+/**
+ * Remove a (key,value) pair from a message.
+ *
+ * It is not an error to remove a non-existant (key,value) pair
+ *
+ * @returns
+ * - NOTMUCH_STATUS_ILLEGAL_ARGUMENT: *key* may not contain an '=' character.
+ * - NOTMUCH_STATUS_NULL_POINTER: Neither *key* nor *value* may be NULL.
+ * - NOTMUCH_STATUS_SUCCESS: No error occured.
+ */
+notmuch_status_t
+notmuch_message_remove_property (notmuch_message_t *message, const char *key, const char *value);
+
+/**
+ * Remove all (key,value) pairs from the given message.
+ *
+ * @param[in,out] message message to operate on.
+ * @param[in] key key to delete properties for. If NULL, delete
+ * properties for all keys
+ * @returns
+ * - NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in
+ * read-only mode so message cannot be modified.
+ * - NOTMUCH_STATUS_SUCCESS: No error occured.
+ *
+ */
+notmuch_status_t
+notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key);
+
+/**@}*/
+
/**
* Is the given 'tags' iterator pointing at a valid tag.
*
--- /dev/null
+#!/usr/bin/env bash
+test_description="message property API"
+
+. ./test-lib.sh || exit 1
+
+add_email_corpus
+
+cat <<EOF > c_head
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <notmuch-test.h>
+
+int main (int argc, char** argv)
+{
+ notmuch_database_t *db;
+ notmuch_message_t *message = NULL;
+ const char *val;
+ notmuch_status_t stat;
+
+ EXPECT0(notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db));
+ EXPECT0(notmuch_database_find_message(db, "4EFC743A.3060609@april.org", &message));
+ if (message == NULL) {
+ fprintf (stderr, "unable to find message");
+ exit (1);
+ }
+EOF
+
+cat <<EOF > c_tail
+ EXPECT0(notmuch_database_destroy(db));
+}
+EOF
+
+test_begin_subtest "notmuch_message_{add,get,remove}_property"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+ EXPECT0(notmuch_message_add_property (message, "testkey1", "testvalue1"));
+ EXPECT0(notmuch_message_get_property (message, "testkey1", &val));
+ printf("testkey1[1] = %s\n", val);
+ EXPECT0(notmuch_message_add_property (message, "testkey2", "this value has spaces and = sign"));
+ EXPECT0(notmuch_message_get_property (message, "testkey1", &val));
+ printf("testkey1[2] = %s\n", val);
+ EXPECT0(notmuch_message_get_property (message, "testkey1", &val));
+
+ EXPECT0(notmuch_message_get_property (message, "testkey2", &val));
+ printf("testkey2 = %s\n", val);
+
+ /* Add second value for key */
+ EXPECT0(notmuch_message_add_property (message, "testkey2", "zztestvalue3"));
+ EXPECT0(notmuch_message_get_property (message, "testkey2", &val));
+ printf("testkey2 = %s\n", val);
+
+ /* remove first value for key */
+ EXPECT0(notmuch_message_remove_property (message, "testkey2", "this value has spaces and = sign"));
+ EXPECT0(notmuch_message_get_property (message, "testkey2", &val));
+ printf("testkey2 = %s\n", val);
+
+ /* remove non-existant value for key */
+ EXPECT0(notmuch_message_remove_property (message, "testkey2", "this value has spaces and = sign"));
+ EXPECT0(notmuch_message_get_property (message, "testkey2", &val));
+ printf("testkey2 = %s\n", val);
+
+ /* remove only value for key */
+ EXPECT0(notmuch_message_remove_property (message, "testkey2", "zztestvalue3"));
+ EXPECT0(notmuch_message_get_property (message, "testkey2", &val));
+ printf("testkey2 = %s\n", val == NULL ? "NULL" : val);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+testkey1[1] = testvalue1
+testkey1[2] = testvalue1
+testkey2 = this value has spaces and = sign
+testkey2 = this value has spaces and = sign
+testkey2 = zztestvalue3
+testkey2 = zztestvalue3
+testkey2 = NULL
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+
+
+test_done