From 566578efa5bae9baebe25c63cc4bc50b70c2f7ab Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 6 Aug 2016 22:52:33 +0900 Subject: [PATCH] [PATCH 3/9] lib: basic message-property API --- 27/5031057fa78d8a7d51935e18dcb7196c2f3b58 | 463 ++++++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 27/5031057fa78d8a7d51935e18dcb7196c2f3b58 diff --git a/27/5031057fa78d8a7d51935e18dcb7196c2f3b58 b/27/5031057fa78d8a7d51935e18dcb7196c2f3b58 new file mode 100644 index 000000000..89c2d6cca --- /dev/null +++ b/27/5031057fa78d8a7d51935e18dcb7196c2f3b58 @@ -0,0 +1,463 @@ +Return-Path: +X-Original-To: notmuch@notmuchmail.org +Delivered-To: notmuch@notmuchmail.org +Received: from localhost (localhost [127.0.0.1]) + by arlo.cworth.org (Postfix) with ESMTP id C86436DE035F + for ; Sat, 6 Aug 2016 06:53:01 -0700 (PDT) +X-Virus-Scanned: Debian amavisd-new at cworth.org +X-Spam-Flag: NO +X-Spam-Score: -0.004 +X-Spam-Level: +X-Spam-Status: No, score=-0.004 tagged_above=-999 required=5 + tests=[AWL=-0.005, HEADER_FROM_DIFFERENT_DOMAINS=0.001] + autolearn=disabled +Received: from arlo.cworth.org ([127.0.0.1]) + by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id O3vFkxT0BjWZ for ; + Sat, 6 Aug 2016 06:52:52 -0700 (PDT) +Received: from fethera.tethera.net (fethera.tethera.net [198.245.60.197]) + by arlo.cworth.org (Postfix) with ESMTPS id F2EEB6DE012F + for ; Sat, 6 Aug 2016 06:52:51 -0700 (PDT) +Received: from remotemail by fethera.tethera.net with local (Exim 4.84_2) + (envelope-from ) + id 1bW22E-0007Dj-UJ; Sat, 06 Aug 2016 09:53:06 -0400 +Received: (nullmailer pid 4129 invoked by uid 1000); + Sat, 06 Aug 2016 13:52:44 -0000 +From: David Bremner +To: notmuch@notmuchmail.org +Subject: [PATCH 3/9] lib: basic message-property API +Date: Sat, 6 Aug 2016 22:52:33 +0900 +Message-Id: <1470491559-3946-4-git-send-email-david@tethera.net> +X-Mailer: git-send-email 2.8.1 +In-Reply-To: <1470491559-3946-1-git-send-email-david@tethera.net> +References: <1470491559-3946-1-git-send-email-david@tethera.net> +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +X-BeenThere: notmuch@notmuchmail.org +X-Mailman-Version: 2.1.20 +Precedence: list +List-Id: "Use and development of the notmuch mail system." + +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +X-List-Received-Date: Sat, 06 Aug 2016 13:53:01 -0000 + +Initially, support get, set and removal of single key/value pair, as +well as removing all properties. +--- + lib/message-private.h | 16 +++++++ + lib/message-property.cc | 108 ++++++++++++++++++++++++++++++++++++++++++ + lib/message.cc | 52 +++++++++++++++++++- + lib/notmuch.h | 72 ++++++++++++++++++++++++++++ + test/T610-message-property.sh | 84 ++++++++++++++++++++++++++++++++ + 5 files changed, 330 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/message-private.h b/lib/message-private.h +new file mode 100644 +index 0000000..7419925 +--- /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 0000000..1f04a20 +--- /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 63a8da5..9d3e807 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 2faa146..b304dca 100644 +--- a/lib/notmuch.h ++++ b/lib/notmuch.h +@@ -180,6 +180,11 @@ typedef enum _notmuch_status { + */ + 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. + */ +@@ -1654,6 +1659,73 @@ 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. + * + * When this function returns TRUE, notmuch_tags_get will return a +diff --git a/test/T610-message-property.sh b/test/T610-message-property.sh +new file mode 100755 +index 0000000..0217950 +--- /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.8.1 + -- 2.26.2