mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-11-24 20:08:10 +01:00
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:
parent
8b03ee1d5a
commit
b8bb6d7964
6 changed files with 331 additions and 2 deletions
|
@ -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
16
lib/message-private.h
Normal 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
108
lib/message-property.cc
Normal 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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
84
test/T610-message-property.sh
Executable 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
|
Loading…
Reference in a new issue