From: David Bremner Date: Sun, 22 May 2016 14:28:59 +0000 (+2100) Subject: [RFC patch 2/2] RFC message-property API X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=1b9ba01ec5ec9cd5bf8f3cca885fbdb2fe1328f9;p=notmuch-archives.git [RFC patch 2/2] RFC message-property API --- diff --git a/55/d63742bb84669d2c443b892844305f20a36f72 b/55/d63742bb84669d2c443b892844305f20a36f72 new file mode 100644 index 000000000..e6d53ec09 --- /dev/null +++ b/55/d63742bb84669d2c443b892844305f20a36f72 @@ -0,0 +1,591 @@ +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 341046DE0222 + for ; Sun, 22 May 2016 07:29:29 -0700 (PDT) +X-Virus-Scanned: Debian amavisd-new at cworth.org +X-Spam-Flag: NO +X-Spam-Score: -0.012 +X-Spam-Level: +X-Spam-Status: No, score=-0.012 tagged_above=-999 required=5 + tests=[AWL=-0.001, SPF_PASS=-0.001, T_RP_MATCHES_RCVD=-0.01] + 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 3RVPcHjpcTy0 for ; + Sun, 22 May 2016 07:29:19 -0700 (PDT) +Received: from fethera.tethera.net (fethera.tethera.net [198.245.60.197]) + by arlo.cworth.org (Postfix) with ESMTPS id 244996DE02C9 + for ; Sun, 22 May 2016 07:29:12 -0700 (PDT) +Received: from remotemail by fethera.tethera.net with local (Exim 4.84) + (envelope-from ) + id 1b4UNL-00051b-Gz; Sun, 22 May 2016 10:29:03 -0400 +Received: (nullmailer pid 5523 invoked by uid 1000); + Sun, 22 May 2016 14:29:10 -0000 +From: David Bremner +To: notmuch@notmuchmail.org +Subject: [RFC patch 2/2] RFC message-property API +Date: Sun, 22 May 2016 11:28:59 -0300 +Message-Id: <1463927339-5441-3-git-send-email-david@tethera.net> +X-Mailer: git-send-email 2.8.1 +In-Reply-To: <1463927339-5441-1-git-send-email-david@tethera.net> +References: <1463927339-5441-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: Sun, 22 May 2016 14:29:29 -0000 + +"properties" are (key,value) pairs that can be attached to messages with +multiple instances of the same key being allowed. + +In this initial prototype, we ignore the existing caching mechanism for +message metadata; worse, we invalidate the cache needlessly. +--- + lib/Makefile.local | 1 + + lib/database.cc | 1 + + lib/message-private.h | 29 ++++++ + lib/message-property.cc | 214 ++++++++++++++++++++++++++++++++++++++++++ + lib/message.cc | 25 +---- + lib/notmuch.h | 33 +++++++ + test/T610-message-property.sh | 148 +++++++++++++++++++++++++++++ + 7 files changed, 427 insertions(+), 24 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 beb9635..1f55542 100644 +--- a/lib/Makefile.local ++++ b/lib/Makefile.local +@@ -48,6 +48,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/database.cc b/lib/database.cc +index 9630000..3ee5a12 100644 +--- a/lib/database.cc ++++ b/lib/database.cc +@@ -252,6 +252,7 @@ static prefix_t BOOLEAN_PREFIX_EXTERNAL[] = { + * colon, which keeps our own logic simpler. + */ + { "folder", "XFOLDER:" }, ++ { "property", "XPROPERTY:"}, + }; + + static prefix_t PROBABILISTIC_PREFIX[]= { +diff --git a/lib/message-private.h b/lib/message-private.h +new file mode 100644 +index 0000000..77a0b3d +--- /dev/null ++++ b/lib/message-private.h +@@ -0,0 +1,29 @@ ++#ifndef MESSAGE_PRIVATE_H ++#define MESSAGE_PRIVATE_H ++ ++struct visible _notmuch_message { ++ notmuch_database_t *notmuch; ++ Xapian::docid doc_id; ++ int frozen; ++ char *message_id; ++ char *thread_id; ++ char *in_reply_to; ++ notmuch_string_list_t *tag_list; ++ notmuch_string_list_t *filename_term_list; ++ notmuch_string_list_t *filename_list; ++ char *author; ++ notmuch_message_file_t *message_file; ++ notmuch_message_list_t *replies; ++ unsigned long flags; ++ /* For flags that are initialized on-demand, lazy_flags indicates ++ * if each flag has been initialized. */ ++ unsigned long lazy_flags; ++ ++ /* Message document modified since last sync */ ++ notmuch_bool_t modified; ++ ++ Xapian::Document doc; ++ Xapian::termcount termpos; ++}; ++ ++#endif +diff --git a/lib/message-property.cc b/lib/message-property.cc +new file mode 100644 +index 0000000..3e8293a +--- /dev/null ++++ b/lib/message-property.cc +@@ -0,0 +1,214 @@ ++/* 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" ++ ++struct _notmuch_message_property_list { ++ Xapian::TermIterator *iterator; ++ notmuch_message_t *message; ++ const char *prefix; ++ char *current; ++}; ++ ++static int ++_notmuch_message_property_list_destroy (notmuch_message_property_list_t *list) ++{ ++ delete list->iterator; ++ return 0; ++} ++ ++notmuch_status_t ++notmuch_message_get_property (notmuch_message_t *message, const char *key, const char **value) ++{ ++ notmuch_private_status_t private_status; ++ char *term = NULL; ++ ++ if (! value) ++ return NOTMUCH_STATUS_NULL_POINTER; ++ ++ term = talloc_asprintf (message, "%s%s=", ++ _find_prefix ("property"), key); ++ if (term == NULL) { ++ private_status = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY; ++ goto DONE; ++ } ++ ++ private_status = _notmuch_message_get_prefixed_term (message, term, value); ++ if (private_status == NOTMUCH_PRIVATE_STATUS_NO_TERM_FOUND) { ++ *value = NULL; ++ private_status = NOTMUCH_PRIVATE_STATUS_SUCCESS; ++ } ++ ++ DONE: ++ if (term) ++ talloc_free (term); ++ ++ if (private_status) ++ return COERCE_STATUS (private_status, ++ "Unhandled error retrieving message property"); ++ 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, '=') || index (value, '=')) ++ return NOTMUCH_STATUS_ILLEGAL_ARGUMENT; ++ ++ term = talloc_asprintf (message, "%s=%s", key, value); ++ ++ /* this invalidates all of the property, which it currently shouldn't */ ++ 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"); ++ ++ _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_get_property_list (notmuch_message_t *message, ++ const char *key, ++ notmuch_message_property_list_t **out) ++{ ++ notmuch_message_property_list_t *list = NULL; ++ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS; ++ notmuch_database_t *notmuch = _notmuch_message_database (message); ++ ++ const char *term; ++ ++ list = talloc (message, notmuch_message_property_list_t); ++ if (! list) { ++ status = NOTMUCH_STATUS_OUT_OF_MEMORY; ++ goto DONE; ++ } ++ ++ talloc_set_destructor (list, _notmuch_message_property_list_destroy); ++ list->current = NULL; ++ list->message = message; ++ ++ ++ term = talloc_asprintf (message, "%s%s=", ++ _find_prefix ("property"), key); ++ ++ list->prefix = talloc_strdup (list, term); ++ ++ if (term == NULL) { ++ status = NOTMUCH_STATUS_OUT_OF_MEMORY; ++ goto DONE; ++ } ++ ++ try { ++ ++ /* force copying onto the heap */ ++ list->iterator = new Xapian::TermIterator; ++ *list->iterator = message->doc.termlist_begin (); ++ ++ (*list->iterator).skip_to (term); ++ ++ } catch (const Xapian::Error &error) { ++ _notmuch_database_log (notmuch, "A Xapian exception occurred getting property iterator: %s.\n", ++ error.get_msg().c_str()); ++ notmuch->exception_reported = TRUE; ++ status = NOTMUCH_STATUS_XAPIAN_EXCEPTION; ++ } ++ ++ *out = list; ++ ++ DONE: ++ if (status && list) ++ talloc_free (list); ++ ++ return status; ++} ++ ++notmuch_bool_t ++notmuch_message_property_list_valid (notmuch_message_property_list_t *list) ++{ ++ if (*(list->iterator) == list->message->doc.termlist_end ()) ++ return FALSE; ++ ++ if (strncmp (list->prefix, (**list->iterator).c_str (), strlen(list->prefix)) != 0) ++ return FALSE; ++ ++ return TRUE; ++} ++ ++const char * ++notmuch_message_property_list_value (notmuch_message_property_list_t *list) ++{ ++ ++ if (list->current) ++ talloc_free (list->current); ++ ++ list->current = talloc_strdup (list, (**list->iterator).c_str () + strlen (list->prefix)); ++ ++ return list->current; ++} ++ ++void ++notmuch_message_property_list_move_to_next (notmuch_message_property_list_t *list) ++{ ++ (*(list->iterator))++; ++} ++ ++void ++notmuch_message_property_list_destroy (notmuch_message_property_list_t *list) ++{ ++ talloc_free (list); ++} +diff --git a/lib/message.cc b/lib/message.cc +index 90c2bb2..cc9ba93 100644 +--- a/lib/message.cc ++++ b/lib/message.cc +@@ -25,30 +25,7 @@ + + #include + +-struct visible _notmuch_message { +- notmuch_database_t *notmuch; +- Xapian::docid doc_id; +- int frozen; +- char *message_id; +- char *thread_id; +- char *in_reply_to; +- notmuch_string_list_t *tag_list; +- notmuch_string_list_t *filename_term_list; +- notmuch_string_list_t *filename_list; +- char *author; +- notmuch_message_file_t *message_file; +- notmuch_message_list_t *replies; +- unsigned long flags; +- /* For flags that are initialized on-demand, lazy_flags indicates +- * if each flag has been initialized. */ +- unsigned long lazy_flags; +- +- /* Message document modified since last sync */ +- notmuch_bool_t modified; +- +- Xapian::Document doc; +- Xapian::termcount termpos; +-}; ++#include + + #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0])) + +diff --git a/lib/notmuch.h b/lib/notmuch.h +index bd977c3..4a1519b 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. + */ +@@ -1651,6 +1656,34 @@ notmuch_message_thaw (notmuch_message_t *message); + void + notmuch_message_destroy (notmuch_message_t *message); + ++notmuch_status_t ++notmuch_message_get_property (notmuch_message_t *message, const char *key, const char **value); ++ ++notmuch_status_t ++notmuch_message_add_property (notmuch_message_t *message, const char *key, const char *value); ++ ++notmuch_status_t ++notmuch_message_remove_property (notmuch_message_t *message, const char *key, const char *value); ++ ++typedef struct _notmuch_message_property_list notmuch_message_property_list_t; ++ ++notmuch_status_t ++notmuch_message_get_property_list (notmuch_message_t *message, ++ const char *key, ++ notmuch_message_property_list_t **out); ++ ++notmuch_bool_t ++notmuch_message_property_list_valid (notmuch_message_property_list_t *metadata); ++ ++void ++notmuch_message_property_list_move_to_next (notmuch_message_property_list_t *list); ++ ++void ++notmuch_message_property_list_destroy (notmuch_message_property_list_t *list); ++ ++const char * ++notmuch_message_property_list_value (notmuch_message_property_list_t *list); ++ + /** + * 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 0000000..dcf9c3e +--- /dev/null ++++ b/test/T610-message-property.sh +@@ -0,0 +1,148 @@ ++#!/usr/bin/env bash ++test_description="message property API" ++ ++. ./test-lib.sh || exit 1 ++ ++add_email_corpus ++ ++cat < c_head ++#include ++#include ++#include ++#include ++ ++void run(int line, notmuch_status_t ret) ++{ ++ if (ret) { ++ fprintf (stderr, "line %d: %s\n", line, ret); ++ exit (1); ++ } ++} ++ ++#define RUN(v) run(__LINE__, v); ++ ++int main (int argc, char** argv) ++{ ++ notmuch_database_t *db; ++ notmuch_message_t *message = NULL; ++ const char *val; ++ notmuch_status_t stat; ++ ++ RUN(notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db)); ++ RUN(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 ++ RUN(notmuch_database_destroy(db)); ++} ++EOF ++ ++test_begin_subtest "notmuch_message_{add,get,remove}_property" ++cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ++{ ++ RUN(notmuch_message_add_property (message, "testkey1", "testvalue1")); ++ RUN(notmuch_message_add_property (message, "testkey2", "testvalue2")); ++ RUN(notmuch_message_get_property (message, "testkey1", &val)); ++ printf("testkey1 = %s\n", val); ++ RUN(notmuch_message_get_property (message, "testkey2", &val)); ++ printf("testkey2 = %s\n", val); ++ ++ /* Add second value for key */ ++ RUN(notmuch_message_add_property (message, "testkey2", "testvalue3")); ++ RUN(notmuch_message_get_property (message, "testkey2", &val)); ++ printf("testkey2 = %s\n", val); ++ ++ /* remove first value for key */ ++ RUN(notmuch_message_remove_property (message, "testkey2", "testvalue2")); ++ RUN(notmuch_message_get_property (message, "testkey2", &val)); ++ printf("testkey2 = %s\n", val); ++ ++ /* remove non-existant value for key */ ++ RUN(notmuch_message_remove_property (message, "testkey2", "testvalue2")); ++ RUN(notmuch_message_get_property (message, "testkey2", &val)); ++ printf("testkey2 = %s\n", val); ++ ++ /* remove only value for key */ ++ RUN(notmuch_message_remove_property (message, "testkey2", "testvalue3")); ++ RUN(notmuch_message_get_property (message, "testkey2", &val)); ++ printf("testkey2 = %s\n", val == NULL ? "NULL" : val); ++} ++EOF ++cat <<'EOF' >EXPECTED ++== stdout == ++testkey1 = testvalue1 ++testkey2 = testvalue2 ++testkey2 = testvalue2 ++testkey2 = testvalue3 ++testkey2 = testvalue3 ++testkey2 = NULL ++== stderr == ++EOF ++test_expect_equal_file EXPECTED OUTPUT ++ ++test_begin_subtest "notmuch_message_get_property_list: empty list" ++cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ++{ ++ notmuch_message_property_list_t *list; ++ RUN(notmuch_message_get_property_list (message, "nonexistent", &list)); ++ printf("valid = %d\n", notmuch_message_property_list_valid (list)); ++ notmuch_message_property_list_destroy (list); ++} ++EOF ++cat <<'EOF' >EXPECTED ++== stdout == ++valid = 0 ++== stderr == ++EOF ++test_expect_equal_file EXPECTED OUTPUT ++ ++test_begin_subtest "notmuch_message_property_list: one value" ++cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ++{ ++ notmuch_message_property_list_t *list; ++ ++ RUN(notmuch_message_get_property_list (message, "testkey1", &list)); ++ for (; notmuch_message_property_list_valid (list); notmuch_message_property_list_move_to_next (list)) { ++ printf("%s\n", notmuch_message_property_list_value(list)); ++ } ++ notmuch_message_property_list_destroy (list); ++} ++EOF ++cat <<'EOF' >EXPECTED ++== stdout == ++testvalue1 ++== stderr == ++EOF ++test_expect_equal_file EXPECTED OUTPUT ++ ++test_begin_subtest "notmuch_message_property_list: multiple values" ++cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ++{ ++ notmuch_message_property_list_t *list; ++ RUN(notmuch_message_add_property (message, "testkey1", "bob")); ++ RUN(notmuch_message_add_property (message, "testkey1", "testvalue2")); ++ RUN(notmuch_message_add_property (message, "testkey1", "alice")); ++ ++ RUN(notmuch_message_get_property_list (message, "testkey1", &list)); ++ for (; notmuch_message_property_list_valid (list); notmuch_message_property_list_move_to_next (list)) { ++ printf("%s\n", notmuch_message_property_list_value(list)); ++ } ++ notmuch_message_property_list_destroy (list); ++} ++EOF ++cat <<'EOF' >EXPECTED ++== stdout == ++alice ++bob ++testvalue1 ++testvalue2 ++== stderr == ++EOF ++test_expect_equal_file EXPECTED OUTPUT ++ ++ ++test_done +-- +2.8.1 +