Re: [feature request] emacs: use `notmuch insert` for FCC
[notmuch-archives.git] / 27 / 5031057fa78d8a7d51935e18dcb7196c2f3b58
1 Return-Path: <bremner@tesseract.cs.unb.ca>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5  by arlo.cworth.org (Postfix) with ESMTP id C86436DE035F\r
6  for <notmuch@notmuchmail.org>; Sat,  6 Aug 2016 06:53:01 -0700 (PDT)\r
7 X-Virus-Scanned: Debian amavisd-new at cworth.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: -0.004\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=-0.004 tagged_above=-999 required=5\r
12  tests=[AWL=-0.005, HEADER_FROM_DIFFERENT_DOMAINS=0.001]\r
13  autolearn=disabled\r
14 Received: from arlo.cworth.org ([127.0.0.1])\r
15  by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024)\r
16  with ESMTP id O3vFkxT0BjWZ for <notmuch@notmuchmail.org>;\r
17  Sat,  6 Aug 2016 06:52:52 -0700 (PDT)\r
18 Received: from fethera.tethera.net (fethera.tethera.net [198.245.60.197])\r
19  by arlo.cworth.org (Postfix) with ESMTPS id F2EEB6DE012F\r
20  for <notmuch@notmuchmail.org>; Sat,  6 Aug 2016 06:52:51 -0700 (PDT)\r
21 Received: from remotemail by fethera.tethera.net with local (Exim 4.84_2)\r
22  (envelope-from <bremner@tesseract.cs.unb.ca>)\r
23  id 1bW22E-0007Dj-UJ; Sat, 06 Aug 2016 09:53:06 -0400\r
24 Received: (nullmailer pid 4129 invoked by uid 1000);\r
25  Sat, 06 Aug 2016 13:52:44 -0000\r
26 From: David Bremner <david@tethera.net>\r
27 To: notmuch@notmuchmail.org\r
28 Subject: [PATCH 3/9] lib: basic message-property API\r
29 Date: Sat,  6 Aug 2016 22:52:33 +0900\r
30 Message-Id: <1470491559-3946-4-git-send-email-david@tethera.net>\r
31 X-Mailer: git-send-email 2.8.1\r
32 In-Reply-To: <1470491559-3946-1-git-send-email-david@tethera.net>\r
33 References: <1470491559-3946-1-git-send-email-david@tethera.net>\r
34 MIME-Version: 1.0\r
35 Content-Type: text/plain; charset=UTF-8\r
36 Content-Transfer-Encoding: 8bit\r
37 X-BeenThere: notmuch@notmuchmail.org\r
38 X-Mailman-Version: 2.1.20\r
39 Precedence: list\r
40 List-Id: "Use and development of the notmuch mail system."\r
41  <notmuch.notmuchmail.org>\r
42 List-Unsubscribe: <https://notmuchmail.org/mailman/options/notmuch>,\r
43  <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
44 List-Archive: <http://notmuchmail.org/pipermail/notmuch/>\r
45 List-Post: <mailto:notmuch@notmuchmail.org>\r
46 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
47 List-Subscribe: <https://notmuchmail.org/mailman/listinfo/notmuch>,\r
48  <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
49 X-List-Received-Date: Sat, 06 Aug 2016 13:53:01 -0000\r
50 \r
51 Initially, support get, set and removal of single key/value pair, as\r
52 well as removing all properties.\r
53 ---\r
54  lib/message-private.h         |  16 +++++++\r
55  lib/message-property.cc       | 108 ++++++++++++++++++++++++++++++++++++++++++\r
56  lib/message.cc                |  52 +++++++++++++++++++-\r
57  lib/notmuch.h                 |  72 ++++++++++++++++++++++++++++\r
58  test/T610-message-property.sh |  84 ++++++++++++++++++++++++++++++++\r
59  5 files changed, 330 insertions(+), 2 deletions(-)\r
60  create mode 100644 lib/message-private.h\r
61  create mode 100644 lib/message-property.cc\r
62  create mode 100755 test/T610-message-property.sh\r
63 \r
64 diff --git a/lib/message-private.h b/lib/message-private.h\r
65 new file mode 100644\r
66 index 0000000..7419925\r
67 --- /dev/null\r
68 +++ b/lib/message-private.h\r
69 @@ -0,0 +1,16 @@\r
70 +#ifndef MESSAGE_PRIVATE_H\r
71 +#define MESSAGE_PRIVATE_H\r
72 +\r
73 +notmuch_string_map_t *\r
74 +_notmuch_message_property_map (notmuch_message_t *message);\r
75 +\r
76 +notmuch_bool_t\r
77 +_notmuch_message_frozen (notmuch_message_t *message);\r
78 +\r
79 +void\r
80 +_notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix);\r
81 +\r
82 +void\r
83 +_notmuch_message_invalidate_metadata (notmuch_message_t *message,  const char *prefix_name);\r
84 +\r
85 +#endif\r
86 diff --git a/lib/message-property.cc b/lib/message-property.cc\r
87 new file mode 100644\r
88 index 0000000..1f04a20\r
89 --- /dev/null\r
90 +++ b/lib/message-property.cc\r
91 @@ -0,0 +1,108 @@\r
92 +/* message-property.cc - Properties are like tags, but (key,value) pairs.\r
93 + * keys are allowed to repeat.\r
94 + *\r
95 + * This file is part of notmuch.\r
96 + *\r
97 + * Copyright © 2016 David Bremner\r
98 + *\r
99 + * This program is free software: you can redistribute it and/or modify\r
100 + * it under the terms of the GNU General Public License as published by\r
101 + * the Free Software Foundation, either version 3 of the License, or\r
102 + * (at your option) any later version.\r
103 + *\r
104 + * This program is distributed in the hope that it will be useful,\r
105 + * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
106 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
107 + * GNU General Public License for more details.\r
108 + *\r
109 + * You should have received a copy of the GNU General Public License\r
110 + * along with this program.  If not, see http://www.gnu.org/licenses/ .\r
111 + *\r
112 + * Author: David Bremner <david@tethera.net>\r
113 + */\r
114 +\r
115 +#include "notmuch-private.h"\r
116 +#include "database-private.h"\r
117 +#include "message-private.h"\r
118 +\r
119 +notmuch_status_t\r
120 +notmuch_message_get_property (notmuch_message_t *message, const char *key, const char **value)\r
121 +{\r
122 +    if (! value)\r
123 +       return NOTMUCH_STATUS_NULL_POINTER;\r
124 +\r
125 +    *value = _notmuch_string_map_get (_notmuch_message_property_map (message), key);\r
126 +\r
127 +    return NOTMUCH_STATUS_SUCCESS;\r
128 +}\r
129 +\r
130 +static notmuch_status_t\r
131 +_notmuch_message_modify_property (notmuch_message_t *message, const char *key, const char *value,\r
132 +                                 notmuch_bool_t delete_it)\r
133 +{\r
134 +    notmuch_private_status_t private_status;\r
135 +    notmuch_status_t status;\r
136 +    char *term = NULL;\r
137 +\r
138 +    status = _notmuch_database_ensure_writable (_notmuch_message_database (message));\r
139 +    if (status)\r
140 +       return status;\r
141 +\r
142 +    if (key == NULL || value == NULL)\r
143 +       return NOTMUCH_STATUS_NULL_POINTER;\r
144 +\r
145 +    if (index (key, '='))\r
146 +       return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;\r
147 +\r
148 +    term = talloc_asprintf (message, "%s=%s", key, value);\r
149 +\r
150 +    if (delete_it)\r
151 +       private_status = _notmuch_message_remove_term (message, "property", term);\r
152 +    else\r
153 +       private_status = _notmuch_message_add_term (message, "property", term);\r
154 +\r
155 +    if (private_status)\r
156 +       return COERCE_STATUS (private_status,\r
157 +                             "Unhandled error modifying message property");\r
158 +    if (! _notmuch_message_frozen (message))\r
159 +       _notmuch_message_sync (message);\r
160 +\r
161 +    if (term)\r
162 +       talloc_free (term);\r
163 +\r
164 +    return NOTMUCH_STATUS_SUCCESS;\r
165 +}\r
166 +\r
167 +notmuch_status_t\r
168 +notmuch_message_add_property (notmuch_message_t *message, const char *key, const char *value)\r
169 +{\r
170 +    return _notmuch_message_modify_property (message, key, value, FALSE);\r
171 +}\r
172 +\r
173 +notmuch_status_t\r
174 +notmuch_message_remove_property (notmuch_message_t *message, const char *key, const char *value)\r
175 +{\r
176 +    return _notmuch_message_modify_property (message, key, value, TRUE);\r
177 +}\r
178 +\r
179 +notmuch_status_t\r
180 +notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key)\r
181 +{\r
182 +    notmuch_status_t status;\r
183 +    const char * term_prefix;\r
184 +\r
185 +    status = _notmuch_database_ensure_writable (_notmuch_message_database (message));\r
186 +    if (status)\r
187 +       return status;\r
188 +\r
189 +    _notmuch_message_invalidate_metadata (message, "property");\r
190 +    if (key)\r
191 +       term_prefix = talloc_asprintf (message, "%s%s=", _find_prefix ("property"), key);\r
192 +    else\r
193 +       term_prefix = _find_prefix ("property");\r
194 +\r
195 +    /* XXX better error reporting ? */\r
196 +    _notmuch_message_remove_terms (message, term_prefix);\r
197 +\r
198 +    return NOTMUCH_STATUS_SUCCESS;\r
199 +}\r
200 diff --git a/lib/message.cc b/lib/message.cc\r
201 index 63a8da5..9d3e807 100644\r
202 --- a/lib/message.cc\r
203 +++ b/lib/message.cc\r
204 @@ -20,6 +20,7 @@\r
205  \r
206  #include "notmuch-private.h"\r
207  #include "database-private.h"\r
208 +#include "message-private.h"\r
209  \r
210  #include <stdint.h>\r
211  \r
212 @@ -395,7 +396,7 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message)\r
213         message->in_reply_to = talloc_strdup (message, "");\r
214  }\r
215  \r
216 -static void\r
217 +void\r
218  _notmuch_message_invalidate_metadata (notmuch_message_t *message,\r
219                                       const char *prefix_name)\r
220  {\r
221 @@ -552,7 +553,7 @@ notmuch_message_get_replies (notmuch_message_t *message)\r
222      return _notmuch_messages_create (message->replies);\r
223  }\r
224  \r
225 -static void\r
226 +void\r
227  _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)\r
228  {\r
229      Xapian::TermIterator i;\r
230 @@ -1799,3 +1800,50 @@ _notmuch_message_database (notmuch_message_t *message)\r
231  {\r
232      return message->notmuch;\r
233  }\r
234 +\r
235 +void\r
236 +_notmuch_message_ensure_property_map (notmuch_message_t *message)\r
237 +{\r
238 +    notmuch_string_node_t *node;\r
239 +\r
240 +    if (message->property_map)\r
241 +       return;\r
242 +\r
243 +    if (!message->property_term_list)\r
244 +       _notmuch_message_ensure_metadata (message);\r
245 +\r
246 +    message->property_map = _notmuch_string_map_create (message);\r
247 +\r
248 +    for (node = message->property_term_list->head; node; node = node->next) {\r
249 +       const char *key;\r
250 +       char *value;\r
251 +\r
252 +       value = index(node->string, '=');\r
253 +       if (!value)\r
254 +           INTERNAL_ERROR ("malformed property term");\r
255 +\r
256 +       *value = '\0';\r
257 +       value++;\r
258 +       key = node->string;\r
259 +\r
260 +       _notmuch_string_map_append (message->property_map, key, value);\r
261 +\r
262 +    }\r
263 +\r
264 +    talloc_free (message->property_term_list);\r
265 +    message->property_term_list = NULL;\r
266 +}\r
267 +\r
268 +notmuch_string_map_t *\r
269 +_notmuch_message_property_map (notmuch_message_t *message)\r
270 +{\r
271 +    _notmuch_message_ensure_property_map (message);\r
272 +\r
273 +    return message->property_map;\r
274 +}\r
275 +\r
276 +notmuch_bool_t\r
277 +_notmuch_message_frozen (notmuch_message_t *message)\r
278 +{\r
279 +    return message->frozen;\r
280 +}\r
281 diff --git a/lib/notmuch.h b/lib/notmuch.h\r
282 index 2faa146..b304dca 100644\r
283 --- a/lib/notmuch.h\r
284 +++ b/lib/notmuch.h\r
285 @@ -180,6 +180,11 @@ typedef enum _notmuch_status {\r
286       */\r
287      NOTMUCH_STATUS_PATH_ERROR,\r
288      /**\r
289 +     * One of the arguments violates the preconditions for the\r
290 +     * function, in a way not covered by a more specific argument.\r
291 +     */\r
292 +    NOTMUCH_STATUS_ILLEGAL_ARGUMENT,\r
293 +    /**\r
294       * Not an actual status value. Just a way to find out how many\r
295       * valid status values there are.\r
296       */\r
297 @@ -1654,6 +1659,73 @@ void\r
298  notmuch_message_destroy (notmuch_message_t *message);\r
299  \r
300  /**\r
301 + * @name Message Properties\r
302 + *\r
303 + * This interface provides the ability to attach arbitrary (key,value)\r
304 + * string pairs to a message, to remove such pairs, and to iterate\r
305 + * over them.  The caller should take some care as to what keys they\r
306 + * add or delete values for, as other subsystems or extensions may\r
307 + * depend on these properties.\r
308 + *\r
309 + */\r
310 +/**@{*/\r
311 +/**\r
312 + * Retrieve the value for a single property key\r
313 + *\r
314 + * *value* is set to a string owned by the message or NULL if there is\r
315 + * no such key. In the case of multiple values for the given key, the\r
316 + * first one is retrieved.\r
317 + *\r
318 + * @returns\r
319 + * - NOTMUCH_STATUS_NULL_POINTER: *value* may not be NULL.\r
320 + * - NOTMUCH_STATUS_SUCCESS: No error occured.\r
321 +\r
322 + */\r
323 +notmuch_status_t\r
324 +notmuch_message_get_property (notmuch_message_t *message, const char *key, const char **value);\r
325 +\r
326 +/**\r
327 + * Add a (key,value) pair to a message\r
328 + *\r
329 + * @returns\r
330 + * - NOTMUCH_STATUS_ILLEGAL_ARGUMENT: *key* may not contain an '=' character.\r
331 + * - NOTMUCH_STATUS_NULL_POINTER: Neither *key* nor *value* may be NULL.\r
332 + * - NOTMUCH_STATUS_SUCCESS: No error occured.\r
333 + */\r
334 +notmuch_status_t\r
335 +notmuch_message_add_property (notmuch_message_t *message, const char *key, const char *value);\r
336 +\r
337 +/**\r
338 + * Remove a (key,value) pair from a message.\r
339 + *\r
340 + * It is not an error to remove a non-existant (key,value) pair\r
341 + *\r
342 + * @returns\r
343 + * - NOTMUCH_STATUS_ILLEGAL_ARGUMENT: *key* may not contain an '=' character.\r
344 + * - NOTMUCH_STATUS_NULL_POINTER: Neither *key* nor *value* may be NULL.\r
345 + * - NOTMUCH_STATUS_SUCCESS: No error occured.\r
346 + */\r
347 +notmuch_status_t\r
348 +notmuch_message_remove_property (notmuch_message_t *message, const char *key, const char *value);\r
349 +\r
350 +/**\r
351 + * Remove all (key,value) pairs from the given message.\r
352 + *\r
353 + * @param[in,out] message  message to operate on.\r
354 + * @param[in]     key      key to delete properties for. If NULL, delete\r
355 + *                        properties for all keys\r
356 + * @returns\r
357 + * - NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in\r
358 + *   read-only mode so message cannot be modified.\r
359 + * - NOTMUCH_STATUS_SUCCESS: No error occured.\r
360 + *\r
361 + */\r
362 +notmuch_status_t\r
363 +notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key);\r
364 +\r
365 +/**@}*/\r
366 +\r
367 +/**\r
368   * Is the given 'tags' iterator pointing at a valid tag.\r
369   *\r
370   * When this function returns TRUE, notmuch_tags_get will return a\r
371 diff --git a/test/T610-message-property.sh b/test/T610-message-property.sh\r
372 new file mode 100755\r
373 index 0000000..0217950\r
374 --- /dev/null\r
375 +++ b/test/T610-message-property.sh\r
376 @@ -0,0 +1,84 @@\r
377 +#!/usr/bin/env bash\r
378 +test_description="message property API"\r
379 +\r
380 +. ./test-lib.sh || exit 1\r
381 +\r
382 +add_email_corpus\r
383 +\r
384 +cat <<EOF > c_head\r
385 +#include <stdio.h>\r
386 +#include <string.h>\r
387 +#include <stdlib.h>\r
388 +#include <notmuch-test.h>\r
389 +\r
390 +int main (int argc, char** argv)\r
391 +{\r
392 +   notmuch_database_t *db;\r
393 +   notmuch_message_t *message = NULL;\r
394 +   const char *val;\r
395 +   notmuch_status_t stat;\r
396 +\r
397 +   EXPECT0(notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db));\r
398 +   EXPECT0(notmuch_database_find_message(db, "4EFC743A.3060609@april.org", &message));\r
399 +   if (message == NULL) {\r
400 +       fprintf (stderr, "unable to find message");\r
401 +       exit (1);\r
402 +   }\r
403 +EOF\r
404 +\r
405 +cat <<EOF > c_tail\r
406 +   EXPECT0(notmuch_database_destroy(db));\r
407 +}\r
408 +EOF\r
409 +\r
410 +test_begin_subtest "notmuch_message_{add,get,remove}_property"\r
411 +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}\r
412 +{\r
413 +   EXPECT0(notmuch_message_add_property (message, "testkey1", "testvalue1"));\r
414 +   EXPECT0(notmuch_message_get_property (message, "testkey1", &val));\r
415 +   printf("testkey1[1] = %s\n", val);\r
416 +   EXPECT0(notmuch_message_add_property (message, "testkey2", "this value has spaces and = sign"));\r
417 +   EXPECT0(notmuch_message_get_property (message, "testkey1", &val));\r
418 +   printf("testkey1[2] = %s\n", val);\r
419 +   EXPECT0(notmuch_message_get_property (message, "testkey1", &val));\r
420 +\r
421 +   EXPECT0(notmuch_message_get_property (message, "testkey2", &val));\r
422 +   printf("testkey2 = %s\n", val);\r
423 +\r
424 +   /* Add second value for key */\r
425 +   EXPECT0(notmuch_message_add_property (message, "testkey2", "zztestvalue3"));\r
426 +   EXPECT0(notmuch_message_get_property (message, "testkey2", &val));\r
427 +   printf("testkey2 = %s\n", val);\r
428 +\r
429 +   /* remove first value for key */\r
430 +   EXPECT0(notmuch_message_remove_property (message, "testkey2", "this value has spaces and = sign"));\r
431 +   EXPECT0(notmuch_message_get_property (message, "testkey2", &val));\r
432 +   printf("testkey2 = %s\n", val);\r
433 +\r
434 +   /* remove non-existant value for key */\r
435 +   EXPECT0(notmuch_message_remove_property (message, "testkey2", "this value has spaces and = sign"));\r
436 +   EXPECT0(notmuch_message_get_property (message, "testkey2", &val));\r
437 +   printf("testkey2 = %s\n", val);\r
438 +\r
439 +   /* remove only value for key */\r
440 +   EXPECT0(notmuch_message_remove_property (message, "testkey2", "zztestvalue3"));\r
441 +   EXPECT0(notmuch_message_get_property (message, "testkey2", &val));\r
442 +   printf("testkey2 = %s\n", val == NULL ? "NULL" : val);\r
443 +}\r
444 +EOF\r
445 +cat <<'EOF' >EXPECTED\r
446 +== stdout ==\r
447 +testkey1[1] = testvalue1\r
448 +testkey1[2] = testvalue1\r
449 +testkey2 = this value has spaces and = sign\r
450 +testkey2 = this value has spaces and = sign\r
451 +testkey2 = zztestvalue3\r
452 +testkey2 = zztestvalue3\r
453 +testkey2 = NULL\r
454 +== stderr ==\r
455 +EOF\r
456 +test_expect_equal_file EXPECTED OUTPUT\r
457 +\r
458 +\r
459 +\r
460 +test_done\r
461 -- \r
462 2.8.1\r
463 \r