lib: basic message-property API

Initially, support get, set and removal of single key/value pair, as
well as removing all properties.
This commit is contained in:
David Bremner 2016-06-12 22:05:50 -03:00
parent 8b03ee1d5a
commit b8bb6d7964
6 changed files with 331 additions and 2 deletions

View file

@ -49,6 +49,7 @@ libnotmuch_cxx_srcs = \
$(dir)/directory.cc \ $(dir)/directory.cc \
$(dir)/index.cc \ $(dir)/index.cc \
$(dir)/message.cc \ $(dir)/message.cc \
$(dir)/message-property.cc \
$(dir)/query.cc \ $(dir)/query.cc \
$(dir)/query-fp.cc \ $(dir)/query-fp.cc \
$(dir)/config.cc \ $(dir)/config.cc \

16
lib/message-private.h Normal file
View file

@ -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

108
lib/message-property.cc Normal file
View file

@ -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 <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;
}

View file

@ -20,6 +20,7 @@
#include "notmuch-private.h" #include "notmuch-private.h"
#include "database-private.h" #include "database-private.h"
#include "message-private.h"
#include <stdint.h> #include <stdint.h>
@ -395,7 +396,7 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message)
message->in_reply_to = talloc_strdup (message, ""); message->in_reply_to = talloc_strdup (message, "");
} }
static void void
_notmuch_message_invalidate_metadata (notmuch_message_t *message, _notmuch_message_invalidate_metadata (notmuch_message_t *message,
const char *prefix_name) const char *prefix_name)
{ {
@ -552,7 +553,7 @@ notmuch_message_get_replies (notmuch_message_t *message)
return _notmuch_messages_create (message->replies); return _notmuch_messages_create (message->replies);
} }
static void void
_notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix) _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
{ {
Xapian::TermIterator i; Xapian::TermIterator i;
@ -1799,3 +1800,50 @@ _notmuch_message_database (notmuch_message_t *message)
{ {
return message->notmuch; 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;
}

View file

@ -179,6 +179,11 @@ typedef enum _notmuch_status {
* passed to a function expecting an absolute path. * passed to a function expecting an absolute path.
*/ */
NOTMUCH_STATUS_PATH_ERROR, 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 * Not an actual status value. Just a way to find out how many
* valid status values there are. * valid status values there are.
@ -1653,6 +1658,73 @@ notmuch_message_thaw (notmuch_message_t *message);
void void
notmuch_message_destroy (notmuch_message_t *message); 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. * Is the given 'tags' iterator pointing at a valid tag.
* *

84
test/T610-message-property.sh Executable file
View file

@ -0,0 +1,84 @@
#!/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