Re: [PATCH v4 16/16] add "notmuch reindex" subcommand
[notmuch-archives.git] / 2f / d9f6d732476fe854aa8add9b173c42c7d8a0ec
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 olra.theworths.org (Postfix) with ESMTP id 61BC2431FBD\r
6         for <notmuch@notmuchmail.org>; Thu,  6 Dec 2012 17:27:30 -0800 (PST)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: 0\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=0 tagged_above=-999 required=5 tests=[none]\r
12         autolearn=disabled\r
13 Received: from olra.theworths.org ([127.0.0.1])\r
14         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
15         with ESMTP id 7jj86OJjMRhL for <notmuch@notmuchmail.org>;\r
16         Thu,  6 Dec 2012 17:27:27 -0800 (PST)\r
17 Received: from tesseract.cs.unb.ca (tesseract.cs.unb.ca [131.202.240.238])\r
18         (using TLSv1 with cipher AES256-SHA (256/256 bits))\r
19         (No client certificate requested)\r
20         by olra.theworths.org (Postfix) with ESMTPS id B9B7F429E29\r
21         for <notmuch@notmuchmail.org>; Thu,  6 Dec 2012 17:27:13 -0800 (PST)\r
22 Received: from fctnnbsc30w-142167090129.dhcp-dynamic.fibreop.nb.bellaliant.net\r
23         ([142.167.90.129] helo=zancas.localnet)\r
24         by tesseract.cs.unb.ca with esmtpsa\r
25         (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.72)\r
26         (envelope-from <bremner@tethera.net>)\r
27         id 1Tgmie-0003Nt-Jg; Thu, 06 Dec 2012 21:27:13 -0400\r
28 Received: from bremner by zancas.localnet with local (Exim 4.80)\r
29         (envelope-from <bremner@tethera.net>)\r
30         id 1TgmiZ-0004ka-5f; Thu, 06 Dec 2012 21:27:07 -0400\r
31 From: david@tethera.net\r
32 To: notmuch@notmuchmail.org\r
33 Subject: [Patch v3b 4/9] tag-util.[ch]: New files for common tagging routines\r
34 Date: Thu,  6 Dec 2012 21:26:42 -0400\r
35 Message-Id: <1354843607-17980-5-git-send-email-david@tethera.net>\r
36 X-Mailer: git-send-email 1.7.10.4\r
37 In-Reply-To: <1354843607-17980-1-git-send-email-david@tethera.net>\r
38 References: <1354843607-17980-1-git-send-email-david@tethera.net>\r
39 X-Spam_bar: -\r
40 Cc: David Bremner <bremner@debian.org>\r
41 X-BeenThere: notmuch@notmuchmail.org\r
42 X-Mailman-Version: 2.1.13\r
43 Precedence: list\r
44 List-Id: "Use and development of the notmuch mail system."\r
45         <notmuch.notmuchmail.org>\r
46 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
47         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
48 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
49 List-Post: <mailto:notmuch@notmuchmail.org>\r
50 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
51 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
52         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
53 X-List-Received-Date: Fri, 07 Dec 2012 01:27:30 -0000\r
54 \r
55 From: David Bremner <bremner@debian.org>\r
56 \r
57 These are meant to be shared between notmuch-tag and notmuch-restore.\r
58 \r
59 The bulk of the routines implement a "tag operation list" abstract\r
60 data type act as a structured representation of a set of tag\r
61 operations (typically coming from a single tag command or line of\r
62 input).\r
63 ---\r
64  Makefile.local |    1 +\r
65  tag-util.c     |  278 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\r
66  tag-util.h     |  119 ++++++++++++++++++++++++\r
67  3 files changed, 398 insertions(+)\r
68  create mode 100644 tag-util.c\r
69  create mode 100644 tag-util.h\r
70 \r
71 diff --git a/Makefile.local b/Makefile.local\r
72 index 2b91946..854867d 100644\r
73 --- a/Makefile.local\r
74 +++ b/Makefile.local\r
75 @@ -274,6 +274,7 @@ notmuch_client_srcs =               \\r
76         query-string.c          \\r
77         mime-node.c             \\r
78         crypto.c                \\r
79 +       tag-util.c\r
80  \r
81  notmuch_client_modules = $(notmuch_client_srcs:.c=.o)\r
82  \r
83 diff --git a/tag-util.c b/tag-util.c\r
84 new file mode 100644\r
85 index 0000000..932ee7f\r
86 --- /dev/null\r
87 +++ b/tag-util.c\r
88 @@ -0,0 +1,278 @@\r
89 +#include <assert.h>\r
90 +#include "string-util.h"\r
91 +#include "tag-util.h"\r
92 +#include "hex-escape.h"\r
93 +\r
94 +#define TAG_OP_LIST_INITIAL_SIZE 10\r
95 +\r
96 +struct _tag_operation_t {\r
97 +    const char *tag;\r
98 +    notmuch_bool_t remove;\r
99 +};\r
100 +\r
101 +struct _tag_op_list_t {\r
102 +    tag_operation_t *ops;\r
103 +    size_t count;\r
104 +    size_t size;\r
105 +};\r
106 +\r
107 +int\r
108 +parse_tag_line (void *ctx, char *line,\r
109 +               tag_op_flag_t flags,\r
110 +               char **query_string,\r
111 +               tag_op_list_t *tag_ops)\r
112 +{\r
113 +    char *tok = line;\r
114 +    size_t tok_len = 0;\r
115 +    char *line_for_error = talloc_strdup (ctx, line);\r
116 +    int ret = 0;\r
117 +\r
118 +    chomp_newline (line);\r
119 +\r
120 +    /* remove leading space */\r
121 +    while (*tok == ' ' || *tok == '\t')\r
122 +       tok++;\r
123 +\r
124 +    /* Skip empty and comment lines. */\r
125 +    if (*tok == '\0' || *tok == '#') {\r
126 +       ret = 1;\r
127 +       goto DONE;\r
128 +    }\r
129 +\r
130 +    tag_op_list_reset (tag_ops);\r
131 +\r
132 +    /* Parse tags. */\r
133 +    while ((tok = strtok_len (tok + tok_len, " ", &tok_len)) != NULL) {\r
134 +       notmuch_bool_t remove;\r
135 +       char *tag;\r
136 +\r
137 +       /* Optional explicit end of tags marker. */\r
138 +       if (tok_len == 2 && strncmp (tok, "--", tok_len) == 0) {\r
139 +           tok = strtok_len (tok + tok_len, " ", &tok_len);\r
140 +           break;\r
141 +       }\r
142 +\r
143 +       /* Implicit end of tags. */\r
144 +       if (*tok != '-' && *tok != '+')\r
145 +           break;\r
146 +\r
147 +       /* If tag is terminated by NUL, there's no query string. */\r
148 +       if (*(tok + tok_len) == '\0') {\r
149 +           fprintf (stderr, "no query string: %s\n", line_for_error);\r
150 +           ret = 1;\r
151 +           goto DONE;\r
152 +       }\r
153 +\r
154 +       /* Terminate, and start next token after terminator. */\r
155 +       *(tok + tok_len++) = '\0';\r
156 +\r
157 +       remove = (*tok == '-');\r
158 +       tag = tok + 1;\r
159 +\r
160 +       /* Maybe refuse empty tags. */\r
161 +       if (! (flags & TAG_FLAG_BE_GENEROUS) && *tag == '\0') {\r
162 +           fprintf (stderr, "Error: empty tag: %s\n", line_for_error);\r
163 +           goto DONE;\r
164 +       }\r
165 +\r
166 +       /* Decode tag. */\r
167 +       if (hex_decode_inplace (tag) != HEX_SUCCESS) {\r
168 +           fprintf (stderr, "Hex decoding of tag %s failed\n",\r
169 +                    tag);\r
170 +           ret = 1;\r
171 +           goto DONE;\r
172 +       }\r
173 +\r
174 +       if (tag_op_list_append (ctx, tag_ops, tag, remove)) {\r
175 +           ret = -1;\r
176 +           goto DONE;\r
177 +       }\r
178 +    }\r
179 +\r
180 +    if (tok == NULL) {\r
181 +       fprintf (stderr, "Warning: Ignoring invalid input line: %s\n",\r
182 +                line_for_error);\r
183 +       ret = 1;\r
184 +       goto DONE;\r
185 +    }\r
186 +\r
187 +    /* tok now points to the query string */\r
188 +    if (hex_decode_inplace (tok) != HEX_SUCCESS) {\r
189 +       fprintf (stderr, "Hex decoding of query %s failed\n",\r
190 +                tok);\r
191 +       ret = 1;\r
192 +       goto DONE;\r
193 +    }\r
194 +\r
195 +    *query_string = tok;\r
196 +  DONE:\r
197 +    talloc_free (line_for_error);\r
198 +    return ret;\r
199 +}\r
200 +\r
201 +static inline void\r
202 +message_error (notmuch_message_t *message,\r
203 +              notmuch_status_t status,\r
204 +              const char *format, ...)\r
205 +{\r
206 +    va_list va_args;\r
207 +\r
208 +    va_start (va_args, format);\r
209 +\r
210 +    vfprintf (stderr, format, va_args);\r
211 +    fprintf (stderr, "Message-ID: %s\n", notmuch_message_get_message_id (message));\r
212 +    fprintf (stderr, "Status: %s\n", notmuch_status_to_string (status));\r
213 +}\r
214 +\r
215 +notmuch_status_t\r
216 +tag_op_list_apply (notmuch_message_t *message,\r
217 +                  tag_op_list_t *list,\r
218 +                  tag_op_flag_t flags)\r
219 +{\r
220 +    size_t i;\r
221 +    notmuch_status_t status = 0;\r
222 +    tag_operation_t *tag_ops = list->ops;\r
223 +\r
224 +    status = notmuch_message_freeze (message);\r
225 +    if (status) {\r
226 +       message_error (message, status, "freezing message");\r
227 +       return status;\r
228 +    }\r
229 +\r
230 +    if (flags & TAG_FLAG_REMOVE_ALL) {\r
231 +       status = notmuch_message_remove_all_tags (message);\r
232 +       if (status) {\r
233 +           message_error (message, status, "removing all tags");\r
234 +           return status;\r
235 +       }\r
236 +    }\r
237 +\r
238 +    for (i = 0; i < list->count; i++) {\r
239 +       if (tag_ops[i].remove) {\r
240 +           status = notmuch_message_remove_tag (message, tag_ops[i].tag);\r
241 +           if (status) {\r
242 +               message_error (message, status, "removing tag %s", tag_ops[i].tag);\r
243 +               return status;\r
244 +           }\r
245 +       } else {\r
246 +           status = notmuch_message_add_tag (message, tag_ops[i].tag);\r
247 +           if (status) {\r
248 +               message_error (message, status, "adding tag %s", tag_ops[i].tag);\r
249 +               return status;\r
250 +           }\r
251 +\r
252 +       }\r
253 +    }\r
254 +\r
255 +    status = notmuch_message_thaw (message);\r
256 +    if (status) {\r
257 +       message_error (message, status, "thawing message");\r
258 +       return status;\r
259 +    }\r
260 +\r
261 +\r
262 +    if (flags & TAG_FLAG_MAILDIR_SYNC) {\r
263 +       status = notmuch_message_tags_to_maildir_flags (message);\r
264 +       if (status) {\r
265 +           message_error (message, status, "synching tags to maildir");\r
266 +           return status;\r
267 +       }\r
268 +    }\r
269 +\r
270 +    return NOTMUCH_STATUS_SUCCESS;\r
271 +\r
272 +}\r
273 +\r
274 +\r
275 +/* Array of tagging operations (add or remove.  Size will be increased\r
276 + * as necessary. */\r
277 +\r
278 +tag_op_list_t *\r
279 +tag_op_list_create (void *ctx)\r
280 +{\r
281 +    tag_op_list_t *list;\r
282 +\r
283 +    list = talloc (ctx, tag_op_list_t);\r
284 +    if (list == NULL)\r
285 +       return NULL;\r
286 +\r
287 +    list->size = TAG_OP_LIST_INITIAL_SIZE;\r
288 +    list->count = 0;\r
289 +\r
290 +    list->ops = talloc_array (ctx, tag_operation_t, list->size);\r
291 +    if (list->ops == NULL)\r
292 +       return NULL;\r
293 +\r
294 +    return list;\r
295 +}\r
296 +\r
297 +\r
298 +int\r
299 +tag_op_list_append (void *ctx,\r
300 +                   tag_op_list_t *list,\r
301 +                   const char *tag,\r
302 +                   notmuch_bool_t remove)\r
303 +{\r
304 +    /* Make room if current array is full.  This should be a fairly\r
305 +     * rare case, considering the initial array size.\r
306 +     */\r
307 +\r
308 +    if (list->count == list->size) {\r
309 +       list->size *= 2;\r
310 +       list->ops = talloc_realloc (ctx, list->ops, tag_operation_t,\r
311 +                                   list->size);\r
312 +       if (list->ops == NULL) {\r
313 +           fprintf (stderr, "Out of memory.\n");\r
314 +           return 1;\r
315 +       }\r
316 +    }\r
317 +\r
318 +    /* add the new operation */\r
319 +\r
320 +    list->ops[list->count].tag = tag;\r
321 +    list->ops[list->count].remove = remove;\r
322 +    list->count++;\r
323 +    return 0;\r
324 +}\r
325 +\r
326 +/*\r
327 + *   Is the i'th tag operation a remove?\r
328 + */\r
329 +\r
330 +notmuch_bool_t\r
331 +tag_op_list_isremove (const tag_op_list_t *list, size_t i)\r
332 +{\r
333 +    assert (i < list->count);\r
334 +    return list->ops[i].remove;\r
335 +}\r
336 +\r
337 +/*\r
338 + * Reset a list to contain no operations\r
339 + */\r
340 +\r
341 +void\r
342 +tag_op_list_reset (tag_op_list_t *list)\r
343 +{\r
344 +    list->count = 0;\r
345 +}\r
346 +\r
347 +/*\r
348 + * Return the number of operations in a list\r
349 + */\r
350 +\r
351 +size_t\r
352 +tag_op_list_size (const tag_op_list_t *list)\r
353 +{\r
354 +    return list->count;\r
355 +}\r
356 +\r
357 +/*\r
358 + *   return the i'th tag in the list\r
359 + */\r
360 +\r
361 +const char *\r
362 +tag_op_list_tag (const tag_op_list_t *list, size_t i)\r
363 +{\r
364 +    assert (i < list->count);\r
365 +    return list->ops[i].tag;\r
366 +}\r
367 diff --git a/tag-util.h b/tag-util.h\r
368 new file mode 100644\r
369 index 0000000..df05d72\r
370 --- /dev/null\r
371 +++ b/tag-util.h\r
372 @@ -0,0 +1,119 @@\r
373 +#ifndef _TAG_UTIL_H\r
374 +#define _TAG_UTIL_H\r
375 +\r
376 +#include "notmuch-client.h"\r
377 +\r
378 +typedef struct _tag_operation_t tag_operation_t;\r
379 +typedef struct _tag_op_list_t tag_op_list_t;\r
380 +\r
381 +/* Use powers of 2 */\r
382 +typedef enum {\r
383 +    TAG_FLAG_NONE = 0,\r
384 +\r
385 +    /* Operations are synced to maildir, if possible.\r
386 +     */\r
387 +    TAG_FLAG_MAILDIR_SYNC = (1 << 0),\r
388 +\r
389 +    /* Remove all tags from message before applying list.\r
390 +     */\r
391 +    TAG_FLAG_REMOVE_ALL = (1 << 1),\r
392 +\r
393 +    /* Don't try to avoid database operations. Useful when we\r
394 +     * know that message passed needs these operations.\r
395 +      */\r
396 +    TAG_FLAG_PRE_OPTIMIZED = (1 << 2),\r
397 +\r
398 +    /* Accept strange tags that might be user error;\r
399 +     * intended for use by notmuch-restore.\r
400 +     */\r
401 +    TAG_FLAG_BE_GENEROUS = (1 << 3)\r
402 +\r
403 +} tag_op_flag_t;\r
404 +\r
405 +/* Parse a string of the following format:\r
406 + *\r
407 + * +<tag>|-<tag> [...] [--] <search-terms>\r
408 + *\r
409 + * Each line is interpreted similarly to "notmuch tag" command line\r
410 + * arguments. The delimiter is one or more spaces ' '. Any characters\r
411 + * in <tag> and <search-terms> MAY be hex encoded with %NN where NN is\r
412 + * the hexadecimal value of the character. Any ' ' and '%' characters\r
413 + * in <tag> and <search-terms> MUST be hex encoded (using %20 and %25,\r
414 + * respectively). Any characters that are not part of <tag> or\r
415 + * <search-terms> MUST NOT be hex encoded.\r
416 + *\r
417 + * Leading and trailing space ' ' is ignored. Empty lines and lines\r
418 + * beginning with '#' are ignored.\r
419 + *\r
420 + * Returns: 0 for OK, 1 for skipped line, -1 for fatal(ish) error.\r
421 + *\r
422 + * Output Parameters:\r
423 + *     ops     contains a list of tag operations\r
424 + *     query_str the search terms.\r
425 + */\r
426 +int\r
427 +parse_tag_line (void *ctx, char *line,\r
428 +               tag_op_flag_t flags,\r
429 +               char **query_str, tag_op_list_t *ops);\r
430 +\r
431 +/*\r
432 + * Create an empty list of tag operations\r
433 + *\r
434 + * ctx is passed to talloc\r
435 + */\r
436 +\r
437 +tag_op_list_t\r
438 +*tag_op_list_create (void *ctx);\r
439 +\r
440 +/*\r
441 + * Add a tag operation (delete iff remove == TRUE) to a list.\r
442 + * The list is expanded as necessary.\r
443 + */\r
444 +\r
445 +int\r
446 +tag_op_list_append (void *ctx,\r
447 +                   tag_op_list_t *list,\r
448 +                   const char *tag,\r
449 +                   notmuch_bool_t remove);\r
450 +\r
451 +/*\r
452 + * Apply a list of tag operations, in order, to a given message.\r
453 + *\r
454 + * Flags can be bitwise ORed; see enum above for possibilies.\r
455 + */\r
456 +\r
457 +notmuch_status_t\r
458 +tag_op_list_apply (notmuch_message_t *message,\r
459 +                  tag_op_list_t *tag_ops,\r
460 +                  tag_op_flag_t flags);\r
461 +\r
462 +/*\r
463 + * Return the number of operations in a list\r
464 + */\r
465 +\r
466 +size_t\r
467 +tag_op_list_size (const tag_op_list_t *list);\r
468 +\r
469 +/*\r
470 + * Reset a list to contain no operations\r
471 + */\r
472 +\r
473 +void\r
474 +tag_op_list_reset (tag_op_list_t *list);\r
475 +\r
476 +\r
477 + /*\r
478 +  *   return the i'th tag in the list\r
479 +  */\r
480 +\r
481 +const char *\r
482 +tag_op_list_tag (const tag_op_list_t *list, size_t i);\r
483 +\r
484 +/*\r
485 + *   Is the i'th tag operation a remove?\r
486 + */\r
487 +\r
488 +notmuch_bool_t\r
489 +tag_op_list_isremove (const tag_op_list_t *list, size_t i);\r
490 +\r
491 +#endif\r
492 -- \r
493 1.7.10.4\r
494 \r