Re: [PATCH] add has: query prefix to search for specific properties
[notmuch-archives.git] / ea / f66301f1bc058608d800fed9c33fbbe8450b14
1 Return-Path: <novalazy@gmail.com>\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 18DF7429E30\r
6         for <notmuch@notmuchmail.org>; Sat, 22 Jun 2013 21:24:34 -0700 (PDT)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: -0.799\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=-0.799 tagged_above=-999 required=5\r
12         tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,\r
13         FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
14 Received: from olra.theworths.org ([127.0.0.1])\r
15         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
16         with ESMTP id Pxfk0fmgzURE for <notmuch@notmuchmail.org>;\r
17         Sat, 22 Jun 2013 21:24:24 -0700 (PDT)\r
18 Received: from mail-pb0-f47.google.com (mail-pb0-f47.google.com\r
19         [209.85.160.47]) (using TLSv1 with cipher RC4-SHA (128/128 bits))\r
20         (No client certificate requested)\r
21         by olra.theworths.org (Postfix) with ESMTPS id 7F007431FBD\r
22         for <notmuch@notmuchmail.org>; Sat, 22 Jun 2013 21:24:11 -0700 (PDT)\r
23 Received: by mail-pb0-f47.google.com with SMTP id rr13so9474791pbb.6\r
24         for <notmuch@notmuchmail.org>; Sat, 22 Jun 2013 21:24:10 -0700 (PDT)\r
25 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r
26         h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references\r
27         :mime-version:content-type:content-transfer-encoding;\r
28         bh=UCqEGTYOHAP7NjtDAsNmvHf6yp1XQ2I5tdxfDHe3djk=;\r
29         b=rmgMud4K9jEVQ/kcIQvnm5Z/+mEVckIfMmk7ZSzCCRTX+Q0si417jmy+th6uVsxR91\r
30         yyvlz+Q8qV9TUGF6JVSXxOTbeUrMCNXzgxtpz3lZbqDwcG54llk1YOgbqQJC+z3nIsj2\r
31         /DByAXXh9scYVoEqeWTz6CYda8JcEFvcEOzs0cy2SeitFt6+F5ucAI2V7QlgNvaNRyUg\r
32         H1f4dttWtEbHJOLgQ92e7q5Eyre+9k4AyB+iRs9eMQdGmk2RoFUaTuLNgsYshMxCcjDp\r
33         xOVheog0L325tFqlxEygmF1A1JLrjGCJfM2JKRIDZbCw/84aRBU220hKK77sUSxVex16\r
34         UpHQ==\r
35 X-Received: by 10.66.164.232 with SMTP id yt8mr22407973pab.21.1371961450776;\r
36         Sat, 22 Jun 2013 21:24:10 -0700 (PDT)\r
37 Received: from localhost (215.42.233.220.static.exetel.com.au.\r
38         [220.233.42.215]) by mx.google.com with ESMTPSA id\r
39         pb5sm11840797pbc.29.2013.06.22.21.24.08 for <multiple recipients>\r
40         (version=TLSv1.2 cipher=RC4-SHA bits=128/128);\r
41         Sat, 22 Jun 2013 21:24:10 -0700 (PDT)\r
42 From: Peter Wang <novalazy@gmail.com>\r
43 To: notmuch@notmuchmail.org\r
44 Subject: [PATCH v7 03/12] cli: add insert command\r
45 Date: Sun, 23 Jun 2013 14:23:56 +1000\r
46 Message-Id: <1371961445-15182-4-git-send-email-novalazy@gmail.com>\r
47 X-Mailer: git-send-email 1.7.12.1\r
48 In-Reply-To: <1371961445-15182-1-git-send-email-novalazy@gmail.com>\r
49 References: <1371961445-15182-1-git-send-email-novalazy@gmail.com>\r
50 MIME-Version: 1.0\r
51 Content-Type: text/plain; charset=UTF-8\r
52 Content-Transfer-Encoding: 8bit\r
53 X-BeenThere: notmuch@notmuchmail.org\r
54 X-Mailman-Version: 2.1.13\r
55 Precedence: list\r
56 List-Id: "Use and development of the notmuch mail system."\r
57         <notmuch.notmuchmail.org>\r
58 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
59         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
60 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
61 List-Post: <mailto:notmuch@notmuchmail.org>\r
62 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
63 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
64         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
65 X-List-Received-Date: Sun, 23 Jun 2013 04:24:34 -0000\r
66 \r
67 The notmuch insert command reads a message from standard input,\r
68 writes it to a Maildir folder, and then incorporates the message into\r
69 the notmuch database.  Essentially it moves the functionality of\r
70 notmuch-deliver into notmuch.\r
71 \r
72 Though it could be used as an alternative to notmuch new, the reason\r
73 I want this is to allow my notmuch frontend to add postponed or sent\r
74 messages to the mail store and notmuch database, without resorting to\r
75 another tool (e.g. notmuch-deliver) nor directly modifying the maildir.\r
76 ---\r
77  Makefile.local   |   1 +\r
78  notmuch-client.h |   3 +\r
79  notmuch-insert.c | 335 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\r
80  notmuch.c        |   2 +\r
81  4 files changed, 341 insertions(+)\r
82  create mode 100644 notmuch-insert.c\r
83 \r
84 diff --git a/Makefile.local b/Makefile.local\r
85 index 644623f..84043fe 100644\r
86 --- a/Makefile.local\r
87 +++ b/Makefile.local\r
88 @@ -261,6 +261,7 @@ notmuch_client_srcs =               \\r
89         notmuch-config.c        \\r
90         notmuch-count.c         \\r
91         notmuch-dump.c          \\r
92 +       notmuch-insert.c        \\r
93         notmuch-new.c           \\r
94         notmuch-reply.c         \\r
95         notmuch-restore.c       \\r
96 diff --git a/notmuch-client.h b/notmuch-client.h\r
97 index 4a3c7ac..dfe81e6 100644\r
98 --- a/notmuch-client.h\r
99 +++ b/notmuch-client.h\r
100 @@ -181,6 +181,9 @@ int\r
101  notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]);\r
102  \r
103  int\r
104 +notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]);\r
105 +\r
106 +int\r
107  notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]);\r
108  \r
109  int\r
110 diff --git a/notmuch-insert.c b/notmuch-insert.c\r
111 new file mode 100644\r
112 index 0000000..591189f\r
113 --- /dev/null\r
114 +++ b/notmuch-insert.c\r
115 @@ -0,0 +1,335 @@\r
116 +/* notmuch - Not much of an email program, (just index and search)\r
117 + *\r
118 + * Copyright © 2013 Peter Wang\r
119 + *\r
120 + * Based in part on notmuch-deliver\r
121 + * Copyright © 2010 Ali Polatel\r
122 + *\r
123 + * This program is free software: you can redistribute it and/or modify\r
124 + * it under the terms of the GNU General Public License as published by\r
125 + * the Free Software Foundation, either version 3 of the License, or\r
126 + * (at your option) any later version.\r
127 + *\r
128 + * This program is distributed in the hope that it will be useful,\r
129 + * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
130 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
131 + * GNU General Public License for more details.\r
132 + *\r
133 + * You should have received a copy of the GNU General Public License\r
134 + * along with this program.  If not, see http://www.gnu.org/licenses/ .\r
135 + *\r
136 + * Author: Peter Wang <novalazy@gmail.com>\r
137 + */\r
138 +\r
139 +#include "notmuch-client.h"\r
140 +#include "tag-util.h"\r
141 +\r
142 +#include <sys/types.h>\r
143 +#include <sys/stat.h>\r
144 +#include <fcntl.h>\r
145 +\r
146 +static volatile sig_atomic_t interrupted;\r
147 +\r
148 +static void\r
149 +handle_sigint (unused (int sig))\r
150 +{\r
151 +    static char msg[] = "Stopping...         \n";\r
152 +\r
153 +    /* This write is "opportunistic", so it's okay to ignore the\r
154 +     * result.  It is not required for correctness, and if it does\r
155 +     * fail or produce a short write, we want to get out of the signal\r
156 +     * handler as quickly as possible, not retry it. */\r
157 +    IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));\r
158 +    interrupted = 1;\r
159 +}\r
160 +\r
161 +/* Like gethostname but guarantees that a null-terminated hostname is\r
162 + * returned, even if it has to make one up. Invalid characters are\r
163 + * substituted such that the hostname can be used within a filename.\r
164 + */\r
165 +static void\r
166 +safe_gethostname (char *hostname, size_t len)\r
167 +{\r
168 +    char *p;\r
169 +\r
170 +    if (gethostname (hostname, len) == -1) {\r
171 +       strncpy (hostname, "unknown", len);\r
172 +    }\r
173 +    hostname[len - 1] = '\0';\r
174 +\r
175 +    for (p = hostname; *p != '\0'; p++) {\r
176 +       if (*p == '/' || *p == ':')\r
177 +           *p = '_';\r
178 +    }\r
179 +}\r
180 +\r
181 +/* Call fsync() on a directory path. */\r
182 +static notmuch_bool_t\r
183 +sync_dir (const char *dir)\r
184 +{\r
185 +    notmuch_bool_t ret;\r
186 +    int fd;\r
187 +\r
188 +    fd = open (dir, O_RDONLY);\r
189 +    if (fd == -1) {\r
190 +       fprintf (stderr, "Error: open() dir failed: %s\n", strerror (errno));\r
191 +       return FALSE;\r
192 +    }\r
193 +    ret = (fsync (fd) == 0);\r
194 +    if (! ret) {\r
195 +       fprintf (stderr, "Error: fsync() dir failed: %s\n", strerror (errno));\r
196 +    }\r
197 +    close (fd);\r
198 +    return ret;\r
199 +}\r
200 +\r
201 +/* Open a unique file in the 'tmp' sub-directory of dir.\r
202 + * Returns the file descriptor on success, or -1 on failure.\r
203 + * On success, file paths for the message in the 'tmp' and 'new'\r
204 + * directories are returned via tmppath and newpath,\r
205 + * and the path of the 'new' directory itself in newdir. */\r
206 +static int\r
207 +maildir_open_tmp_file (void *ctx, const char *dir,\r
208 +                      char **tmppath, char **newpath, char **newdir)\r
209 +{\r
210 +    pid_t pid;\r
211 +    char hostname[256];\r
212 +    struct timeval tv;\r
213 +    char *filename;\r
214 +    int fd = -1;\r
215 +\r
216 +    /* We follow the Dovecot file name generation algorithm. */\r
217 +    pid = getpid ();\r
218 +    safe_gethostname (hostname, sizeof (hostname));\r
219 +    do {\r
220 +       gettimeofday (&tv, NULL);\r
221 +       filename = talloc_asprintf (ctx, "%ld.M%ldP%d.%s",\r
222 +                                   tv.tv_sec, tv.tv_usec, pid, hostname);\r
223 +       if (! filename) {\r
224 +           fprintf (stderr, "Out of memory\n");\r
225 +           return -1;\r
226 +       }\r
227 +\r
228 +       *tmppath = talloc_asprintf (ctx, "%s/tmp/%s", dir, filename);\r
229 +       if (! *tmppath) {\r
230 +           fprintf (stderr, "Out of memory\n");\r
231 +           return -1;\r
232 +       }\r
233 +\r
234 +       fd = open (*tmppath, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);\r
235 +    } while (fd == -1 && errno == EEXIST);\r
236 +\r
237 +    if (fd == -1) {\r
238 +       fprintf (stderr, "Error: opening %s: %s\n", *tmppath, strerror (errno));\r
239 +       return -1;\r
240 +    }\r
241 +\r
242 +    *newdir = talloc_asprintf (ctx, "%s/new", dir);\r
243 +    *newpath = talloc_asprintf (ctx, "%s/new/%s", dir, filename);\r
244 +    if (! *newdir || ! *newpath) {\r
245 +       fprintf (stderr, "Out of memory\n");\r
246 +       close (fd);\r
247 +       unlink (*tmppath);\r
248 +       return -1;\r
249 +    }\r
250 +\r
251 +    talloc_free (filename);\r
252 +\r
253 +    return fd;\r
254 +}\r
255 +\r
256 +/* Copy the contents of standard input (fdin) into fdout.\r
257 + * Returns TRUE if a non-empty file was written successfully.\r
258 + * Otherwise, return FALSE. */\r
259 +static notmuch_bool_t\r
260 +copy_stdin (int fdin, int fdout)\r
261 +{\r
262 +    notmuch_bool_t empty = TRUE;\r
263 +\r
264 +    while (! interrupted) {\r
265 +       ssize_t remain;\r
266 +       char buf[4096];\r
267 +       char *p;\r
268 +\r
269 +       remain = read (fdin, buf, sizeof (buf));\r
270 +       if (remain == 0)\r
271 +           break;\r
272 +       if (remain < 0) {\r
273 +           if (errno == EINTR)\r
274 +               continue;\r
275 +           fprintf (stderr, "Error: reading from standard input: %s\n",\r
276 +                    strerror (errno));\r
277 +           return FALSE;\r
278 +       }\r
279 +\r
280 +       p = buf;\r
281 +       do {\r
282 +           ssize_t written = write (fdout, p, remain);\r
283 +           if (written < 0 && errno == EINTR)\r
284 +               continue;\r
285 +           if (written <= 0) {\r
286 +               fprintf (stderr, "Error: writing to temporary file: %s",\r
287 +                        strerror (errno));\r
288 +               return FALSE;\r
289 +           }\r
290 +           p += written;\r
291 +           remain -= written;\r
292 +           empty = FALSE;\r
293 +       } while (remain > 0);\r
294 +    }\r
295 +\r
296 +    return (!interrupted && !empty);\r
297 +}\r
298 +\r
299 +/* Add the specified message file to the notmuch database, applying tags.\r
300 + * The file is renamed to encode notmuch tags as maildir flags. */\r
301 +static void\r
302 +add_file_to_database (notmuch_database_t *notmuch, const char *path,\r
303 +                     tag_op_list_t *tag_ops)\r
304 +{\r
305 +    notmuch_message_t *message;\r
306 +    notmuch_status_t status;\r
307 +\r
308 +    status = notmuch_database_add_message (notmuch, path, &message);\r
309 +    switch (status) {\r
310 +    case NOTMUCH_STATUS_SUCCESS:\r
311 +    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:\r
312 +       break;\r
313 +    default:\r
314 +    case NOTMUCH_STATUS_FILE_NOT_EMAIL:\r
315 +    case NOTMUCH_STATUS_READ_ONLY_DATABASE:\r
316 +    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:\r
317 +    case NOTMUCH_STATUS_OUT_OF_MEMORY:\r
318 +    case NOTMUCH_STATUS_FILE_ERROR:\r
319 +    case NOTMUCH_STATUS_NULL_POINTER:\r
320 +    case NOTMUCH_STATUS_TAG_TOO_LONG:\r
321 +    case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:\r
322 +    case NOTMUCH_STATUS_UNBALANCED_ATOMIC:\r
323 +    case NOTMUCH_STATUS_LAST_STATUS:\r
324 +       fprintf (stderr, "Error: failed to add `%s' to notmuch database: %s\n",\r
325 +                path, notmuch_status_to_string (status));\r
326 +       return;\r
327 +    }\r
328 +\r
329 +    if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {\r
330 +       /* Don't change tags of an existing message. */\r
331 +       status = notmuch_message_tags_to_maildir_flags (message);\r
332 +       if (status != NOTMUCH_STATUS_SUCCESS)\r
333 +           fprintf (stderr, "Error: failed to sync tags to maildir flags\n");\r
334 +    } else {\r
335 +       tag_op_list_apply (message, tag_ops, TAG_FLAG_MAILDIR_SYNC);\r
336 +    }\r
337 +\r
338 +    notmuch_message_destroy (message);\r
339 +}\r
340 +\r
341 +static notmuch_bool_t\r
342 +insert_message (void *ctx, notmuch_database_t *notmuch, int fdin,\r
343 +               const char *dir, tag_op_list_t *tag_ops)\r
344 +{\r
345 +    char *tmppath;\r
346 +    char *newpath;\r
347 +    char *newdir;\r
348 +    int fdout;\r
349 +    char *cleanup_path;\r
350 +\r
351 +    fdout = maildir_open_tmp_file (ctx, dir, &tmppath, &newpath, &newdir);\r
352 +    if (fdout < 0)\r
353 +       return FALSE;\r
354 +\r
355 +    cleanup_path = tmppath;\r
356 +\r
357 +    if (! copy_stdin (fdin, fdout))\r
358 +       goto FAIL;\r
359 +\r
360 +    if (fsync (fdout) != 0) {\r
361 +       fprintf (stderr, "Error: fsync failed: %s\n", strerror (errno));\r
362 +       goto FAIL;\r
363 +    }\r
364 +\r
365 +    close (fdout);\r
366 +    fdout = -1;\r
367 +\r
368 +    /* Atomically move the new message file from the Maildir 'tmp' directory\r
369 +     * to the 'new' directory.  We follow the Dovecot recommendation to\r
370 +     * simply use rename() instead of link() and unlink().\r
371 +     * See also: http://wiki.dovecot.org/MailboxFormat/Maildir#Mail_delivery\r
372 +     */\r
373 +    if (rename (tmppath, newpath) != 0) {\r
374 +       fprintf (stderr, "Error: rename() failed: %s\n", strerror (errno));\r
375 +       goto FAIL;\r
376 +    }\r
377 +\r
378 +    if (! sync_dir (newdir))\r
379 +       goto FAIL;\r
380 +\r
381 +    /* Even if adding the message to the notmuch database fails,\r
382 +     * the message is on disk and we consider the delivery completed. */\r
383 +    add_file_to_database (notmuch, newpath, tag_ops);\r
384 +\r
385 +    return TRUE;\r
386 +\r
387 +  FAIL:\r
388 +    if (fdout >= 0)\r
389 +       close (fdout);\r
390 +    unlink (cleanup_path);\r
391 +    return FALSE;\r
392 +}\r
393 +\r
394 +int\r
395 +notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])\r
396 +{\r
397 +    notmuch_database_t *notmuch;\r
398 +    struct sigaction action;\r
399 +    const char *db_path;\r
400 +    const char **new_tags;\r
401 +    size_t new_tags_length;\r
402 +    tag_op_list_t *tag_ops;\r
403 +    char *query_string = NULL;\r
404 +    const char *maildir;\r
405 +    int opt_index = 1;\r
406 +    unsigned int i;\r
407 +    notmuch_bool_t ret;\r
408 +\r
409 +    db_path = notmuch_config_get_database_path (config);\r
410 +    new_tags = notmuch_config_get_new_tags (config, &new_tags_length);\r
411 +\r
412 +    tag_ops = tag_op_list_create (config);\r
413 +    if (tag_ops == NULL) {\r
414 +       fprintf (stderr, "Out of memory.\n");\r
415 +       return 1;\r
416 +    }\r
417 +    for (i = 0; i < new_tags_length; i++) {\r
418 +       if (tag_op_list_append (tag_ops, new_tags[i], FALSE))\r
419 +           return 1;\r
420 +    }\r
421 +\r
422 +    if (parse_tag_command_line (config, argc - opt_index, argv + opt_index,\r
423 +                               &query_string, tag_ops))\r
424 +       return 1;\r
425 +\r
426 +    if (*query_string != '\0') {\r
427 +       fprintf (stderr, "Error: unexpected query string: %s\n", query_string);\r
428 +       return 1;\r
429 +    }\r
430 +\r
431 +    maildir = db_path;\r
432 +\r
433 +    /* Setup our handler for SIGINT. We do not set SA_RESTART so that copying\r
434 +     * from standard input may be interrupted. */\r
435 +    memset (&action, 0, sizeof (struct sigaction));\r
436 +    action.sa_handler = handle_sigint;\r
437 +    sigemptyset (&action.sa_mask);\r
438 +    action.sa_flags = 0;\r
439 +    sigaction (SIGINT, &action, NULL);\r
440 +\r
441 +    if (notmuch_database_open (notmuch_config_get_database_path (config),\r
442 +                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))\r
443 +       return 1;\r
444 +\r
445 +    ret = insert_message (config, notmuch, STDIN_FILENO, maildir, tag_ops);\r
446 +\r
447 +    notmuch_database_destroy (notmuch);\r
448 +\r
449 +    return (ret) ? 0 : 1;\r
450 +}\r
451 diff --git a/notmuch.c b/notmuch.c\r
452 index 45a73ce..d11f214 100644\r
453 --- a/notmuch.c\r
454 +++ b/notmuch.c\r
455 @@ -44,6 +44,8 @@ static command_t commands[] = {\r
456        "Interactively setup notmuch for first use." },\r
457      { "new", notmuch_new_command, FALSE,\r
458        "Find and import new messages to the notmuch database." },\r
459 +    { "insert", notmuch_insert_command, FALSE,\r
460 +      "Add a new message into the maildir and notmuch database." },\r
461      { "search", notmuch_search_command, FALSE,\r
462        "Search for messages matching the given search terms." },\r
463      { "show", notmuch_show_command, FALSE,\r
464 -- \r
465 1.7.12.1\r
466 \r