]> git.notmuchmail.org Git - notmuch/blob - notmuch.c
fd0e0b253c56388a8c5fd2bec01053016c4cb7b6
[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 (", notmuch_thread_get_thread_id (thread));
655
656         for (tags = notmuch_thread_get_tags (thread);
657              notmuch_tags_has_more (tags);
658              notmuch_tags_advance (tags))
659         {
660             if (! first)
661                 printf (" ");
662             printf ("%s", notmuch_tags_get (tags));
663             first = 0;
664         }
665
666         printf (")\n");
667
668         notmuch_thread_destroy (thread);
669     }
670
671     notmuch_query_destroy (query);
672
673   DONE:
674     if (notmuch)
675         notmuch_database_close (notmuch);
676     talloc_free (local);
677
678     return ret;
679 }
680
681 static int
682 show_command (unused (int argc), unused (char *argv[]))
683 {
684     fprintf (stderr, "Error: show is not implemented yet.\n");
685     return 1;
686 }
687
688 static int
689 dump_command (int argc, char *argv[])
690 {
691     FILE *output = NULL;
692     notmuch_database_t *notmuch = NULL;
693     notmuch_query_t *query;
694     notmuch_message_results_t *results;
695     notmuch_message_t *message;
696     notmuch_tags_t *tags;
697     int ret = 0;
698
699     if (argc) {
700         output = fopen (argv[0], "w");
701         if (output == NULL) {
702             fprintf (stderr, "Error opening %s for writing: %s\n",
703                      argv[0], strerror (errno));
704             ret = 1;
705             goto DONE;
706         }
707     } else {
708         output = stdout;
709     }
710
711     notmuch = notmuch_database_open (NULL);
712     if (notmuch == NULL) {
713         ret = 1;
714         goto DONE;
715     }
716
717     query = notmuch_query_create (notmuch, "");
718     if (query == NULL) {
719         fprintf (stderr, "Out of memory\n");
720         ret = 1;
721         goto DONE;
722     }
723
724     notmuch_query_set_sort (query, NOTMUCH_SORT_MESSAGE_ID);
725
726     for (results = notmuch_query_search_messages (query);
727          notmuch_message_results_has_more (results);
728          notmuch_message_results_advance (results))
729     {
730         int first = 1;
731         message = notmuch_message_results_get (results);
732
733         fprintf (output,
734                  "%s (", notmuch_message_get_message_id (message));
735
736         for (tags = notmuch_message_get_tags (message);
737              notmuch_tags_has_more (tags);
738              notmuch_tags_advance (tags))
739         {
740             if (! first)
741                 fprintf (output, " ");
742
743             fprintf (output, "%s", notmuch_tags_get (tags));
744
745             first = 0;
746         }
747
748         fprintf (output, ")\n");
749
750         notmuch_message_destroy (message);
751     }
752
753     notmuch_query_destroy (query);
754
755   DONE:
756     if (notmuch)
757         notmuch_database_close (notmuch);
758     if (output && output != stdout)
759         fclose (output);
760
761     return ret;
762 }
763
764 static int
765 restore_command (int argc, char *argv[])
766 {
767     FILE *input = NULL;
768     notmuch_database_t *notmuch = NULL;
769     char *line = NULL;
770     size_t line_size;
771     ssize_t line_len;
772     regex_t regex;
773     int rerr;
774     int ret = 0;
775
776     if (argc) {
777         input = fopen (argv[0], "r");
778         if (input == NULL) {
779             fprintf (stderr, "Error opening %s for reading: %s\n",
780                      argv[0], strerror (errno));
781             ret = 1;
782             goto DONE;
783         }
784     } else {
785         printf ("No filename given. Reading dump from stdin.\n");
786         input = stdin;
787     }
788
789     notmuch = notmuch_database_open (NULL);
790     if (notmuch == NULL) {
791         ret = 1;
792         goto DONE;
793     }
794
795     /* Dump output is one line per message. We match a sequence of
796      * non-space characters for the message-id, then one or more
797      * spaces, then a list of space-separated tags as a sequence of
798      * characters within literal '(' and ')'. */
799     xregcomp (&regex,
800               "^([^ ]+) \\(([^)]*)\\)$",
801               REG_EXTENDED);
802
803     while ((line_len = getline (&line, &line_size, input)) != -1) {
804         regmatch_t match[3];
805         char *message_id, *tags, *tag, *next;
806         notmuch_message_t *message;
807         notmuch_status_t status;
808
809         chomp_newline (line);
810
811         rerr = xregexec (&regex, line, 3, match, 0);
812         if (rerr == REG_NOMATCH)
813         {
814             fprintf (stderr, "Warning: Ignoring invalid input line: %s\n",
815                      line);
816             continue;
817         }
818
819         message_id = xstrndup (line + match[1].rm_so,
820                                match[1].rm_eo - match[1].rm_so);
821         tags = xstrndup (line + match[2].rm_so,
822                          match[2].rm_eo - match[2].rm_so);
823
824         if (strlen (tags)) {
825
826             message = notmuch_database_find_message (notmuch, message_id);
827             if (message == NULL) {
828                 fprintf (stderr, "Warning: Cannot apply tags to missing message: %s (",
829                          message_id);
830             }
831
832             next = tags;
833             while (next) {
834                 tag = strsep (&next, " ");
835                 if (*tag == '\0')
836                     continue;
837                 if (message) {
838                     status = notmuch_message_add_tag (message, tag);
839                     if (status) {
840                         fprintf (stderr,
841                                  "Error applying tag %s to message %s:\n",
842                                  tag, message_id);
843                         fprintf (stderr, "%s\n",
844                                  notmuch_status_to_string (status));
845                     }
846                 } else {
847                     fprintf (stderr, "%s%s",
848                              tag == tags ? "" : " ", tag);
849                 }
850             }
851
852             if (message)
853                 notmuch_message_destroy (message);
854             else
855                 fprintf (stderr, ")\n");
856         }
857         free (message_id);
858         free (tags);
859     }
860
861     regfree (&regex);
862
863   DONE:
864     if (line)
865         free (line);
866     if (notmuch)
867         notmuch_database_close (notmuch);
868     if (input && input != stdin)
869         fclose (input);
870
871     return ret;
872 }
873
874 command_t commands[] = {
875     { "setup", setup_command,
876       "Interactively setup notmuch for first use.\n\n"
877       "\t\tInvoking notmuch with no command argument will run setup if\n"
878       "\t\tthe setup command has not previously been completed." },
879     { "new", new_command,
880       "Find and import any new messages.\n\n"
881       "\t\tScans all sub-directories of the database, adding new files\n"
882       "\t\tthat are found. Note: \"notmuch new\" will skip any\n"
883       "\t\tread-only directories, so you can use that to mark\n"
884       "\t\tdirectories that will not receive any new mail."},
885     { "search", search_command,
886       "<search-term> [...]\n\n"
887       "\t\tSearch for threads matching the given search terms.\n"
888       "\t\tOnce we actually implement search we'll document the\n"
889       "\t\tsyntax here." },
890     { "show", show_command,
891       "<thread-id>\n\n"
892       "\t\tShow the thread with the given thread ID (see 'search')." },
893     { "dump", dump_command,
894       "[<filename>]\n\n"
895       "\t\tCreate a plain-text dump of the tags for each message\n"
896       "\t\twriting to the given filename, if any, or to stdout.\n"
897       "\t\tThese tags are the only data in the notmuch database\n"
898       "\t\tthat can't be recreated from the messages themselves.\n"
899       "\t\tThe output of notmuch dump is therefore the only\n"
900       "\t\tcritical thing to backup (and much more friendly to\n"
901       "\t\tincremental backup than the native database files." },
902     { "restore", restore_command,
903       "<filename>\n\n"
904       "\t\tRestore the tags from the given dump file (see 'dump')." }
905 };
906
907 static void
908 usage (void)
909 {
910     command_t *command;
911     unsigned int i;
912
913     fprintf (stderr, "Usage: notmuch <command> [args...]\n");
914     fprintf (stderr, "\n");
915     fprintf (stderr, "Where <command> and [args...] are as follows:\n");
916     fprintf (stderr, "\n");
917
918     for (i = 0; i < ARRAY_SIZE (commands); i++) {
919         command = &commands[i];
920
921         fprintf (stderr, "\t%s\t%s\n\n", command->name, command->usage);
922     }
923 }
924     
925 int
926 main (int argc, char *argv[])
927 {
928     command_t *command;
929     unsigned int i;
930
931     if (argc == 1)
932         return setup_command (0, NULL);
933
934     for (i = 0; i < ARRAY_SIZE (commands); i++) {
935         command = &commands[i];
936
937         if (strcmp (argv[1], command->name) == 0)
938             return (command->function) (argc - 2, &argv[2]);
939     }
940
941     /* Don't complain about "help" being an unknown command when we're
942        about to provide exactly what's wanted anyway. */
943     if (strcmp (argv[1], "help") == 0 ||
944         strcmp (argv[1], "--help") == 0)
945     {
946         fprintf (stderr, "The notmuch mail system.\n\n");
947         usage ();
948         return 0;
949     } else {
950         fprintf (stderr, "Error: Unknown command '%s'\n\n", argv[1]);
951         usage ();
952         return 1;
953     }
954 }