Add public notmuch_thread_get_subject
[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 "notmuch.h"
27
28 /* This is separate from notmuch-private.h because we're trying to
29  * keep notmuch.c from looking into any internals, (which helps us
30  * develop notmuch.h into a plausible library interface).
31  */
32 #include "xutil.h"
33
34 #include <stddef.h>
35 #include <string.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include <unistd.h>
39 #include <dirent.h>
40 #include <errno.h>
41
42 #include <talloc.h>
43
44 #define unused(x) x __attribute__ ((unused))
45
46 /* There's no point in continuing when we've detected that we've done
47  * something wrong internally (as opposed to the user passing in a
48  * bogus value).
49  *
50  * Note that __location__ comes from talloc.h.
51  */
52 #define INTERNAL_ERROR(format, ...)                     \
53     do {                                                \
54         fprintf(stderr,                                 \
55                 "Internal error: " format " (%s)\n",    \
56                 ##__VA_ARGS__, __location__);           \
57         exit (1);                                       \
58     } while (0)
59
60 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
61
62 typedef int (*command_function_t) (int argc, char *argv[]);
63
64 typedef struct command {
65     const char *name;
66     command_function_t function;
67     const char *usage;
68 } command_t;
69
70 typedef struct {
71     int ignore_read_only_directories;
72     int saw_read_only_directory;
73
74     int total_files;
75     int processed_files;
76     int added_messages;
77     struct timeval tv_start;
78 } add_files_state_t;
79
80 static void
81 chomp_newline (char *str)
82 {
83     if (str && str[strlen(str)-1] == '\n')
84         str[strlen(str)-1] = '\0';
85 }
86
87 /* Compute the number of seconds elapsed from start to end. */
88 static double
89 tv_elapsed (struct timeval start, struct timeval end)
90 {
91     return ((end.tv_sec - start.tv_sec) +
92             (end.tv_usec - start.tv_usec) / 1e6);
93 }
94
95 static void
96 print_formatted_seconds (double seconds)
97 {
98     int hours;
99     int minutes;
100
101     if (seconds < 1) {
102         printf ("almost no time");
103         return;
104     }
105
106     if (seconds > 3600) {
107         hours = (int) seconds / 3600;
108         printf ("%dh ", hours);
109         seconds -= hours * 3600;
110     }
111
112     if (seconds > 60) {
113         minutes = (int) seconds / 60;
114         printf ("%dm ", minutes);
115         seconds -= minutes * 60;
116     }
117
118     printf ("%ds", (int) seconds);
119 }
120
121 static void
122 add_files_print_progress (add_files_state_t *state)
123 {
124     struct timeval tv_now;
125     double elapsed_overall, rate_overall;
126
127     gettimeofday (&tv_now, NULL);
128
129     elapsed_overall = tv_elapsed (state->tv_start, tv_now);
130     rate_overall = (state->processed_files) / elapsed_overall;
131
132     printf ("Processed %d", state->processed_files);
133
134     if (state->total_files) {
135         printf (" of %d files (", state->total_files);
136         print_formatted_seconds ((state->total_files - state->processed_files) /
137                                  rate_overall);
138         printf (" remaining).      \r");
139     } else {
140         printf (" files (%d files/sec.)    \r", (int) rate_overall);
141     }
142
143     fflush (stdout);
144 }
145
146 /* Examine 'path' recursively as follows:
147  *
148  *   o Ask the filesystem for the mtime of 'path' (path_mtime)
149  *
150  *   o Ask the database for its timestamp of 'path' (path_dbtime)
151  *
152  *   o If 'path_mtime' > 'path_dbtime'
153  *
154  *       o For each regular file in 'path' with mtime newer than the
155  *         'path_dbtime' call add_message to add the file to the
156  *         database.
157  *
158  *       o For each sub-directory of path, recursively call into this
159  *         same function.
160  *
161  *   o Tell the database to update its time of 'path' to 'path_mtime'
162  *
163  * The 'struct stat *st' must point to a structure that has already
164  * been initialized for 'path' by calling stat().
165  */
166 static notmuch_status_t
167 add_files_recursive (notmuch_database_t *notmuch,
168                      const char *path,
169                      struct stat *st,
170                      add_files_state_t *state)
171 {
172     DIR *dir = NULL;
173     struct dirent *e, *entry = NULL;
174     int entry_length;
175     int err;
176     char *next = NULL;
177     time_t path_mtime, path_dbtime;
178     notmuch_status_t status, ret = NOTMUCH_STATUS_SUCCESS;
179
180     /* If we're told to, we bail out on encountering a read-only
181      * directory, (with this being a clear clue from the user to
182      * Notmuch that new mail won't be arriving there and we need not
183      * look. */
184     if (state->ignore_read_only_directories &&
185         (st->st_mode & S_IWUSR) == 0)
186     {
187         state->saw_read_only_directory = TRUE;
188         goto DONE;
189     }
190
191     path_mtime = st->st_mtime;
192
193     path_dbtime = notmuch_database_get_timestamp (notmuch, path);
194
195     dir = opendir (path);
196     if (dir == NULL) {
197         fprintf (stderr, "Error opening directory %s: %s\n",
198                  path, strerror (errno));
199         ret = NOTMUCH_STATUS_FILE_ERROR;
200         goto DONE;
201     }
202
203     entry_length = offsetof (struct dirent, d_name) +
204         pathconf (path, _PC_NAME_MAX) + 1;
205     entry = malloc (entry_length);
206
207     while (1) {
208         err = readdir_r (dir, entry, &e);
209         if (err) {
210             fprintf (stderr, "Error reading directory: %s\n",
211                      strerror (errno));
212             ret = NOTMUCH_STATUS_FILE_ERROR;
213             goto DONE;
214         }
215
216         if (e == NULL)
217             break;
218
219         /* If this directory hasn't been modified since the last
220          * add_files, then we only need to look further for
221          * sub-directories. */
222         if (path_mtime <= path_dbtime && entry->d_type != DT_DIR)
223             continue;
224
225         /* Ignore special directories to avoid infinite recursion.
226          * Also ignore the .notmuch directory.
227          */
228         /* XXX: Eventually we'll want more sophistication to let the
229          * user specify files to be ignored. */
230         if (strcmp (entry->d_name, ".") == 0 ||
231             strcmp (entry->d_name, "..") == 0 ||
232             strcmp (entry->d_name, ".notmuch") ==0)
233         {
234             continue;
235         }
236
237         next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name);
238
239         if (stat (next, st)) {
240             fprintf (stderr, "Error reading %s: %s\n",
241                      next, strerror (errno));
242             ret = NOTMUCH_STATUS_FILE_ERROR;
243             continue;
244         }
245
246         if (S_ISREG (st->st_mode)) {
247             /* If the file hasn't been modified since the last
248              * add_files, then we need not look at it. */
249             if (st->st_mtime > path_dbtime) {
250                 state->processed_files++;
251
252                 status = notmuch_database_add_message (notmuch, next);
253                 switch (status) {
254                     /* success */
255                     case NOTMUCH_STATUS_SUCCESS:
256                         state->added_messages++;
257                         break;
258                     /* Non-fatal issues (go on to next file) */
259                     case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
260                         /* Stay silent on this one. */
261                         break;
262                     case NOTMUCH_STATUS_FILE_NOT_EMAIL:
263                         fprintf (stderr, "Note: Ignoring non-mail file: %s\n",
264                                  next);
265                         break;
266                     /* Fatal issues. Don't process anymore. */
267                     case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
268                     case NOTMUCH_STATUS_OUT_OF_MEMORY:
269                         fprintf (stderr, "Error: %s. Halting processing.\n",
270                                  notmuch_status_to_string (status));
271                         ret = status;
272                         goto DONE;
273                     default:
274                     case NOTMUCH_STATUS_FILE_ERROR:
275                     case NOTMUCH_STATUS_NULL_POINTER:
276                     case NOTMUCH_STATUS_TAG_TOO_LONG:
277                     case NOTMUCH_STATUS_LAST_STATUS:
278                         INTERNAL_ERROR ("add_message returned unexpected value: %d",  status);
279                         goto DONE;
280                 }
281                 if (state->processed_files % 1000 == 0)
282                     add_files_print_progress (state);
283             }
284         } else if (S_ISDIR (st->st_mode)) {
285             status = add_files_recursive (notmuch, next, st, state);
286             if (status && ret == NOTMUCH_STATUS_SUCCESS)
287                 ret = status;
288         }
289
290         talloc_free (next);
291         next = NULL;
292     }
293
294     status = notmuch_database_set_timestamp (notmuch, path, path_mtime);
295     if (status && ret == NOTMUCH_STATUS_SUCCESS)
296         ret = status;
297
298   DONE:
299     if (next)
300         talloc_free (next);
301     if (entry)
302         free (entry);
303     if (dir)
304         closedir (dir);
305
306     return ret;
307 }
308
309 /* This is the top-level entry point for add_files. It does a couple
310  * of error checks, and then calls into the recursive function,
311  * (avoiding the repeating of these error checks at every
312  * level---which would be useless becaues we already do a stat() at
313  * the level above). */
314 static notmuch_status_t
315 add_files (notmuch_database_t *notmuch,
316            const char *path,
317            add_files_state_t *state)
318 {
319     struct stat st;
320
321     if (stat (path, &st)) {
322         fprintf (stderr, "Error reading directory %s: %s\n",
323                  path, strerror (errno));
324         return NOTMUCH_STATUS_FILE_ERROR;
325     }
326
327     if (! S_ISDIR (st.st_mode)) {
328         fprintf (stderr, "Error: %s is not a directory.\n", path);
329         return NOTMUCH_STATUS_FILE_ERROR;
330     }
331
332     return add_files_recursive (notmuch, path, &st, state);
333 }
334
335 /* Recursively count all regular files in path and all sub-direcotries
336  * of path.  The result is added to *count (which should be
337  * initialized to zero by the top-level caller before calling
338  * count_files). */
339 static void
340 count_files (const char *path, int *count)
341 {
342     DIR *dir;
343     struct dirent *e, *entry = NULL;
344     int entry_length;
345     int err;
346     char *next;
347     struct stat st;
348
349     dir = opendir (path);
350
351     if (dir == NULL) {
352         fprintf (stderr, "Warning: failed to open directory %s: %s\n",
353                  path, strerror (errno));
354         goto DONE;
355     }
356
357     entry_length = offsetof (struct dirent, d_name) +
358         pathconf (path, _PC_NAME_MAX) + 1;
359     entry = malloc (entry_length);
360
361     while (1) {
362         err = readdir_r (dir, entry, &e);
363         if (err) {
364             fprintf (stderr, "Error reading directory: %s\n",
365                      strerror (errno));
366             free (entry);
367             goto DONE;
368         }
369
370         if (e == NULL)
371             break;
372
373         /* Ignore special directories to avoid infinite recursion.
374          * Also ignore the .notmuch directory.
375          */
376         /* XXX: Eventually we'll want more sophistication to let the
377          * user specify files to be ignored. */
378         if (strcmp (entry->d_name, ".") == 0 ||
379             strcmp (entry->d_name, "..") == 0 ||
380             strcmp (entry->d_name, ".notmuch") == 0)
381         {
382             continue;
383         }
384
385         if (asprintf (&next, "%s/%s", path, entry->d_name) == -1) {
386             next = NULL;
387             fprintf (stderr, "Error descending from %s to %s: Out of memory\n",
388                      path, entry->d_name);
389             continue;
390         }
391
392         stat (next, &st);
393
394         if (S_ISREG (st.st_mode)) {
395             *count = *count + 1;
396             if (*count % 1000 == 0) {
397                 printf ("Found %d files so far.\r", *count);
398                 fflush (stdout);
399             }
400         } else if (S_ISDIR (st.st_mode)) {
401             count_files (next, count);
402         }
403
404         free (next);
405     }
406
407   DONE:
408     if (entry)
409         free (entry);
410
411     closedir (dir);
412 }
413
414 static int
415 setup_command (unused (int argc), unused (char *argv[]))
416 {
417     notmuch_database_t *notmuch = NULL;
418     char *default_path, *mail_directory = NULL;
419     size_t line_size;
420     int count;
421     add_files_state_t add_files_state;
422     double elapsed;
423     struct timeval tv_now;
424     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
425
426     printf ("Welcome to notmuch!\n\n");
427
428     printf ("The goal of notmuch is to help you manage and search your collection of\n"
429             "email, and to efficiently keep up with the flow of email as it comes in.\n\n");
430
431     printf ("Notmuch needs to know the top-level directory of your email archive,\n"
432             "(where you already have mail stored and where messages will be delivered\n"
433             "in the future). This directory can contain any number of sub-directories\n"
434             "and primarily just files with indvidual email messages (eg. maildir or mh\n"
435             "archives are perfect). If there are other, non-email files (such as\n"
436             "indexes maintained by other email programs) then notmuch will do its\n"
437             "best to detect those and ignore them.\n\n");
438
439     printf ("Mail storage that uses mbox format, (where one mbox file contains many\n"
440             "messages), will not work with notmuch. If that's how your mail is currently\n"
441             "stored, we recommend you first convert it to maildir format with a utility\n"
442             "such as mb2md. In that case, press Control-C now and run notmuch again\n"
443             "once the conversion is complete.\n\n");
444
445
446     default_path = notmuch_database_default_path ();
447     printf ("Top-level mail directory [%s]: ", default_path);
448     fflush (stdout);
449
450     getline (&mail_directory, &line_size, stdin);
451     chomp_newline (mail_directory);
452
453     printf ("\n");
454
455     if (mail_directory == NULL || strlen (mail_directory) == 0) {
456         if (mail_directory)
457             free (mail_directory);
458         mail_directory = default_path;
459     } else {
460         /* XXX: Instead of telling the user to use an environment
461          * variable here, we should really be writing out a configuration
462          * file and loading that on the next run. */
463         if (strcmp (mail_directory, default_path)) {
464             printf ("Note: Since you are not using the default path, you will want to set\n"
465                     "the NOTMUCH_BASE environment variable to %s so that\n"
466                     "future calls to notmuch commands will know where to find your mail.\n",
467                     mail_directory);
468             printf ("For example, if you are using bash for your shell, add:\n\n");
469             printf ("\texport NOTMUCH_BASE=%s\n\n", mail_directory);
470             printf ("to your ~/.bashrc file.\n\n");
471         }
472         free (default_path);
473     }
474
475     notmuch = notmuch_database_create (mail_directory);
476     if (notmuch == NULL) {
477         fprintf (stderr, "Failed to create new notmuch database at %s\n",
478                  mail_directory);
479         ret = NOTMUCH_STATUS_FILE_ERROR;
480         goto DONE;
481     }
482
483     printf ("OK. Let's take a look at the mail we can find in the directory\n");
484     printf ("%s ...\n", mail_directory);
485
486     count = 0;
487     count_files (mail_directory, &count);
488
489     printf ("Found %d total files. That's not much mail.\n\n", count);
490
491     printf ("Next, we'll inspect the messages and create a database of threads:\n");
492
493     add_files_state.ignore_read_only_directories = FALSE;
494     add_files_state.saw_read_only_directory = FALSE;
495     add_files_state.total_files = count;
496     add_files_state.processed_files = 0;
497     add_files_state.added_messages = 0;
498     gettimeofday (&add_files_state.tv_start, NULL);
499
500     ret = add_files (notmuch, mail_directory, &add_files_state);
501
502     gettimeofday (&tv_now, NULL);
503     elapsed = tv_elapsed (add_files_state.tv_start,
504                           tv_now);
505     printf ("Processed %d %s in ", add_files_state.processed_files,
506             add_files_state.processed_files == 1 ?
507             "file" : "total files");
508     print_formatted_seconds (elapsed);
509     if (elapsed > 1) {
510         printf (" (%d files/sec.).                 \n",
511                 (int) (add_files_state.processed_files / elapsed));
512     } else {
513         printf (".                    \n");
514     }
515     if (add_files_state.added_messages) {
516         printf ("Added %d %s to the database.\n\n",
517                 add_files_state.added_messages,
518                 add_files_state.added_messages == 1 ?
519                 "message" : "unique messages");
520     }
521
522     printf ("When new mail is delivered to %s in the future,\n"
523             "run \"notmuch new\" to add it to the database.\n\n",
524             mail_directory);
525
526     if (ret) {
527         printf ("Note: At least one error was encountered: %s\n",
528                 notmuch_status_to_string (ret));
529     }
530
531   DONE:
532     if (mail_directory)
533         free (mail_directory);
534     if (notmuch)
535         notmuch_database_close (notmuch);
536
537     return ret;
538 }
539
540 static int
541 new_command (unused (int argc), unused (char *argv[]))
542 {
543     notmuch_database_t *notmuch;
544     const char *mail_directory;
545     add_files_state_t add_files_state;
546     double elapsed;
547     struct timeval tv_now;
548     int ret = 0;
549
550     notmuch = notmuch_database_open (NULL);
551     if (notmuch == NULL) {
552         ret = 1;
553         goto DONE;
554     }
555
556     mail_directory = notmuch_database_get_path (notmuch);
557
558     add_files_state.ignore_read_only_directories = TRUE;
559     add_files_state.saw_read_only_directory = FALSE;
560     add_files_state.total_files = 0;
561     add_files_state.processed_files = 0;
562     add_files_state.added_messages = 0;
563     gettimeofday (&add_files_state.tv_start, NULL);
564
565     ret = add_files (notmuch, mail_directory, &add_files_state);
566
567     gettimeofday (&tv_now, NULL);
568     elapsed = tv_elapsed (add_files_state.tv_start,
569                           tv_now);
570     if (add_files_state.processed_files) {
571         printf ("Processed %d %s in ", add_files_state.processed_files,
572                 add_files_state.processed_files == 1 ?
573                 "file" : "total files");
574         print_formatted_seconds (elapsed);
575         if (elapsed > 1) {
576             printf (" (%d files/sec.).                 \n",
577                     (int) (add_files_state.processed_files / elapsed));
578         } else {
579             printf (".                    \n");
580         }
581     }
582     if (add_files_state.added_messages) {
583         printf ("Added %d new %s to the database (not much, really).\n",
584                 add_files_state.added_messages,
585                 add_files_state.added_messages == 1 ?
586                 "message" : "messages");
587     } else {
588         printf ("No new mail---and that's not much.\n");
589     }
590
591     if (elapsed > 1 && ! add_files_state.saw_read_only_directory) {
592         printf ("\nTip: If you have any sub-directories that are archives (that is,\n"
593                 "they will never receive new mail), marking these directores as\n"
594                 "read-only (chmod u-w /path/to/dir) will make \"notmuch new\"\n"
595                 "much more efficient (it won't even look in those directories).\n");
596     }
597
598     if (ret) {
599         printf ("\nNote: At least one error was encountered: %s\n",
600                 notmuch_status_to_string (ret));
601     }
602
603   DONE:
604     if (notmuch)
605         notmuch_database_close (notmuch);
606
607     return ret;
608 }
609
610 static int
611 search_command (int argc, char *argv[])
612 {
613     void *local = talloc_new (NULL);
614     notmuch_database_t *notmuch = NULL;
615     notmuch_query_t *query;
616     notmuch_thread_results_t *results;
617     notmuch_thread_t *thread;
618     notmuch_tags_t *tags;
619     char *query_str;
620     int i;
621     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
622
623     notmuch = notmuch_database_open (NULL);
624     if (notmuch == NULL) {
625         ret = 1;
626         goto DONE;
627     }
628
629     /* XXX: Should add xtalloc wrappers here and use them. */
630     query_str = talloc_strdup (local, "");
631
632     for (i = 0; i < argc; i++) {
633         if (i != 0)
634             query_str = talloc_asprintf_append (query_str, " ");
635
636         query_str = talloc_asprintf_append (query_str, "%s", argv[i]);
637     }
638
639     query = notmuch_query_create (notmuch, query_str);
640     if (query == NULL) {
641         fprintf (stderr, "Out of memory\n");
642         ret = 1;
643         goto DONE;
644     }
645
646     for (results = notmuch_query_search_threads (query);
647          notmuch_thread_results_has_more (results);
648          notmuch_thread_results_advance (results))
649     {
650         int first = 1;
651
652         thread = notmuch_thread_results_get (results);
653
654         printf ("%s %s",
655                 notmuch_thread_get_thread_id (thread),
656                 _notmuch_thread_get_subject (thread));
657
658         printf (" (");
659         for (tags = notmuch_thread_get_tags (thread);
660              notmuch_tags_has_more (tags);
661              notmuch_tags_advance (tags))
662         {
663             if (! first)
664                 printf (" ");
665             printf ("%s", notmuch_tags_get (tags));
666             first = 0;
667         }
668         printf (")\n");
669
670         notmuch_thread_destroy (thread);
671     }
672
673     notmuch_query_destroy (query);
674
675   DONE:
676     if (notmuch)
677         notmuch_database_close (notmuch);
678     talloc_free (local);
679
680     return ret;
681 }
682
683 static int
684 show_command (unused (int argc), unused (char *argv[]))
685 {
686     fprintf (stderr, "Error: show is not implemented yet.\n");
687     return 1;
688 }
689
690 static int
691 dump_command (int argc, char *argv[])
692 {
693     FILE *output = NULL;
694     notmuch_database_t *notmuch = NULL;
695     notmuch_query_t *query;
696     notmuch_message_results_t *results;
697     notmuch_message_t *message;
698     notmuch_tags_t *tags;
699     int ret = 0;
700
701     if (argc) {
702         output = fopen (argv[0], "w");
703         if (output == NULL) {
704             fprintf (stderr, "Error opening %s for writing: %s\n",
705                      argv[0], strerror (errno));
706             ret = 1;
707             goto DONE;
708         }
709     } else {
710         output = stdout;
711     }
712
713     notmuch = notmuch_database_open (NULL);
714     if (notmuch == NULL) {
715         ret = 1;
716         goto DONE;
717     }
718
719     query = notmuch_query_create (notmuch, "");
720     if (query == NULL) {
721         fprintf (stderr, "Out of memory\n");
722         ret = 1;
723         goto DONE;
724     }
725
726     notmuch_query_set_sort (query, NOTMUCH_SORT_MESSAGE_ID);
727
728     for (results = notmuch_query_search_messages (query);
729          notmuch_message_results_has_more (results);
730          notmuch_message_results_advance (results))
731     {
732         int first = 1;
733         message = notmuch_message_results_get (results);
734
735         fprintf (output,
736                  "%s (", notmuch_message_get_message_id (message));
737
738         for (tags = notmuch_message_get_tags (message);
739              notmuch_tags_has_more (tags);
740              notmuch_tags_advance (tags))
741         {
742             if (! first)
743                 fprintf (output, " ");
744
745             fprintf (output, "%s", notmuch_tags_get (tags));
746
747             first = 0;
748         }
749
750         fprintf (output, ")\n");
751
752         notmuch_message_destroy (message);
753     }
754
755     notmuch_query_destroy (query);
756
757   DONE:
758     if (notmuch)
759         notmuch_database_close (notmuch);
760     if (output && output != stdout)
761         fclose (output);
762
763     return ret;
764 }
765
766 static int
767 restore_command (int argc, char *argv[])
768 {
769     FILE *input = NULL;
770     notmuch_database_t *notmuch = NULL;
771     char *line = NULL;
772     size_t line_size;
773     ssize_t line_len;
774     regex_t regex;
775     int rerr;
776     int ret = 0;
777
778     if (argc) {
779         input = fopen (argv[0], "r");
780         if (input == NULL) {
781             fprintf (stderr, "Error opening %s for reading: %s\n",
782                      argv[0], strerror (errno));
783             ret = 1;
784             goto DONE;
785         }
786     } else {
787         printf ("No filename given. Reading dump from stdin.\n");
788         input = stdin;
789     }
790
791     notmuch = notmuch_database_open (NULL);
792     if (notmuch == NULL) {
793         ret = 1;
794         goto DONE;
795     }
796
797     /* Dump output is one line per message. We match a sequence of
798      * non-space characters for the message-id, then one or more
799      * spaces, then a list of space-separated tags as a sequence of
800      * characters within literal '(' and ')'. */
801     xregcomp (&regex,
802               "^([^ ]+) \\(([^)]*)\\)$",
803               REG_EXTENDED);
804
805     while ((line_len = getline (&line, &line_size, input)) != -1) {
806         regmatch_t match[3];
807         char *message_id, *tags, *tag, *next;
808         notmuch_message_t *message;
809         notmuch_status_t status;
810
811         chomp_newline (line);
812
813         rerr = xregexec (&regex, line, 3, match, 0);
814         if (rerr == REG_NOMATCH)
815         {
816             fprintf (stderr, "Warning: Ignoring invalid input line: %s\n",
817                      line);
818             continue;
819         }
820
821         message_id = xstrndup (line + match[1].rm_so,
822                                match[1].rm_eo - match[1].rm_so);
823         tags = xstrndup (line + match[2].rm_so,
824                          match[2].rm_eo - match[2].rm_so);
825
826         if (strlen (tags)) {
827
828             message = notmuch_database_find_message (notmuch, message_id);
829             if (message == NULL) {
830                 fprintf (stderr, "Warning: Cannot apply tags to missing message: %s (",
831                          message_id);
832             }
833
834             next = tags;
835             while (next) {
836                 tag = strsep (&next, " ");
837                 if (*tag == '\0')
838                     continue;
839                 if (message) {
840                     status = notmuch_message_add_tag (message, tag);
841                     if (status) {
842                         fprintf (stderr,
843                                  "Error applying tag %s to message %s:\n",
844                                  tag, message_id);
845                         fprintf (stderr, "%s\n",
846                                  notmuch_status_to_string (status));
847                     }
848                 } else {
849                     fprintf (stderr, "%s%s",
850                              tag == tags ? "" : " ", tag);
851                 }
852             }
853
854             if (message)
855                 notmuch_message_destroy (message);
856             else
857                 fprintf (stderr, ")\n");
858         }
859         free (message_id);
860         free (tags);
861     }
862
863     regfree (&regex);
864
865   DONE:
866     if (line)
867         free (line);
868     if (notmuch)
869         notmuch_database_close (notmuch);
870     if (input && input != stdin)
871         fclose (input);
872
873     return ret;
874 }
875
876 command_t commands[] = {
877     { "setup", setup_command,
878       "Interactively setup notmuch for first use.\n\n"
879       "\t\tInvoking notmuch with no command argument will run setup if\n"
880       "\t\tthe setup command has not previously been completed." },
881     { "new", new_command,
882       "Find and import any new messages.\n\n"
883       "\t\tScans all sub-directories of the database, adding new files\n"
884       "\t\tthat are found. Note: \"notmuch new\" will skip any\n"
885       "\t\tread-only directories, so you can use that to mark\n"
886       "\t\tdirectories that will not receive any new mail."},
887     { "search", search_command,
888       "<search-term> [...]\n\n"
889       "\t\tSearch for threads matching the given search terms.\n"
890       "\t\tOnce we actually implement search we'll document the\n"
891       "\t\tsyntax here." },
892     { "show", show_command,
893       "<thread-id>\n\n"
894       "\t\tShow the thread with the given thread ID (see 'search')." },
895     { "dump", dump_command,
896       "[<filename>]\n\n"
897       "\t\tCreate a plain-text dump of the tags for each message\n"
898       "\t\twriting to the given filename, if any, or to stdout.\n"
899       "\t\tThese tags are the only data in the notmuch database\n"
900       "\t\tthat can't be recreated from the messages themselves.\n"
901       "\t\tThe output of notmuch dump is therefore the only\n"
902       "\t\tcritical thing to backup (and much more friendly to\n"
903       "\t\tincremental backup than the native database files." },
904     { "restore", restore_command,
905       "<filename>\n\n"
906       "\t\tRestore the tags from the given dump file (see 'dump')." }
907 };
908
909 static void
910 usage (void)
911 {
912     command_t *command;
913     unsigned int i;
914
915     fprintf (stderr, "Usage: notmuch <command> [args...]\n");
916     fprintf (stderr, "\n");
917     fprintf (stderr, "Where <command> and [args...] are as follows:\n");
918     fprintf (stderr, "\n");
919
920     for (i = 0; i < ARRAY_SIZE (commands); i++) {
921         command = &commands[i];
922
923         fprintf (stderr, "\t%s\t%s\n\n", command->name, command->usage);
924     }
925 }
926     
927 int
928 main (int argc, char *argv[])
929 {
930     command_t *command;
931     unsigned int i;
932
933     if (argc == 1)
934         return setup_command (0, NULL);
935
936     for (i = 0; i < ARRAY_SIZE (commands); i++) {
937         command = &commands[i];
938
939         if (strcmp (argv[1], command->name) == 0)
940             return (command->function) (argc - 2, &argv[2]);
941     }
942
943     /* Don't complain about "help" being an unknown command when we're
944        about to provide exactly what's wanted anyway. */
945     if (strcmp (argv[1], "help") == 0 ||
946         strcmp (argv[1], "--help") == 0)
947     {
948         fprintf (stderr, "The notmuch mail system.\n\n");
949         usage ();
950         return 0;
951     } else {
952         fprintf (stderr, "Error: Unknown command '%s'\n\n", argv[1]);
953         usage ();
954         return 1;
955     }
956 }