unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
From: David Bremner <david@tethera.net>
To: notmuch@notmuchmail.org
Subject: [RFC patch 2/2] RFC message-property API
Date: Sun, 22 May 2016 11:28:59 -0300	[thread overview]
Message-ID: <1463927339-5441-3-git-send-email-david@tethera.net> (raw)
In-Reply-To: <1463927339-5441-1-git-send-email-david@tethera.net>

"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 <david@tethera.net>
+ */
+
+#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 <gmime/gmime.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;
-};
+#include <message-private.h>
 
 #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 <<EOF > c_head
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <notmuch.h>
+
+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 <<EOF > 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

  parent reply	other threads:[~2016-05-22 14:29 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-05-22 14:28 RFC: message property API David Bremner
2016-05-22 14:28 ` [RFC patch 1/2] lib: refactor _notmuch_message_has_term David Bremner
2016-05-22 14:28 ` David Bremner [this message]
2016-05-30 11:49 ` message properties, round 2 David Bremner
2016-05-30 11:49   ` [RFC2 Patch 1/5] lib: read "property" terms from messages David Bremner
2016-05-30 11:49   ` [RFC2 Patch 2/5] lib: private string map (associative array) API David Bremner
2016-05-30 11:49   ` [RFC2 Patch 3/5] lib: basic message-property API David Bremner
2016-05-30 11:49   ` [RFC2 Patch 4/5] lib: extend private string map API with iterators David Bremner
2016-05-30 11:49   ` [RFC2 Patch 5/5] lib: iterator API for message properties David Bremner
2016-06-01  1:12     ` David Bremner
2016-06-01  1:52       ` Daniel Kahn Gillmor
2016-06-01  5:04         ` Tomi Ollila
2016-06-01 10:04         ` David Bremner
2016-06-01 14:13         ` Daniel Kahn Gillmor
2016-06-01 23:29           ` David Bremner
2016-06-02 17:33             ` Daniel Kahn Gillmor
2016-06-03 12:54               ` David Bremner
2016-06-03 14:38                 ` Daniel Kahn Gillmor
2016-06-03 23:12                   ` David Bremner
2016-06-04 16:23                     ` Daniel Kahn Gillmor
2016-06-05 10:24                   ` [PATCH] doc: document notmuch-dump header line David Bremner
2016-06-05 22:23                     ` David Bremner
2016-06-06  6:38                       ` Tomi Ollila
2016-06-07 10:55                       ` David Bremner
2016-06-01  4:38       ` [RFC2 Patch 5/5] lib: iterator API for message properties Tomi Ollila

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://notmuchmail.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1463927339-5441-3-git-send-email-david@tethera.net \
    --to=david@tethera.net \
    --cc=notmuch@notmuchmail.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).