]> git.notmuchmail.org Git - notmuch/blob - notmuch.c
notmuch show: Move subject from one-line summary down to its own line.
[notmuch] / notmuch.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2009 Carl Worth
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ .
17  *
18  * Author: Carl Worth <cworth@cworth.org>
19  */
20
21 #ifndef _GNU_SOURCE
22 #define _GNU_SOURCE /* for getline */
23 #endif
24 #include <stdio.h>
25
26 #include <gmime/gmime.h>
27
28 #include "notmuch.h"
29
30 /* This is separate from notmuch-private.h because we're trying to
31  * keep notmuch.c from looking into any internals, (which helps us
32  * develop notmuch.h into a plausible library interface).
33  */
34 #include "xutil.h"
35
36 #include <stddef.h>
37 #include <string.h>
38 #include <sys/stat.h>
39 #include <sys/time.h>
40 #include <unistd.h>
41 #include <dirent.h>
42 #include <errno.h>
43 #include <signal.h>
44
45 #include <talloc.h>
46
47 #define unused(x) x __attribute__ ((unused))
48
49 /* There's no point in continuing when we've detected that we've done
50  * something wrong internally (as opposed to the user passing in a
51  * bogus value).
52  *
53  * Note that __location__ comes from talloc.h.
54  */
55 #define INTERNAL_ERROR(format, ...)                     \
56     do {                                                \
57         fprintf(stderr,                                 \
58                 "Internal error: " format " (%s)\n",    \
59                 ##__VA_ARGS__, __location__);           \
60         exit (1);                                       \
61     } while (0)
62
63 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
64
65 typedef int (*command_function_t) (void *ctx, int argc, char *argv[]);
66
67 typedef struct command {
68     const char *name;
69     command_function_t function;
70     const char *summary;
71     const char *documentation;
72 } command_t;
73
74 typedef void (*add_files_callback_t) (notmuch_message_t *message);
75
76 typedef struct {
77     int ignore_read_only_directories;
78     int saw_read_only_directory;
79
80     int total_files;
81     int processed_files;
82     int added_messages;
83     struct timeval tv_start;
84
85     add_files_callback_t callback;
86 } add_files_state_t;
87
88 static void
89 chomp_newline (char *str)
90 {
91     if (str && str[strlen(str)-1] == '\n')
92         str[strlen(str)-1] = '\0';
93 }
94
95 /* Compute the number of seconds elapsed from start to end. */
96 static double
97 tv_elapsed (struct timeval start, struct timeval end)
98 {
99     return ((end.tv_sec - start.tv_sec) +
100             (end.tv_usec - start.tv_usec) / 1e6);
101 }
102
103 static void
104 print_formatted_seconds (double seconds)
105 {
106     int hours;
107     int minutes;
108
109     if (seconds < 1) {
110         printf ("almost no time");
111         return;
112     }
113
114     if (seconds > 3600) {
115         hours = (int) seconds / 3600;
116         printf ("%dh ", hours);
117         seconds -= hours * 3600;
118     }
119
120     if (seconds > 60) {
121         minutes = (int) seconds / 60;
122         printf ("%dm ", minutes);
123         seconds -= minutes * 60;
124     }
125
126     printf ("%ds", (int) seconds);
127 }
128
129 static volatile sig_atomic_t do_add_files_print_progress = 0;
130
131 static void
132 handle_sigalrm (unused (int signal))
133 {
134     do_add_files_print_progress = 1;
135 }
136
137 static void
138 add_files_print_progress (add_files_state_t *state)
139 {
140     struct timeval tv_now;
141     double elapsed_overall, rate_overall;
142
143     gettimeofday (&tv_now, NULL);
144
145     elapsed_overall = tv_elapsed (state->tv_start, tv_now);
146     rate_overall = (state->processed_files) / elapsed_overall;
147
148     printf ("Processed %d", state->processed_files);
149
150     if (state->total_files) {
151         printf (" of %d files (", state->total_files);
152         print_formatted_seconds ((state->total_files - state->processed_files) /
153                                  rate_overall);
154         printf (" remaining).      \r");
155     } else {
156         printf (" files (%d files/sec.)    \r", (int) rate_overall);
157     }
158
159     fflush (stdout);
160 }
161
162 /* Examine 'path' recursively as follows:
163  *
164  *   o Ask the filesystem for the mtime of 'path' (path_mtime)
165  *
166  *   o Ask the database for its timestamp of 'path' (path_dbtime)
167  *
168  *   o If 'path_mtime' > 'path_dbtime'
169  *
170  *       o For each regular file in 'path' with mtime newer than the
171  *         'path_dbtime' call add_message to add the file to the
172  *         database.
173  *
174  *       o For each sub-directory of path, recursively call into this
175  *         same function.
176  *
177  *   o Tell the database to update its time of 'path' to 'path_mtime'
178  *
179  * The 'struct stat *st' must point to a structure that has already
180  * been initialized for 'path' by calling stat().
181  */
182 static notmuch_status_t
183 add_files_recursive (notmuch_database_t *notmuch,
184                      const char *path,
185                      struct stat *st,
186                      add_files_state_t *state)
187 {
188     DIR *dir = NULL;
189     struct dirent *e, *entry = NULL;
190     int entry_length;
191     int err;
192     char *next = NULL;
193     time_t path_mtime, path_dbtime;
194     notmuch_status_t status, ret = NOTMUCH_STATUS_SUCCESS;
195     notmuch_message_t *message = NULL, **closure;
196
197     /* If we're told to, we bail out on encountering a read-only
198      * directory, (with this being a clear clue from the user to
199      * Notmuch that new mail won't be arriving there and we need not
200      * look. */
201     if (state->ignore_read_only_directories &&
202         (st->st_mode & S_IWUSR) == 0)
203     {
204         state->saw_read_only_directory = TRUE;
205         goto DONE;
206     }
207
208     path_mtime = st->st_mtime;
209
210     path_dbtime = notmuch_database_get_timestamp (notmuch, path);
211
212     dir = opendir (path);
213     if (dir == NULL) {
214         fprintf (stderr, "Error opening directory %s: %s\n",
215                  path, strerror (errno));
216         ret = NOTMUCH_STATUS_FILE_ERROR;
217         goto DONE;
218     }
219
220     entry_length = offsetof (struct dirent, d_name) +
221         pathconf (path, _PC_NAME_MAX) + 1;
222     entry = malloc (entry_length);
223
224     while (1) {
225         err = readdir_r (dir, entry, &e);
226         if (err) {
227             fprintf (stderr, "Error reading directory: %s\n",
228                      strerror (errno));
229             ret = NOTMUCH_STATUS_FILE_ERROR;
230             goto DONE;
231         }
232
233         if (e == NULL)
234             break;
235
236         /* If this directory hasn't been modified since the last
237          * add_files, then we only need to look further for
238          * sub-directories. */
239         if (path_mtime <= path_dbtime && entry->d_type != DT_DIR)
240             continue;
241
242         /* Ignore special directories to avoid infinite recursion.
243          * Also ignore the .notmuch directory.
244          */
245         /* XXX: Eventually we'll want more sophistication to let the
246          * user specify files to be ignored. */
247         if (strcmp (entry->d_name, ".") == 0 ||
248             strcmp (entry->d_name, "..") == 0 ||
249             strcmp (entry->d_name, ".notmuch") ==0)
250         {
251             continue;
252         }
253
254         next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name);
255
256         if (stat (next, st)) {
257             fprintf (stderr, "Error reading %s: %s\n",
258                      next, strerror (errno));
259             ret = NOTMUCH_STATUS_FILE_ERROR;
260             continue;
261         }
262
263         if (S_ISREG (st->st_mode)) {
264             /* If the file hasn't been modified since the last
265              * add_files, then we need not look at it. */
266             if (st->st_mtime > path_dbtime) {
267                 state->processed_files++;
268
269                 if (state->callback)
270                     closure = &message;
271                 else
272                     closure = NULL;
273
274                 status = notmuch_database_add_message (notmuch, next, closure);
275                 switch (status) {
276                     /* success */
277                     case NOTMUCH_STATUS_SUCCESS:
278                         state->added_messages++;
279                         if (state->callback)
280                             (state->callback) (message);
281                         break;
282                     /* Non-fatal issues (go on to next file) */
283                     case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
284                         /* Stay silent on this one. */
285                         break;
286                     case NOTMUCH_STATUS_FILE_NOT_EMAIL:
287                         fprintf (stderr, "Note: Ignoring non-mail file: %s\n",
288                                  next);
289                         break;
290                     /* Fatal issues. Don't process anymore. */
291                     case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
292                     case NOTMUCH_STATUS_OUT_OF_MEMORY:
293                         fprintf (stderr, "Error: %s. Halting processing.\n",
294                                  notmuch_status_to_string (status));
295                         ret = status;
296                         goto DONE;
297                     default:
298                     case NOTMUCH_STATUS_FILE_ERROR:
299                     case NOTMUCH_STATUS_NULL_POINTER:
300                     case NOTMUCH_STATUS_TAG_TOO_LONG:
301                     case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
302                     case NOTMUCH_STATUS_LAST_STATUS:
303                         INTERNAL_ERROR ("add_message returned unexpected value: %d",  status);
304                         goto DONE;
305                 }
306
307                 if (message) {
308                     notmuch_message_destroy (message);
309                     message = NULL;
310                 }
311
312                 if (do_add_files_print_progress) {
313                     do_add_files_print_progress = 0;
314                     add_files_print_progress (state);
315                 }
316             }
317         } else if (S_ISDIR (st->st_mode)) {
318             status = add_files_recursive (notmuch, next, st, state);
319             if (status && ret == NOTMUCH_STATUS_SUCCESS)
320                 ret = status;
321         }
322
323         talloc_free (next);
324         next = NULL;
325     }
326
327     status = notmuch_database_set_timestamp (notmuch, path, path_mtime);
328     if (status && ret == NOTMUCH_STATUS_SUCCESS)
329         ret = status;
330
331   DONE:
332     if (next)
333         talloc_free (next);
334     if (entry)
335         free (entry);
336     if (dir)
337         closedir (dir);
338
339     return ret;
340 }
341
342 /* This is the top-level entry point for add_files. It does a couple
343  * of error checks, sets up the progress-printing timer and then calls
344  * into the recursive function. */
345 static notmuch_status_t
346 add_files (notmuch_database_t *notmuch,
347            const char *path,
348            add_files_state_t *state)
349 {
350     struct stat st;
351     notmuch_status_t status;
352     struct sigaction action;
353     struct itimerval timerval;
354
355     if (stat (path, &st)) {
356         fprintf (stderr, "Error reading directory %s: %s\n",
357                  path, strerror (errno));
358         return NOTMUCH_STATUS_FILE_ERROR;
359     }
360
361     if (! S_ISDIR (st.st_mode)) {
362         fprintf (stderr, "Error: %s is not a directory.\n", path);
363         return NOTMUCH_STATUS_FILE_ERROR;
364     }
365
366     /* Setup our handler for SIGALRM */
367     memset (&action, 0, sizeof (struct sigaction));
368     action.sa_handler = handle_sigalrm;
369     sigemptyset (&action.sa_mask);
370     action.sa_flags = SA_RESTART;
371     sigaction (SIGALRM, &action, NULL);
372
373     /* Then start a timer to send SIGALRM once per second. */
374     timerval.it_interval.tv_sec = 1;
375     timerval.it_interval.tv_usec = 0;
376     timerval.it_value.tv_sec = 1;
377     timerval.it_value.tv_usec = 0;
378     setitimer (ITIMER_REAL, &timerval, NULL);
379
380     status = add_files_recursive (notmuch, path, &st, state);
381
382     /* Now stop the timer. */
383     timerval.it_interval.tv_sec = 0;
384     timerval.it_interval.tv_usec = 0;
385     timerval.it_value.tv_sec = 0;
386     timerval.it_value.tv_usec = 0;
387     setitimer (ITIMER_REAL, &timerval, NULL);
388
389     /* And disable the signal handler. */
390     action.sa_handler = SIG_IGN;
391     sigaction (SIGALRM, &action, NULL);
392
393     return status;
394 }
395
396 /* Recursively count all regular files in path and all sub-direcotries
397  * of path.  The result is added to *count (which should be
398  * initialized to zero by the top-level caller before calling
399  * count_files). */
400 static void
401 count_files (const char *path, int *count)
402 {
403     DIR *dir;
404     struct dirent *e, *entry = NULL;
405     int entry_length;
406     int err;
407     char *next;
408     struct stat st;
409
410     dir = opendir (path);
411
412     if (dir == NULL) {
413         fprintf (stderr, "Warning: failed to open directory %s: %s\n",
414                  path, strerror (errno));
415         goto DONE;
416     }
417
418     entry_length = offsetof (struct dirent, d_name) +
419         pathconf (path, _PC_NAME_MAX) + 1;
420     entry = malloc (entry_length);
421
422     while (1) {
423         err = readdir_r (dir, entry, &e);
424         if (err) {
425             fprintf (stderr, "Error reading directory: %s\n",
426                      strerror (errno));
427             free (entry);
428             goto DONE;
429         }
430
431         if (e == NULL)
432             break;
433
434         /* Ignore special directories to avoid infinite recursion.
435          * Also ignore the .notmuch directory.
436          */
437         /* XXX: Eventually we'll want more sophistication to let the
438          * user specify files to be ignored. */
439         if (strcmp (entry->d_name, ".") == 0 ||
440             strcmp (entry->d_name, "..") == 0 ||
441             strcmp (entry->d_name, ".notmuch") == 0)
442         {
443             continue;
444         }
445
446         if (asprintf (&next, "%s/%s", path, entry->d_name) == -1) {
447             next = NULL;
448             fprintf (stderr, "Error descending from %s to %s: Out of memory\n",
449                      path, entry->d_name);
450             continue;
451         }
452
453         stat (next, &st);
454
455         if (S_ISREG (st.st_mode)) {
456             *count = *count + 1;
457             if (*count % 1000 == 0) {
458                 printf ("Found %d files so far.\r", *count);
459                 fflush (stdout);
460             }
461         } else if (S_ISDIR (st.st_mode)) {
462             count_files (next, count);
463         }
464
465         free (next);
466     }
467
468   DONE:
469     if (entry)
470         free (entry);
471
472     closedir (dir);
473 }
474
475 static int
476 setup_command (unused (void *ctx), unused (int argc), unused (char *argv[]))
477 {
478     notmuch_database_t *notmuch = NULL;
479     char *default_path, *mail_directory = NULL;
480     size_t line_size;
481     int count;
482     add_files_state_t add_files_state;
483     double elapsed;
484     struct timeval tv_now;
485     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
486
487     printf ("Welcome to notmuch!\n\n");
488
489     printf ("The goal of notmuch is to help you manage and search your collection of\n"
490             "email, and to efficiently keep up with the flow of email as it comes in.\n\n");
491
492     printf ("Notmuch needs to know the top-level directory of your email archive,\n"
493             "(where you already have mail stored and where messages will be delivered\n"
494             "in the future). This directory can contain any number of sub-directories\n"
495             "and primarily just files with indvidual email messages (eg. maildir or mh\n"
496             "archives are perfect). If there are other, non-email files (such as\n"
497             "indexes maintained by other email programs) then notmuch will do its\n"
498             "best to detect those and ignore them.\n\n");
499
500     printf ("Mail storage that uses mbox format, (where one mbox file contains many\n"
501             "messages), will not work with notmuch. If that's how your mail is currently\n"
502             "stored, we recommend you first convert it to maildir format with a utility\n"
503             "such as mb2md. In that case, press Control-C now and run notmuch again\n"
504             "once the conversion is complete.\n\n");
505
506
507     default_path = notmuch_database_default_path ();
508     printf ("Top-level mail directory [%s]: ", default_path);
509     fflush (stdout);
510
511     getline (&mail_directory, &line_size, stdin);
512     chomp_newline (mail_directory);
513
514     printf ("\n");
515
516     if (mail_directory == NULL || strlen (mail_directory) == 0) {
517         if (mail_directory)
518             free (mail_directory);
519         mail_directory = default_path;
520     } else {
521         /* XXX: Instead of telling the user to use an environment
522          * variable here, we should really be writing out a configuration
523          * file and loading that on the next run. */
524         if (strcmp (mail_directory, default_path)) {
525             printf ("Note: Since you are not using the default path, you will want to set\n"
526                     "the NOTMUCH_BASE environment variable to %s so that\n"
527                     "future calls to notmuch commands will know where to find your mail.\n",
528                     mail_directory);
529             printf ("For example, if you are using bash for your shell, add:\n\n");
530             printf ("\texport NOTMUCH_BASE=%s\n\n", mail_directory);
531             printf ("to your ~/.bashrc file.\n\n");
532         }
533         free (default_path);
534     }
535
536     /* Coerce th directory into an absolute directory name. */
537     if (*mail_directory != '/') {
538         char *cwd, *absolute_mail_directory;
539
540         cwd = getcwd (NULL, 0);
541         if (cwd == NULL) {
542             fprintf (stderr, "Out of memory.\n");
543             exit (1);
544         }
545
546         if (asprintf (&absolute_mail_directory, "%s/%s",
547                       cwd, mail_directory) < 0)
548         {
549             fprintf (stderr, "Out of memory.\n");
550             exit (1);
551         }
552
553         free (cwd);
554         free (mail_directory);
555         mail_directory = absolute_mail_directory;
556
557         printf ("Abs: %s\n", mail_directory);
558     }
559
560     notmuch = notmuch_database_create (mail_directory);
561     if (notmuch == NULL) {
562         fprintf (stderr, "Failed to create new notmuch database at %s\n",
563                  mail_directory);
564         ret = NOTMUCH_STATUS_FILE_ERROR;
565         goto DONE;
566     }
567
568     printf ("OK. Let's take a look at the mail we can find in the directory\n");
569     printf ("%s ...\n", mail_directory);
570
571     count = 0;
572     count_files (mail_directory, &count);
573
574     printf ("Found %d total files. That's not much mail.\n\n", count);
575
576     printf ("Next, we'll inspect the messages and create a database of threads:\n");
577
578     add_files_state.ignore_read_only_directories = FALSE;
579     add_files_state.saw_read_only_directory = FALSE;
580     add_files_state.total_files = count;
581     add_files_state.processed_files = 0;
582     add_files_state.added_messages = 0;
583     add_files_state.callback = NULL;
584     gettimeofday (&add_files_state.tv_start, NULL);
585
586     ret = add_files (notmuch, mail_directory, &add_files_state);
587
588     gettimeofday (&tv_now, NULL);
589     elapsed = tv_elapsed (add_files_state.tv_start,
590                           tv_now);
591     printf ("Processed %d %s in ", add_files_state.processed_files,
592             add_files_state.processed_files == 1 ?
593             "file" : "total files");
594     print_formatted_seconds (elapsed);
595     if (elapsed > 1) {
596         printf (" (%d files/sec.).                 \n",
597                 (int) (add_files_state.processed_files / elapsed));
598     } else {
599         printf (".                    \n");
600     }
601     if (add_files_state.added_messages) {
602         printf ("Added %d %s to the database.\n\n",
603                 add_files_state.added_messages,
604                 add_files_state.added_messages == 1 ?
605                 "message" : "unique messages");
606     }
607
608     printf ("When new mail is delivered to %s in the future,\n"
609             "run \"notmuch new\" to add it to the database.\n\n",
610             mail_directory);
611
612     if (ret) {
613         printf ("Note: At least one error was encountered: %s\n",
614                 notmuch_status_to_string (ret));
615     }
616
617   DONE:
618     if (mail_directory)
619         free (mail_directory);
620     if (notmuch)
621         notmuch_database_close (notmuch);
622
623     return ret;
624 }
625
626 static void
627 tag_inbox_and_unread (notmuch_message_t *message)
628 {
629     notmuch_message_add_tag (message, "inbox");
630     notmuch_message_add_tag (message, "unread");
631 }
632
633 static int
634 new_command (unused (void *ctx), unused (int argc), unused (char *argv[]))
635 {
636     notmuch_database_t *notmuch;
637     const char *mail_directory;
638     add_files_state_t add_files_state;
639     double elapsed;
640     struct timeval tv_now;
641     int ret = 0;
642
643     notmuch = notmuch_database_open (NULL);
644     if (notmuch == NULL) {
645         ret = 1;
646         goto DONE;
647     }
648
649     mail_directory = notmuch_database_get_path (notmuch);
650
651     add_files_state.ignore_read_only_directories = TRUE;
652     add_files_state.saw_read_only_directory = FALSE;
653     add_files_state.total_files = 0;
654     add_files_state.processed_files = 0;
655     add_files_state.added_messages = 0;
656     add_files_state.callback = tag_inbox_and_unread;
657     gettimeofday (&add_files_state.tv_start, NULL);
658
659     ret = add_files (notmuch, mail_directory, &add_files_state);
660
661     gettimeofday (&tv_now, NULL);
662     elapsed = tv_elapsed (add_files_state.tv_start,
663                           tv_now);
664     if (add_files_state.processed_files) {
665         printf ("Processed %d %s in ", add_files_state.processed_files,
666                 add_files_state.processed_files == 1 ?
667                 "file" : "total files");
668         print_formatted_seconds (elapsed);
669         if (elapsed > 1) {
670             printf (" (%d files/sec.).                 \n",
671                     (int) (add_files_state.processed_files / elapsed));
672         } else {
673             printf (".                    \n");
674         }
675     }
676     if (add_files_state.added_messages) {
677         printf ("Added %d new %s to the database (not much, really).\n",
678                 add_files_state.added_messages,
679                 add_files_state.added_messages == 1 ?
680                 "message" : "messages");
681     } else {
682         printf ("No new mail---and that's not much.\n");
683     }
684
685     if (elapsed > 1 && ! add_files_state.saw_read_only_directory) {
686         printf ("\nTip: If you have any sub-directories that are archives (that is,\n"
687                 "they will never receive new mail), marking these directores as\n"
688                 "read-only (chmod u-w /path/to/dir) will make \"notmuch new\"\n"
689                 "much more efficient (it won't even look in those directories).\n");
690     }
691
692     if (ret) {
693         printf ("\nNote: At least one error was encountered: %s\n",
694                 notmuch_status_to_string (ret));
695     }
696
697   DONE:
698     if (notmuch)
699         notmuch_database_close (notmuch);
700
701     return ret;
702 }
703
704 /* Construct a single query string from the passed arguments, using
705  * 'ctx' as the talloc owner for all allocations.
706  *
707  * Currently, the arguments are just connected with space characters,
708  * but we might do more processing in the future, (such as inserting
709  * any AND operators needed to work around Xapian QueryParser bugs).
710  *
711  * This function returns NULL in case of insufficient memory.
712  */
713 static char *
714 query_string_from_args (void *ctx, int argc, char *argv[])
715 {
716     char *query_string;
717     int i;
718
719     query_string = talloc_strdup (ctx, "");
720     if (query_string == NULL)
721         return NULL;
722
723     for (i = 0; i < argc; i++) {
724         if (i != 0) {
725             query_string = talloc_strdup_append (query_string, " ");
726             if (query_string == NULL)
727                 return NULL;
728         }
729
730         query_string = talloc_strdup_append (query_string, argv[i]);
731         if (query_string == NULL)
732             return NULL;
733     }
734
735     return query_string;
736 }
737
738 /* Format a nice representation of 'time' relative to the current time.
739  *
740  * Examples include:
741  *
742  *      5 mins. ago     (For times less than 60 minutes ago)
743  *      Today 12:30     (For times >60 minutes but still today)
744  *      Yest. 12:30
745  *      Mon.  12:30     (Before yesterday but fewer than 7 days ago)
746  *      October 12      (Between 7 and 180 days ago (about 6 months))
747  *      2008-06-30      (More than 180 days ago)
748  */
749 #define MINUTE (60)
750 #define HOUR (60 * MINUTE)
751 #define DAY (24 * HOUR)
752 #define RELATIVE_DATE_MAX 20
753 static const char *
754 _format_relative_date (void *ctx, time_t then)
755 {
756     struct tm tm_now, tm_then;
757     time_t now = time(NULL);
758     time_t delta;
759     char *result;
760
761     localtime_r (&now, &tm_now);
762     localtime_r (&then, &tm_then);
763
764     result = talloc_zero_size (ctx, RELATIVE_DATE_MAX);
765     if (result == NULL)
766         return "when?";
767
768     if (then > now)
769         return "the future";
770
771     delta = now - then;
772
773     if (delta > 180 * DAY) {
774         strftime (result, RELATIVE_DATE_MAX,
775                   "%F", &tm_then); /* 2008-06-30 */
776         return result;
777     }
778
779     if (delta < 3600) {
780         snprintf (result, RELATIVE_DATE_MAX,
781                   "%d mins. ago", (int) (delta / 60));
782         return result;
783     }
784
785     if (delta <= 7 * DAY) {
786         if (tm_then.tm_wday == tm_now.tm_wday &&
787             delta < DAY)
788         {
789             strftime (result, RELATIVE_DATE_MAX,
790                       "Today %R", &tm_then); /* Today 12:30 */
791             return result;
792         } else if ((tm_now.tm_wday + 7 - tm_then.tm_wday) % 7 == 1) {
793             strftime (result, RELATIVE_DATE_MAX,
794                       "Yest. %R", &tm_then); /* Yest. 12:30 */
795             return result;
796         } else {
797             if (tm_then.tm_wday != tm_now.tm_wday) {
798                 strftime (result, RELATIVE_DATE_MAX,
799                           "%a. %R", &tm_then); /* Mon. 12:30 */
800                 return result;
801             }
802         }
803     }
804
805     strftime (result, RELATIVE_DATE_MAX,
806               "%B %d", &tm_then); /* October 12 */
807     return result;
808 }
809 #undef MINUTE
810 #undef HOUR
811 #undef DAY
812
813 static int
814 search_command (void *ctx, int argc, char *argv[])
815 {
816     void *local = talloc_new (ctx);
817     notmuch_database_t *notmuch = NULL;
818     notmuch_query_t *query;
819     notmuch_threads_t *threads;
820     notmuch_thread_t *thread;
821     notmuch_tags_t *tags;
822     char *query_str;
823     const char *relative_date;
824     time_t date;
825     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
826
827     notmuch = notmuch_database_open (NULL);
828     if (notmuch == NULL) {
829         ret = 1;
830         goto DONE;
831     }
832
833     query_str = query_string_from_args (local, argc, argv);
834
835     query = notmuch_query_create (notmuch, query_str);
836     if (query == NULL) {
837         fprintf (stderr, "Out of memory\n");
838         ret = 1;
839         goto DONE;
840     }
841
842     for (threads = notmuch_query_search_threads (query);
843          notmuch_threads_has_more (threads);
844          notmuch_threads_advance (threads))
845     {
846         int first = 1;
847
848         thread = notmuch_threads_get (threads);
849
850         date = notmuch_thread_get_oldest_date (thread);
851         relative_date = _format_relative_date (local, date);
852
853         printf ("thread:%s %12s %s",
854                 notmuch_thread_get_thread_id (thread),
855                 relative_date,
856                 notmuch_thread_get_subject (thread));
857
858         printf (" (");
859         for (tags = notmuch_thread_get_tags (thread);
860              notmuch_tags_has_more (tags);
861              notmuch_tags_advance (tags))
862         {
863             if (! first)
864                 printf (" ");
865             printf ("%s", notmuch_tags_get (tags));
866             first = 0;
867         }
868         printf (")\n");
869
870         notmuch_thread_destroy (thread);
871     }
872
873     notmuch_query_destroy (query);
874
875   DONE:
876     if (notmuch)
877         notmuch_database_close (notmuch);
878     talloc_free (local);
879
880     return ret;
881 }
882
883 static const char *
884 _get_tags_as_string (void *ctx, notmuch_message_t *message)
885 {
886     notmuch_tags_t *tags;
887     int first = 1;
888     const char *tag;
889     char *result;
890
891     result = talloc_strdup (ctx, "");
892     if (result == NULL)
893         return NULL;
894
895     for (tags = notmuch_message_get_tags (message);
896          notmuch_tags_has_more (tags);
897          notmuch_tags_advance (tags))
898     {
899         tag = notmuch_tags_get (tags);
900
901         result = talloc_asprintf_append (result, "%s%s",
902                                          first ? "" : " ", tag);
903         first = 0;
904     }
905
906     return result;
907 }
908
909 /* Get a nice, single-line summary of message. */
910 static const char *
911 _get_one_line_summary (void *ctx, notmuch_message_t *message)
912 {
913     const char *from;
914     time_t date;
915     const char *relative_date;
916     const char *tags;
917
918     from = notmuch_message_get_header (message, "from");
919
920     date = notmuch_message_get_date (message);
921     relative_date = _format_relative_date (ctx, date);
922
923     tags = _get_tags_as_string (ctx, message);
924
925     return talloc_asprintf (ctx, "%s (%s) (%s)",
926                             from, relative_date, tags);
927 }
928
929 static void
930 show_message_part (GMimeObject *part, int *part_count)
931 {
932     GMimeStream *stream;
933     GMimeDataWrapper *wrapper;
934     GMimeContentDisposition *disposition;
935     GMimeContentType *content_type;
936
937     *part_count = *part_count + 1;
938
939     if (GMIME_IS_MULTIPART (part)) {
940         GMimeMultipart *multipart = GMIME_MULTIPART (part);
941         int i;
942
943         for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
944             if (GMIME_IS_MULTIPART_SIGNED (multipart)) {
945                 /* Don't index the signature. */
946                 if (i == 1)
947                     continue;
948                 if (i > 1)
949                     fprintf (stderr, "Warning: Unexpected extra parts of mutlipart/signed. Continuing.\n");
950             }
951             show_message_part (g_mime_multipart_get_part (multipart, i),
952                                part_count);
953         }
954         return;
955     }
956
957     if (GMIME_IS_MESSAGE_PART (part)) {
958         GMimeMessage *mime_message;
959
960         mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
961
962         show_message_part (g_mime_message_get_mime_part (mime_message),
963                            part_count);
964
965         return;
966     }
967
968     if (! (GMIME_IS_PART (part))) {
969         fprintf (stderr, "Warning: Not displaying unknown mime part: %s.\n",
970                  g_type_name (G_OBJECT_TYPE (part)));
971         return;
972     }
973
974     disposition = g_mime_object_get_content_disposition (part);
975     if (disposition &&
976         strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
977     {
978         const char *filename = g_mime_part_get_filename (GMIME_PART (part));
979         content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
980
981         printf ("\fattachment{ ID: %d, Content-type: %s\n",
982                 *part_count,
983                 g_mime_content_type_to_string (content_type));
984         printf ("Attachment: %s (%s)\n", filename,
985                 g_mime_content_type_to_string (content_type));
986         printf ("\fattachment}\n");
987
988         return;
989     }
990
991     content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
992
993     printf ("\fpart{ ID: %d, Content-type: %s\n",
994             *part_count,
995             g_mime_content_type_to_string (content_type));
996
997     if (g_mime_content_type_is_type (content_type, "text", "*") &&
998         !g_mime_content_type_is_type (content_type, "text", "html"))
999     {
1000         stream = g_mime_stream_file_new (stdout);
1001         g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
1002
1003         wrapper = g_mime_part_get_content_object (GMIME_PART (part));
1004         if (wrapper)
1005             g_mime_data_wrapper_write_to_stream (wrapper, stream);
1006
1007         g_object_unref (stream);
1008     }
1009     else
1010     {
1011         printf ("Non-text part: %s\n",
1012                 g_mime_content_type_to_string (content_type));
1013     }
1014
1015     printf ("\fpart}\n");
1016 }
1017
1018 static notmuch_status_t
1019 show_message_body (const char *filename)
1020 {
1021     GMimeStream *stream = NULL;
1022     GMimeParser *parser = NULL;
1023     GMimeMessage *mime_message = NULL;
1024     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
1025     static int initialized = 0;
1026     FILE *file = NULL;
1027     int part_count = 0;
1028
1029     if (! initialized) {
1030         g_mime_init (0);
1031         initialized = 1;
1032     }
1033
1034     file = fopen (filename, "r");
1035     if (! file) {
1036         fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
1037         ret = NOTMUCH_STATUS_FILE_ERROR;
1038         goto DONE;
1039     }
1040
1041     stream = g_mime_stream_file_new (file);
1042     g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
1043
1044     parser = g_mime_parser_new_with_stream (stream);
1045
1046     mime_message = g_mime_parser_construct_message (parser);
1047
1048     show_message_part (g_mime_message_get_mime_part (mime_message),
1049                        &part_count);
1050
1051   DONE:
1052     if (mime_message)
1053         g_object_unref (mime_message);
1054
1055     if (parser)
1056         g_object_unref (parser);
1057
1058     if (stream)
1059         g_object_unref (stream);
1060
1061     if (file)
1062         fclose (file);
1063
1064     return ret;
1065 }
1066
1067 static int
1068 show_command (void *ctx, unused (int argc), unused (char *argv[]))
1069 {
1070     void *local = talloc_new (ctx);
1071     char *query_string;
1072     notmuch_database_t *notmuch = NULL;
1073     notmuch_query_t *query = NULL;
1074     notmuch_messages_t *messages;
1075     notmuch_message_t *message;
1076     int ret = 0;
1077
1078     const char *headers[] = {
1079         "Subject", "From", "To", "Cc", "Bcc", "Date"
1080     };
1081     const char *name, *value;
1082     unsigned int i;
1083
1084     notmuch = notmuch_database_open (NULL);
1085     if (notmuch == NULL) {
1086         ret = 1;
1087         goto DONE;
1088     }
1089
1090     query_string = query_string_from_args (local, argc, argv);
1091     if (query_string == NULL) {
1092         fprintf (stderr, "Out of memory\n");
1093         ret = 1;
1094         goto DONE;
1095     }
1096
1097     query = notmuch_query_create (notmuch, query_string);
1098     if (query == NULL) {
1099         fprintf (stderr, "Out of memory\n");
1100         ret = 1;
1101         goto DONE;
1102     }
1103
1104     for (messages = notmuch_query_search_messages (query);
1105          notmuch_messages_has_more (messages);
1106          notmuch_messages_advance (messages))
1107     {
1108         message = notmuch_messages_get (messages);
1109
1110         printf ("\fmessage{ id:%s filename:%s\n",
1111                 notmuch_message_get_message_id (message),
1112                 notmuch_message_get_filename (message));
1113
1114         printf ("\fheader{\n");
1115
1116         printf ("%s\n", _get_one_line_summary (local, message));
1117
1118         printf ("%s\n", notmuch_message_get_header (message, "subject"));
1119
1120         for (i = 0; i < ARRAY_SIZE (headers); i++) {
1121             name = headers[i];
1122             value = notmuch_message_get_header (message, name);
1123             if (value)
1124                 printf ("%s: %s\n", name, value);
1125         }
1126
1127         printf ("\fheader}\n");
1128         printf ("\fbody{\n");
1129
1130         show_message_body (notmuch_message_get_filename (message));
1131
1132         printf ("\fbody}\n");
1133
1134         printf ("\fmessage}\n");
1135
1136         notmuch_message_destroy (message);
1137     }
1138
1139   DONE:
1140     if (local)
1141         talloc_free (local);
1142
1143     if (query)
1144         notmuch_query_destroy (query);
1145
1146     if (notmuch)
1147         notmuch_database_close (notmuch);
1148
1149     return ret;
1150 }
1151
1152 static int
1153 tag_command (void *ctx, unused (int argc), unused (char *argv[]))
1154 {
1155     void *local;
1156     int *add_tags, *remove_tags;
1157     int add_tags_count = 0;
1158     int remove_tags_count = 0;
1159     char *query_string;
1160     notmuch_database_t *notmuch = NULL;
1161     notmuch_query_t *query;
1162     notmuch_messages_t *messages;
1163     notmuch_message_t *message;
1164     int ret = 0;
1165     int i;
1166
1167     local = talloc_new (ctx);
1168     if (local == NULL) {
1169         ret = 1;
1170         goto DONE;
1171     }
1172
1173     add_tags = talloc_size (local, argc * sizeof (int));
1174     if (add_tags == NULL) {
1175         ret = 1;
1176         goto DONE;
1177     }
1178
1179     remove_tags = talloc_size (local, argc * sizeof (int));
1180     if (remove_tags == NULL) {
1181         ret = 1;
1182         goto DONE;
1183     }
1184
1185     for (i = 0; i < argc; i++) {
1186         if (strcmp (argv[i], "--") == 0) {
1187             i++;
1188             break;
1189         }
1190         if (argv[i][0] == '+') {
1191             add_tags[add_tags_count++] = i;
1192         } else if (argv[i][0] == '-') {
1193             remove_tags[remove_tags_count++] = i;
1194         } else {
1195             break;
1196         }
1197     }
1198
1199     if (add_tags_count == 0 && remove_tags_count == 0) {
1200         fprintf (stderr, "Error: 'notmuch tag' requires at least one tag to add or remove.\n");
1201         ret = 1;
1202         goto DONE;
1203     }
1204
1205     if (i == argc) {
1206         fprintf (stderr, "Error: 'notmuch tag' requires at least one search term.\n");
1207         ret = 1;
1208         goto DONE;
1209     }
1210
1211     notmuch = notmuch_database_open (NULL);
1212     if (notmuch == NULL) {
1213         ret = 1;
1214         goto DONE;
1215     }
1216
1217     query_string = query_string_from_args (local, argc - i, &argv[i]);
1218
1219     query = notmuch_query_create (notmuch, query_string);
1220     if (query == NULL) {
1221         fprintf (stderr, "Out of memory.\n");
1222         ret = 1;
1223         goto DONE;
1224     }
1225
1226     for (messages = notmuch_query_search_messages (query);
1227          notmuch_messages_has_more (messages);
1228          notmuch_messages_advance (messages))
1229     {
1230         message = notmuch_messages_get (messages);
1231
1232         notmuch_message_freeze (message);
1233
1234         for (i = 0; i < remove_tags_count; i++)
1235             notmuch_message_remove_tag (message,
1236                                         argv[remove_tags[i]] + 1);
1237
1238         for (i = 0; i < add_tags_count; i++)
1239             notmuch_message_add_tag (message, argv[add_tags[i]] + 1);
1240
1241         notmuch_message_thaw (message);
1242
1243         notmuch_message_destroy (message);
1244     }
1245
1246     notmuch_query_destroy (query);
1247
1248   DONE:
1249     if (notmuch)
1250         notmuch_database_close (notmuch);
1251
1252     talloc_free (local);
1253
1254     return ret;
1255 }
1256
1257 static int
1258 dump_command (unused (void *ctx), int argc, char *argv[])
1259 {
1260     FILE *output = NULL;
1261     notmuch_database_t *notmuch = NULL;
1262     notmuch_query_t *query;
1263     notmuch_messages_t *messages;
1264     notmuch_message_t *message;
1265     notmuch_tags_t *tags;
1266     int ret = 0;
1267
1268     if (argc) {
1269         output = fopen (argv[0], "w");
1270         if (output == NULL) {
1271             fprintf (stderr, "Error opening %s for writing: %s\n",
1272                      argv[0], strerror (errno));
1273             ret = 1;
1274             goto DONE;
1275         }
1276     } else {
1277         output = stdout;
1278     }
1279
1280     notmuch = notmuch_database_open (NULL);
1281     if (notmuch == NULL) {
1282         ret = 1;
1283         goto DONE;
1284     }
1285
1286     query = notmuch_query_create (notmuch, "");
1287     if (query == NULL) {
1288         fprintf (stderr, "Out of memory\n");
1289         ret = 1;
1290         goto DONE;
1291     }
1292
1293     notmuch_query_set_sort (query, NOTMUCH_SORT_MESSAGE_ID);
1294
1295     for (messages = notmuch_query_search_messages (query);
1296          notmuch_messages_has_more (messages);
1297          notmuch_messages_advance (messages))
1298     {
1299         int first = 1;
1300         message = notmuch_messages_get (messages);
1301
1302         fprintf (output,
1303                  "%s (", notmuch_message_get_message_id (message));
1304
1305         for (tags = notmuch_message_get_tags (message);
1306              notmuch_tags_has_more (tags);
1307              notmuch_tags_advance (tags))
1308         {
1309             if (! first)
1310                 fprintf (output, " ");
1311
1312             fprintf (output, "%s", notmuch_tags_get (tags));
1313
1314             first = 0;
1315         }
1316
1317         fprintf (output, ")\n");
1318
1319         notmuch_message_destroy (message);
1320     }
1321
1322     notmuch_query_destroy (query);
1323
1324   DONE:
1325     if (notmuch)
1326         notmuch_database_close (notmuch);
1327     if (output && output != stdout)
1328         fclose (output);
1329
1330     return ret;
1331 }
1332
1333 static int
1334 restore_command (unused (void *ctx), int argc, char *argv[])
1335 {
1336     FILE *input = NULL;
1337     notmuch_database_t *notmuch = NULL;
1338     char *line = NULL;
1339     size_t line_size;
1340     ssize_t line_len;
1341     regex_t regex;
1342     int rerr;
1343     int ret = 0;
1344
1345     if (argc) {
1346         input = fopen (argv[0], "r");
1347         if (input == NULL) {
1348             fprintf (stderr, "Error opening %s for reading: %s\n",
1349                      argv[0], strerror (errno));
1350             ret = 1;
1351             goto DONE;
1352         }
1353     } else {
1354         printf ("No filename given. Reading dump from stdin.\n");
1355         input = stdin;
1356     }
1357
1358     notmuch = notmuch_database_open (NULL);
1359     if (notmuch == NULL) {
1360         ret = 1;
1361         goto DONE;
1362     }
1363
1364     /* Dump output is one line per message. We match a sequence of
1365      * non-space characters for the message-id, then one or more
1366      * spaces, then a list of space-separated tags as a sequence of
1367      * characters within literal '(' and ')'. */
1368     xregcomp (&regex,
1369               "^([^ ]+) \\(([^)]*)\\)$",
1370               REG_EXTENDED);
1371
1372     while ((line_len = getline (&line, &line_size, input)) != -1) {
1373         regmatch_t match[3];
1374         char *message_id, *tags, *tag, *next;
1375         notmuch_message_t *message;
1376         notmuch_status_t status;
1377
1378         chomp_newline (line);
1379
1380         rerr = xregexec (&regex, line, 3, match, 0);
1381         if (rerr == REG_NOMATCH)
1382         {
1383             fprintf (stderr, "Warning: Ignoring invalid input line: %s\n",
1384                      line);
1385             continue;
1386         }
1387
1388         message_id = xstrndup (line + match[1].rm_so,
1389                                match[1].rm_eo - match[1].rm_so);
1390         tags = xstrndup (line + match[2].rm_so,
1391                          match[2].rm_eo - match[2].rm_so);
1392
1393         if (strlen (tags)) {
1394
1395             message = notmuch_database_find_message (notmuch, message_id);
1396             if (message == NULL) {
1397                 fprintf (stderr, "Warning: Cannot apply tags to missing message: %s\n",
1398                          message_id);
1399                 goto NEXT_LINE;
1400             }
1401
1402             notmuch_message_freeze (message);
1403
1404             notmuch_message_remove_all_tags (message);
1405
1406             next = tags;
1407             while (next) {
1408                 tag = strsep (&next, " ");
1409                 if (*tag == '\0')
1410                     continue;
1411                 status = notmuch_message_add_tag (message, tag);
1412                 if (status) {
1413                     fprintf (stderr,
1414                              "Error applying tag %s to message %s:\n",
1415                              tag, message_id);
1416                     fprintf (stderr, "%s\n",
1417                              notmuch_status_to_string (status));
1418                 }
1419             }
1420
1421             notmuch_message_thaw (message);
1422             notmuch_message_destroy (message);
1423         }
1424       NEXT_LINE:
1425         free (message_id);
1426         free (tags);
1427     }
1428
1429     regfree (&regex);
1430
1431   DONE:
1432     if (line)
1433         free (line);
1434     if (notmuch)
1435         notmuch_database_close (notmuch);
1436     if (input && input != stdin)
1437         fclose (input);
1438
1439     return ret;
1440 }
1441
1442 static int
1443 help_command (void *ctx, int argc, char *argv[]);
1444
1445 command_t commands[] = {
1446     { "setup", setup_command,
1447       "Interactively setup notmuch for first use.",
1448       "\t\tThe setup command is the first command you will run in order\n"
1449       "\t\tto start using notmuch. It will prompt you for the directory\n"
1450       "\t\tcontaining your email archives, and will then proceed to build\n"
1451       "\t\ta database to allow fast searching of that mail.\n\n"
1452       "\t\tInvoking notmuch with no command argument will run setup if\n"
1453       "\t\tthe setup command has not previously been completed." },
1454     { "new", new_command,
1455       "Find and import any new messages.",
1456       "\t\tScans all sub-directories of the database, adding new messages\n"
1457       "\t\tthat are found. Each new message will be tagged as both\n"
1458       "\t\t\"inbox\" and \"unread\".\n"
1459       "\n"
1460       "\t\tNote: \"notmuch new\" will skip any read-only directories,\n"
1461       "\t\tso you can use that to mark directories that will not\n"
1462       "\t\treceive any new mail (and make \"notmuch new\" faster)." },
1463     { "search", search_command,
1464       "<search-term> [...]\n\n"
1465       "\t\tSearch for threads matching the given search terms.",
1466       "\t\tNote that the individual mail messages will be matched\n"
1467       "\t\tagainst the search terms, but the results will be the\n"
1468       "\t\tthreads containing the matched messages.\n\n"
1469       "\t\tCurrently, in addition to free text (and quoted phrases)\n"
1470       "\t\twhich match terms appearing anywhere within an email,\n"
1471       "\t\tthe following prefixes can be used to search specific\n"
1472       "\t\tportions of an email, (where <brackets> indicate user-\n"
1473       "\t\tsupplied values):\n\n"
1474       "\t\t\tfrom:<name-or-address>\n"
1475       "\t\t\tto:<name-or-address>\n"
1476       "\t\t\tsubject:<word-or-quoted-phrase>\n"
1477       "\t\t\ttag:<tag>\n"
1478       "\t\t\tid:<message-id>\n"
1479       "\t\t\tthread:<thread-id>\n\n"
1480       "\t\tThe from: prefix is used to match the name or address of\n"
1481       "\t\tthe sender of an email message.\n\n"
1482       "\t\tThe to: prefix is used to match the names or addresses of\n"
1483       "\t\tany recipient of an email message, (whether To, Cc, or Bcc).\n\n"
1484       "\t\tAny term prefixed with subject: will match only text from\n"
1485       "\t\tthe subject of an email. Quoted phrases are supported when\n"
1486       "\t\tsearching with: subject:\"this is a phrase\".\n\n"
1487       "\t\tValid tag values include \"inbox\" and \"unread\" by default\n"
1488       "\t\tfor new messages added by \"notmuch new\" as well as any other\n"
1489       "\t\ttag values added manually with \"notmuch tag\".\n\n"
1490       "\t\tMessage ID values are the literal contents of the Message-ID:\n"
1491       "\t\theader of email messages, but without the '<','>' delimiters.\n\n"
1492       "\t\tThread ID values are generated internally by notmuch but can\n"
1493       "\t\tbe seen in the output of \"notmuch search\" for example.\n\n"
1494       "\t\tIn addition to individual terms, multiple terms can be\n"
1495       "\t\tcombined with Boolean operators (\"and\", \"or\", \"not\", etc.).\n"
1496       "\t\tEach term in the query will be implicitly connected by a\n"
1497       "\t\tlogical AND if no explicit operator is provided, (except\n"
1498       "\t\tthat terms with a common prefix will be implicitly combined\n"
1499       "\t\twith OR until we get Xapian defect #402 fixed).\n\n"
1500       "\t\tParentheses can also be used to control the combination of\n"
1501       "\t\tthe Boolean operators, but will have to be protected from\n"
1502       "\t\tinterpretation by the shell, (such as by putting quotation\n"
1503       "\t\tmarks around any parenthesized expression)." },
1504     { "show", show_command,
1505       "<search-terms> [...]\n\n"
1506       "\t\tShows all messages matching the search terms.",
1507       "\t\tSee the documentation of \"notmuch search\" for details\n"
1508       "\t\tof the supported syntax of search terms.\n\n"
1509       "\t\tA common use of \"notmuch show\" is to display a single\n"
1510       "\t\tthread of email messages. For this, use a search term of\n"
1511       "\t\t\"thread:<thread-id>\" as can be seen in the first column\n"
1512       "\t\tof output from the \"notmuch search\" command.\n\n"
1513       "\t\tAll messages will be displayed in date order. The output\n"
1514       "\t\tformat is plain-text, with all text-content MIME parts\n"
1515       "\t\tdecoded. Various components in the output, ('message',\n"
1516       "\t\t'header', 'body', 'attachment', and MIME 'part') will be\n"
1517       "\t\tdelimited by easily-parsed markers. Each marker consists\n"
1518       "\t\tof a Control-L character (ASCII decimal 12), the name of\n"
1519       "\t\tthe marker, and then either an opening or closing brace,\n"
1520       "\t\t'{' or '}' to either open or close the component."},
1521     { "tag", tag_command,
1522       "+<tag>|-<tag> [...] [--] <search-term> [...]\n\n"
1523       "\t\tAdd/remove tags for all messages matching the search terms.",
1524       "\t\tThe search terms are handled exactly as in 'search' so one\n"
1525       "\t\tcan use that command first to see what will be modified.\n\n"
1526       "\t\tTags prefixed by '+' are added while those prefixed by '-' are\n"
1527       "\t\tremoved. For each message, tag removal is before tag addition.\n\n"
1528       "\t\tThe beginning of <search-terms> is recognized by the first\n"
1529       "\t\targument that begins with neither '+' nor '-'. Support for\n"
1530       "\t\tan initial search term beginning with '+' or '-' is provided\n"
1531       "\t\tby allowing the user to specify a \"--\" argument to separate\n"
1532       "\t\tthe tags from the search terms.\n\n"
1533       "\t\tNote: If you run \"notmuch new\" between reading a thread with\n"
1534       "\t\t\"notmuch show\" and removing the \"inbox\" tag for that thread\n"
1535       "\t\twith \"notmuch tag\" then you create the possibility of moving\n"
1536       "\t\tsome messages from that thread out of your inbox without ever\n"
1537       "\t\treading them. The easiest way to avoid this problem is to not\n"
1538       "\t\trun \"notmuch new\" between reading and removing tags." },
1539     { "dump", dump_command,
1540       "[<filename>]\n\n"
1541       "\t\tCreate a plain-text dump of the tags for each message.",
1542       "\t\tOutput is to the given filename, if any, or to stdout.\n"
1543       "\t\tThese tags are the only data in the notmuch database\n"
1544       "\t\tthat can't be recreated from the messages themselves.\n"
1545       "\t\tThe output of notmuch dump is therefore the only\n"
1546       "\t\tcritical thing to backup (and much more friendly to\n"
1547       "\t\tincremental backup than the native database files.)" },
1548     { "restore", restore_command,
1549       "<filename>\n\n"
1550       "\t\tRestore the tags from the given dump file (see 'dump').",
1551       "\t\tNote: The dump file format is specifically chosen to be\n"
1552       "\t\tcompatible with the format of files produced by sup-dump.\n"
1553       "\t\tSo if you've previously been using sup for mail, then the\n"
1554       "\t\t\"notmuch restore\" command provides you a way to import\n"
1555       "\t\tall of your tags (or labels as sup calls them)." },
1556     { "help", help_command,
1557       "[<command>]\n\n"
1558       "\t\tThis message, or more detailed help for the named command.",
1559       "\t\tExcept in this case, where there's not much more detailed\n"
1560       "\t\thelp available." }
1561 };
1562
1563 static void
1564 usage (void)
1565 {
1566     command_t *command;
1567     unsigned int i;
1568
1569     fprintf (stderr, "Usage: notmuch <command> [args...]\n");
1570     fprintf (stderr, "\n");
1571     fprintf (stderr, "Where <command> and [args...] are as follows:\n");
1572     fprintf (stderr, "\n");
1573
1574     for (i = 0; i < ARRAY_SIZE (commands); i++) {
1575         command = &commands[i];
1576
1577         fprintf (stderr, "\t%s\t%s\n\n", command->name, command->summary);
1578     }
1579
1580     fprintf (stderr, "Use \"notmuch help <command>\" for more details on each command.\n\n");
1581 }
1582
1583 static int
1584 help_command (unused (void *ctx), int argc, char *argv[])
1585 {
1586     command_t *command;
1587     unsigned int i;
1588
1589     if (argc == 0) {
1590         fprintf (stderr, "The notmuch mail system.\n\n");
1591         usage ();
1592         return 0;
1593     }
1594
1595     for (i = 0; i < ARRAY_SIZE (commands); i++) {
1596         command = &commands[i];
1597
1598         if (strcmp (argv[0], command->name) == 0) {
1599             fprintf (stderr, "Help for \"notmuch %s\":\n\n", argv[0]);
1600             fprintf (stderr, "\t%s\t%s\n\n%s\n\n", command->name,
1601                      command->summary, command->documentation);
1602             return 0;
1603         }
1604     }
1605
1606     fprintf (stderr,
1607              "\nSorry, %s is not a known command. There's not much I can do to help.\n\n",
1608              argv[0]);
1609     return 1;
1610 }
1611     
1612 int
1613 main (int argc, char *argv[])
1614 {
1615     void *local = talloc_new (NULL);
1616     command_t *command;
1617     unsigned int i;
1618
1619     if (argc == 1)
1620         return setup_command (local, 0, NULL);
1621
1622     for (i = 0; i < ARRAY_SIZE (commands); i++) {
1623         command = &commands[i];
1624
1625         if (strcmp (argv[1], command->name) == 0)
1626             return (command->function) (local, argc - 2, &argv[2]);
1627     }
1628
1629     /* Don't complain about "help" being an unknown command when we're
1630        about to provide exactly what's wanted anyway. */
1631     fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
1632              argv[1]);
1633
1634     talloc_free (local);
1635
1636     return 1;
1637 }