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