Re: [PATCH 1/2] Add Google Inc. to AUTHORS as a contributor.
[notmuch-archives.git] / 43 / 167b470170fb0076beede42b85200c4a9a3bc0
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 4A9E1431FDA\r
6         for <notmuch@notmuchmail.org>; Thu, 24 Jan 2013 04:09:38 -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.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 1ogO2gYrHdnC for <notmuch@notmuchmail.org>;\r
17         Thu, 24 Jan 2013 04:09:34 -0800 (PST)\r
18 Received: from mail-da0-f48.google.com (mail-da0-f48.google.com\r
19         [209.85.210.48]) (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 52BFB431FDB\r
22         for <notmuch@notmuchmail.org>; Thu, 24 Jan 2013 04:09:30 -0800 (PST)\r
23 Received: by mail-da0-f48.google.com with SMTP id k18so4242643dae.21\r
24         for <notmuch@notmuchmail.org>; Thu, 24 Jan 2013 04:09:29 -0800 (PST)\r
25 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r
26         h=x-received:from:to:cc:subject:date:message-id:x-mailer:in-reply-to\r
27         :references:mime-version:content-type:content-transfer-encoding;\r
28         bh=MXqGwY8d1zepC3hxsBpykWuFWGygQ9eNuz2WI3R4Efw=;\r
29         b=bneo3r8406MFmssekRC9Gr02BlCCyS2qvnyjMAM/lOYP5AcE+v7Y9MfL4CXQ6NiwaN\r
30         IJesIvD04cnUNUdQl1GXm8Dy0fJWYgp7Yc29nrUIvkeNecdgbQR7lqTozoHLEZPbIuRa\r
31         9vdKXWes6HZG+eIUeTmBQxx7dbSp/xNBNMGC+F1Ek3D4S++vVWD2rJH7jZMsXVSgujlN\r
32         YgNM64lEeTc/Vkq1oEs1c4Ju2awO/uu12um1uBvfNe/SSx5wlsX7mbWqNOpf/3PVX9z6\r
33         kKBk8H8383IIuPrgDgowNpPNqeGRCYmkaJuWqLJq7tCUzI4qkv4NrATK9H3D77Sl2Aph\r
34         WVmA==\r
35 X-Received: by 10.66.88.164 with SMTP id bh4mr4069139pab.41.1359029369550;\r
36         Thu, 24 Jan 2013 04:09:29 -0800 (PST)\r
37 Received: from localhost (215.42.233.220.static.exetel.com.au.\r
38         [220.233.42.215])\r
39         by mx.google.com with ESMTPS id i5sm15604556pax.13.2013.01.24.04.09.27\r
40         (version=TLSv1.2 cipher=RC4-SHA bits=128/128);\r
41         Thu, 24 Jan 2013 04:09:29 -0800 (PST)\r
42 From: Peter Wang <novalazy@gmail.com>\r
43 To: notmuch@notmuchmail.org\r
44 Subject: [PATCH v4 03/12] cli: add insert command\r
45 Date: Thu, 24 Jan 2013 23:07:59 +1100\r
46 Message-Id: <1359029288-12132-4-git-send-email-novalazy@gmail.com>\r
47 X-Mailer: git-send-email 1.7.12.1\r
48 In-Reply-To: <1359029288-12132-1-git-send-email-novalazy@gmail.com>\r
49 References: <1359029288-12132-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: Thu, 24 Jan 2013 12:09:38 -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 | 341 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\r
80  notmuch.c        |   3 +\r
81  4 files changed, 348 insertions(+)\r
82  create mode 100644 notmuch-insert.c\r
83 \r
84 diff --git a/Makefile.local b/Makefile.local\r
85 index c274f07..bb2381d 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 5f28836..af7d094 100644\r
98 --- a/notmuch-client.h\r
99 +++ b/notmuch-client.h\r
100 @@ -175,6 +175,9 @@ int\r
101  notmuch_dump_command (void *ctx, int argc, char *argv[]);\r
102  \r
103  int\r
104 +notmuch_insert_command (void *ctx, int argc, char *argv[]);\r
105 +\r
106 +int\r
107  notmuch_new_command (void *ctx, 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..31c1152\r
113 --- /dev/null\r
114 +++ b/notmuch-insert.c\r
115 @@ -0,0 +1,341 @@\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 +    ret = TRUE;\r
189 +    fd = open (dir, O_RDONLY);\r
190 +    if (fd == -1) {\r
191 +       fprintf (stderr, "Error: open() dir failed: %s\n", strerror (errno));\r
192 +       ret = FALSE;\r
193 +    }\r
194 +    if (ret && fsync (fd) != 0) {\r
195 +       fprintf (stderr, "Error: fsync() dir failed: %s\n", strerror (errno));\r
196 +       ret = FALSE;\r
197 +    }\r
198 +    if (fd != -1)\r
199 +       close (fd);\r
200 +    return ret;\r
201 +}\r
202 +\r
203 +/* Open a unique file in the Maildir 'tmp' directory.\r
204 + * Returns the file descriptor on success, or -1 on failure.\r
205 + * On success, file paths for the message in the 'tmp' and 'new'\r
206 + * directories are returned via tmppath and newpath,\r
207 + * and the path of the 'new' directory itself in newdir. */\r
208 +static int\r
209 +maildir_open_tmp_file (void *ctx, const char *dir,\r
210 +                      char **tmppath, char **newpath, char **newdir)\r
211 +{\r
212 +    pid_t pid;\r
213 +    char hostname[256];\r
214 +    struct timeval tv;\r
215 +    char *filename;\r
216 +    int fd = -1;\r
217 +\r
218 +    /* We follow the Dovecot file name generation algorithm. */\r
219 +    pid = getpid ();\r
220 +    safe_gethostname (hostname, sizeof (hostname));\r
221 +    do {\r
222 +       gettimeofday (&tv, NULL);\r
223 +       filename = talloc_asprintf (ctx, "%ld.M%ldP%d.%s",\r
224 +                                   tv.tv_sec, tv.tv_usec, pid, hostname);\r
225 +       if (! filename) {\r
226 +           fprintf (stderr, "Out of memory\n");\r
227 +           return -1;\r
228 +       }\r
229 +\r
230 +       *tmppath = talloc_asprintf (ctx, "%s/tmp/%s", dir, filename);\r
231 +       if (! *tmppath) {\r
232 +           fprintf (stderr, "Out of memory\n");\r
233 +           return -1;\r
234 +       }\r
235 +\r
236 +       fd = open (*tmppath, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);\r
237 +    } while (fd == -1 && errno == EEXIST);\r
238 +\r
239 +    if (fd == -1) {\r
240 +       fprintf (stderr, "Error: opening %s: %s\n", *tmppath, strerror (errno));\r
241 +       return -1;\r
242 +    }\r
243 +\r
244 +    *newdir = talloc_asprintf (ctx, "%s/new", dir);\r
245 +    *newpath = talloc_asprintf (ctx, "%s/new/%s", dir, filename);\r
246 +    if (! *newdir || ! *newpath) {\r
247 +       fprintf (stderr, "Out of memory\n");\r
248 +       close (fd);\r
249 +       unlink (*tmppath);\r
250 +       return -1;\r
251 +    }\r
252 +\r
253 +    talloc_free (filename);\r
254 +\r
255 +    return fd;\r
256 +}\r
257 +\r
258 +/* Copy the contents of standard input (fdin) into fdout. */\r
259 +static notmuch_bool_t\r
260 +copy_stdin (int fdin, int fdout)\r
261 +{\r
262 +    char buf[4096];\r
263 +    char *p;\r
264 +    ssize_t remain;\r
265 +    ssize_t written;\r
266 +\r
267 +    while (! interrupted) {\r
268 +       remain = read (fdin, buf, sizeof (buf));\r
269 +       if (remain == 0)\r
270 +           break;\r
271 +       if (remain < 0) {\r
272 +           if (errno == EINTR)\r
273 +               continue;\r
274 +           fprintf (stderr, "Error: reading from standard input: %s\n",\r
275 +                    strerror (errno));\r
276 +           return FALSE;\r
277 +       }\r
278 +\r
279 +       p = buf;\r
280 +       do {\r
281 +           written = write (fdout, p, remain);\r
282 +           if (written < 0 && errno == EINTR)\r
283 +               continue;\r
284 +           if (written <= 0) {\r
285 +               fprintf (stderr, "Error: writing to temporary file: %s",\r
286 +                        strerror (errno));\r
287 +               return FALSE;\r
288 +           }\r
289 +           p += written;\r
290 +           remain -= written;\r
291 +       } while (remain > 0);\r
292 +    }\r
293 +\r
294 +    return ! interrupted;\r
295 +}\r
296 +\r
297 +/* Add the specified message file to the notmuch database, applying tags.\r
298 + * The file is renamed to encode notmuch tags as maildir flags. */\r
299 +static notmuch_bool_t\r
300 +add_file_to_database (notmuch_database_t *notmuch, const char *path,\r
301 +                     tag_op_list_t *tag_ops)\r
302 +{\r
303 +    notmuch_message_t *message;\r
304 +    notmuch_status_t status;\r
305 +\r
306 +    status = notmuch_database_add_message (notmuch, path, &message);\r
307 +    switch (status) {\r
308 +    case NOTMUCH_STATUS_SUCCESS:\r
309 +    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:\r
310 +       break;\r
311 +    default:\r
312 +    case NOTMUCH_STATUS_FILE_NOT_EMAIL:\r
313 +    case NOTMUCH_STATUS_READ_ONLY_DATABASE:\r
314 +    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:\r
315 +    case NOTMUCH_STATUS_OUT_OF_MEMORY:\r
316 +    case NOTMUCH_STATUS_FILE_ERROR:\r
317 +    case NOTMUCH_STATUS_NULL_POINTER:\r
318 +    case NOTMUCH_STATUS_TAG_TOO_LONG:\r
319 +    case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:\r
320 +    case NOTMUCH_STATUS_UNBALANCED_ATOMIC:\r
321 +    case NOTMUCH_STATUS_LAST_STATUS:\r
322 +       fprintf (stderr, "Error: failed to add `%s' to notmuch database: %s\n",\r
323 +                path, notmuch_status_to_string (status));\r
324 +       return FALSE;\r
325 +    }\r
326 +\r
327 +    if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {\r
328 +       /* Don't change tags of an existing message. */\r
329 +       status = notmuch_message_tags_to_maildir_flags (message);\r
330 +       if (status != NOTMUCH_STATUS_SUCCESS)\r
331 +           fprintf (stderr, "Error: failed to sync tags to maildir flags\n");\r
332 +    } else {\r
333 +       status = tag_op_list_apply (message, tag_ops, TAG_FLAG_MAILDIR_SYNC);\r
334 +    }\r
335 +\r
336 +    notmuch_message_destroy (message);\r
337 +\r
338 +    return (status == NOTMUCH_STATUS_SUCCESS) ? TRUE : FALSE;\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 +\r
350 +    fdout = maildir_open_tmp_file (ctx, dir, &tmppath, &newpath, &newdir);\r
351 +    if (fdout < 0) {\r
352 +       return FALSE;\r
353 +    }\r
354 +\r
355 +    if (! copy_stdin (fdin, fdout)) {\r
356 +       close (fdout);\r
357 +       unlink (tmppath);\r
358 +       return FALSE;\r
359 +    }\r
360 +\r
361 +    if (fsync (fdout) != 0) {\r
362 +       fprintf (stderr, "Error: fsync failed: %s\n", strerror (errno));\r
363 +       close (fdout);\r
364 +       unlink (tmppath);\r
365 +       return FALSE;\r
366 +    }\r
367 +\r
368 +    close (fdout);\r
369 +\r
370 +    /* Atomically move the new message file from the Maildir 'tmp' directory\r
371 +     * to the 'new' directory.  We follow the Dovecot recommendation to\r
372 +     * simply use rename() instead of link() and unlink().\r
373 +     * See also: http://wiki.dovecot.org/MailboxFormat/Maildir#Mail_delivery\r
374 +     */\r
375 +    if (rename (tmppath, newpath) != 0) {\r
376 +       fprintf (stderr, "Error: rename() failed: %s\n", strerror (errno));\r
377 +       unlink (tmppath);\r
378 +       return FALSE;\r
379 +    }\r
380 +\r
381 +    if (! add_file_to_database (notmuch, newpath, tag_ops)) {\r
382 +       /* XXX add an option to keep the file in maildir? */\r
383 +       unlink (newpath);\r
384 +       return FALSE;\r
385 +    }\r
386 +\r
387 +    if (! sync_dir (newdir)) {\r
388 +       unlink (newpath);\r
389 +       return FALSE;\r
390 +    }\r
391 +\r
392 +    return TRUE;\r
393 +}\r
394 +\r
395 +int\r
396 +notmuch_insert_command (void *ctx, int argc, char *argv[])\r
397 +{\r
398 +    notmuch_config_t *config;\r
399 +    notmuch_database_t *notmuch;\r
400 +    struct sigaction action;\r
401 +    const char *db_path;\r
402 +    const char **new_tags;\r
403 +    size_t new_tags_length;\r
404 +    tag_op_list_t *tag_ops;\r
405 +    char *query_string = NULL;\r
406 +    const char *maildir;\r
407 +    int opt_index = 1;\r
408 +    unsigned int i;\r
409 +    notmuch_bool_t ret;\r
410 +\r
411 +    config = notmuch_config_open (ctx, NULL, NULL);\r
412 +    if (config == NULL)\r
413 +       return 1;\r
414 +\r
415 +    db_path = notmuch_config_get_database_path (config);\r
416 +    new_tags = notmuch_config_get_new_tags (config, &new_tags_length);\r
417 +\r
418 +    tag_ops = tag_op_list_create (ctx);\r
419 +    if (tag_ops == NULL) {\r
420 +       fprintf (stderr, "Out of memory.\n");\r
421 +       return 1;\r
422 +    }\r
423 +    for (i = 0; i < new_tags_length; i++) {\r
424 +       if (tag_op_list_append (tag_ops, new_tags[i], FALSE))\r
425 +           return 1;\r
426 +    }\r
427 +\r
428 +    if (parse_tag_command_line (ctx, argc - opt_index, argv + opt_index,\r
429 +                               &query_string, tag_ops))\r
430 +       return 1;\r
431 +\r
432 +    if (*query_string != '\0') {\r
433 +       fprintf (stderr, "Error: unexpected query string: %s\n", query_string);\r
434 +       return 1;\r
435 +    }\r
436 +\r
437 +    maildir = db_path;\r
438 +\r
439 +    /* Setup our handler for SIGINT. We do not set SA_RESTART so that copying\r
440 +     * from standard input may be interrupted. */\r
441 +    memset (&action, 0, sizeof (struct sigaction));\r
442 +    action.sa_handler = handle_sigint;\r
443 +    sigemptyset (&action.sa_mask);\r
444 +    action.sa_flags = 0;\r
445 +    sigaction (SIGINT, &action, NULL);\r
446 +\r
447 +    if (notmuch_database_open (notmuch_config_get_database_path (config),\r
448 +                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))\r
449 +       return 1;\r
450 +\r
451 +    ret = insert_message (ctx, notmuch, STDIN_FILENO, maildir, tag_ops);\r
452 +\r
453 +    notmuch_database_destroy (notmuch);\r
454 +\r
455 +    return (ret) ? 0 : 1;\r
456 +}\r
457 diff --git a/notmuch.c b/notmuch.c\r
458 index 4fc0973..1c3b893 100644\r
459 --- a/notmuch.c\r
460 +++ b/notmuch.c\r
461 @@ -53,6 +53,9 @@ static command_t commands[] = {\r
462      { "new", notmuch_new_command,\r
463        "[options...]",\r
464        "Find and import new messages to the notmuch database." },\r
465 +    { "insert", notmuch_insert_command,\r
466 +      "[options...] [--] [+<tag>|-<tag> ...] < message",\r
467 +      "Add a new message into the maildir and notmuch database." },\r
468      { "search", notmuch_search_command,\r
469        "[options...] <search-terms> [...]",\r
470        "Search for messages matching the given search terms." },\r
471 -- \r
472 1.7.12.1\r
473 \r