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