From b8bb6d796458732622f80464dd808b3e02f57d9d Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 12 Jun 2016 22:05:50 -0300 Subject: [PATCH] lib: basic message-property API Initially, support get, set and removal of single key/value pair, as well as removing all properties. --- lib/Makefile.local | 1 + lib/message-private.h | 16 +++++ lib/message-property.cc | 108 ++++++++++++++++++++++++++++++++++ lib/message.cc | 52 +++++++++++++++- lib/notmuch.h | 72 +++++++++++++++++++++++ test/T610-message-property.sh | 84 ++++++++++++++++++++++++++ 6 files changed, 331 insertions(+), 2 deletions(-) create mode 100644 lib/message-private.h create mode 100644 lib/message-property.cc create mode 100755 test/T610-message-property.sh diff --git a/lib/Makefile.local b/lib/Makefile.local index b13513a2..3d1030a5 100644 --- a/lib/Makefile.local +++ b/lib/Makefile.local @@ -49,6 +49,7 @@ libnotmuch_cxx_srcs = \ $(dir)/directory.cc \ $(dir)/index.cc \ $(dir)/message.cc \ + $(dir)/message-property.cc \ $(dir)/query.cc \ $(dir)/query-fp.cc \ $(dir)/config.cc \ diff --git a/lib/message-private.h b/lib/message-private.h new file mode 100644 index 00000000..74199256 --- /dev/null +++ b/lib/message-private.h @@ -0,0 +1,16 @@ +#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 diff --git a/lib/message-property.cc b/lib/message-property.cc new file mode 100644 index 00000000..1f04a207 --- /dev/null +++ b/lib/message-property.cc @@ -0,0 +1,108 @@ +/* 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 + */ + +#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; +} diff --git a/lib/message.cc b/lib/message.cc index 63a8da56..9d3e8071 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -20,6 +20,7 @@ #include "notmuch-private.h" #include "database-private.h" +#include "message-private.h" #include @@ -395,7 +396,7 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message) message->in_reply_to = talloc_strdup (message, ""); } -static void +void _notmuch_message_invalidate_metadata (notmuch_message_t *message, const char *prefix_name) { @@ -552,7 +553,7 @@ notmuch_message_get_replies (notmuch_message_t *message) return _notmuch_messages_create (message->replies); } -static void +void _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix) { Xapian::TermIterator i; @@ -1799,3 +1800,50 @@ _notmuch_message_database (notmuch_message_t *message) { 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; +} diff --git a/lib/notmuch.h b/lib/notmuch.h index 2faa1468..b304dcac 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -179,6 +179,11 @@ typedef enum _notmuch_status { * 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. @@ -1653,6 +1658,73 @@ notmuch_message_thaw (notmuch_message_t *message); 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. * diff --git a/test/T610-message-property.sh b/test/T610-message-property.sh new file mode 100755 index 00000000..0217950f --- /dev/null +++ b/test/T610-message-property.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +test_description="message property API" + +. ./test-lib.sh || exit 1 + +add_email_corpus + +cat < c_head +#include +#include +#include +#include + +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 < 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 -- 2.26.2