]> git.notmuchmail.org Git - notmuch/blob - notmuch-new.c
1473d2e69449c80e596b0440bbd6269c31d8fd61
[notmuch] / notmuch-new.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 #include "notmuch-client.h"
22
23 #include <unistd.h>
24
25 typedef struct _filename_node {
26     char *filename;
27     struct _filename_node *next;
28 } _filename_node_t;
29
30 typedef struct _filename_list {
31     unsigned count;
32     _filename_node_t *head;
33     _filename_node_t **tail;
34 } _filename_list_t;
35
36 typedef struct {
37     int output_is_a_tty;
38     int verbose;
39     const char **new_tags;
40     size_t new_tags_length;
41
42     int total_files;
43     int processed_files;
44     int added_messages;
45     struct timeval tv_start;
46
47     _filename_list_t *removed_files;
48     _filename_list_t *removed_directories;
49
50     notmuch_bool_t synchronize_flags;
51     _filename_list_t *message_ids_to_sync;
52 } add_files_state_t;
53
54 static volatile sig_atomic_t do_print_progress = 0;
55
56 static void
57 handle_sigalrm (unused (int signal))
58 {
59     do_print_progress = 1;
60 }
61
62 static volatile sig_atomic_t interrupted;
63
64 static void
65 handle_sigint (unused (int sig))
66 {
67     ssize_t ignored;
68     static char msg[] = "Stopping...         \n";
69
70     ignored = write(2, msg, sizeof(msg)-1);
71     interrupted = 1;
72 }
73
74 static _filename_list_t *
75 _filename_list_create (const void *ctx)
76 {
77     _filename_list_t *list;
78
79     list = talloc (ctx, _filename_list_t);
80     if (list == NULL)
81         return NULL;
82
83     list->head = NULL;
84     list->tail = &list->head;
85     list->count = 0;
86
87     return list;
88 }
89
90 static void
91 _filename_list_add (_filename_list_t *list,
92                     const char *filename)
93 {
94     _filename_node_t *node = talloc (list, _filename_node_t);
95
96     list->count++;
97
98     node->filename = talloc_strdup (list, filename);
99     node->next = NULL;
100
101     *(list->tail) = node;
102     list->tail = &node->next;
103 }
104
105 static void
106 generic_print_progress (const char *action, const char *object,
107                         struct timeval tv_start, unsigned processed, unsigned total)
108 {
109     struct timeval tv_now;
110     double elapsed_overall, rate_overall;
111
112     gettimeofday (&tv_now, NULL);
113
114     elapsed_overall = notmuch_time_elapsed (tv_start, tv_now);
115     rate_overall = processed / elapsed_overall;
116
117     printf ("%s %d ", action, processed);
118
119     if (total) {
120         double time_remaining;
121
122         time_remaining = ((total - processed) / rate_overall);
123         printf ("of %d %s (", total, object);
124         notmuch_time_print_formatted_seconds (time_remaining);
125         printf (" remaining).\033[K\r");
126     } else {
127         printf ("%s (%d %s/sec.)\033[K\r", object, (int) rate_overall, object);
128     }
129
130     fflush (stdout);
131 }
132
133 static int
134 dirent_sort_inode (const struct dirent **a, const struct dirent **b)
135 {
136     return ((*a)->d_ino < (*b)->d_ino) ? -1 : 1;
137 }
138
139 static int
140 dirent_sort_strcmp_name (const struct dirent **a, const struct dirent **b)
141 {
142     return strcmp ((*a)->d_name, (*b)->d_name);
143 }
144
145 /* Test if the directory looks like a Maildir directory.
146  *
147  * Search through the array of directory entries to see if we can find all
148  * three subdirectories typical for Maildir, that is "new", "cur", and "tmp".
149  *
150  * Return 1 if the directory looks like a Maildir and 0 otherwise.
151  */
152 static int
153 _entries_resemble_maildir (struct dirent **entries, int count)
154 {
155     int i, found = 0;
156
157     for (i = 0; i < count; i++) {
158         if (entries[i]->d_type != DT_DIR && entries[i]->d_type != DT_UNKNOWN)
159             continue;
160
161         if (strcmp(entries[i]->d_name, "new") == 0 ||
162             strcmp(entries[i]->d_name, "cur") == 0 ||
163             strcmp(entries[i]->d_name, "tmp") == 0)
164         {
165             found++;
166             if (found == 3)
167                 return 1;
168         }
169     }
170
171     return 0;
172 }
173
174 /* Examine 'path' recursively as follows:
175  *
176  *   o Ask the filesystem for the mtime of 'path' (fs_mtime)
177  *   o Ask the database for its timestamp of 'path' (db_mtime)
178  *
179  *   o Ask the filesystem for files and directories within 'path'
180  *     (via scandir and stored in fs_entries)
181  *   o Ask the database for files and directories within 'path'
182  *     (db_files and db_subdirs)
183  *
184  *   o Pass 1: For each directory in fs_entries, recursively call into
185  *     this same function.
186  *
187  *   o Pass 2: If 'fs_mtime' > 'db_mtime', then walk fs_entries
188  *     simultaneously with db_files and db_subdirs. Look for one of
189  *     three interesting cases:
190  *
191  *         1. Regular file in fs_entries and not in db_files
192  *            This is a new file to add_message into the database.
193  *
194  *         2. Filename in db_files not in fs_entries.
195  *            This is a file that has been removed from the mail store.
196  *
197  *         3. Directory in db_subdirs not in fs_entries
198  *            This is a directory that has been removed from the mail store.
199  *
200  *     Note that the addition of a directory is not interesting here,
201  *     since that will have been taken care of in pass 1. Also, we
202  *     don't immediately act on file/directory removal since we must
203  *     ensure that in the case of a rename that the new filename is
204  *     added before the old filename is removed, (so that no
205  *     information is lost from the database).
206  *
207  *   o Tell the database to update its time of 'path' to 'fs_mtime'
208  */
209 static notmuch_status_t
210 add_files_recursive (notmuch_database_t *notmuch,
211                      const char *path,
212                      add_files_state_t *state)
213 {
214     DIR *dir = NULL;
215     struct dirent *entry = NULL;
216     char *next = NULL;
217     time_t fs_mtime, db_mtime;
218     notmuch_status_t status, ret = NOTMUCH_STATUS_SUCCESS;
219     notmuch_message_t *message = NULL;
220     struct dirent **fs_entries = NULL;
221     int i, num_fs_entries;
222     notmuch_directory_t *directory;
223     notmuch_filenames_t *db_files = NULL;
224     notmuch_filenames_t *db_subdirs = NULL;
225     struct stat st;
226     notmuch_bool_t is_maildir, new_directory;
227     const char **tag;
228
229     if (stat (path, &st)) {
230         fprintf (stderr, "Error reading directory %s: %s\n",
231                  path, strerror (errno));
232         return NOTMUCH_STATUS_FILE_ERROR;
233     }
234
235     /* This is not an error since we may have recursed based on a
236      * symlink to a regular file, not a directory, and we don't know
237      * that until this stat. */
238     if (! S_ISDIR (st.st_mode))
239         return NOTMUCH_STATUS_SUCCESS;
240
241     fs_mtime = st.st_mtime;
242
243     directory = notmuch_database_get_directory (notmuch, path);
244     db_mtime = notmuch_directory_get_mtime (directory);
245
246     if (db_mtime == 0) {
247         new_directory = TRUE;
248         db_files = NULL;
249         db_subdirs = NULL;
250     } else {
251         new_directory = FALSE;
252         db_files = notmuch_directory_get_child_files (directory);
253         db_subdirs = notmuch_directory_get_child_directories (directory);
254     }
255
256     /* If the database knows about this directory, then we sort based
257      * on strcmp to match the database sorting. Otherwise, we can do
258      * inode-based sorting for faster filesystem operation. */
259     num_fs_entries = scandir (path, &fs_entries, 0,
260                               new_directory ?
261                               dirent_sort_inode : dirent_sort_strcmp_name);
262
263     if (num_fs_entries == -1) {
264         fprintf (stderr, "Error opening directory %s: %s\n",
265                  path, strerror (errno));
266         ret = NOTMUCH_STATUS_FILE_ERROR;
267         goto DONE;
268     }
269
270     /* Pass 1: Recurse into all sub-directories. */
271     is_maildir = _entries_resemble_maildir (fs_entries, num_fs_entries);
272
273     for (i = 0; i < num_fs_entries; i++) {
274         if (interrupted)
275             break;
276
277         entry = fs_entries[i];
278
279         /* We only want to descend into directories.
280          * But symlinks can be to directories too, of course.
281          *
282          * And if the filesystem doesn't tell us the file type in the
283          * scandir results, then it might be a directory (and if not,
284          * then we'll stat and return immediately in the next level of
285          * recursion). */
286         if (entry->d_type != DT_DIR &&
287             entry->d_type != DT_LNK &&
288             entry->d_type != DT_UNKNOWN)
289         {
290             continue;
291         }
292
293         /* Ignore special directories to avoid infinite recursion.
294          * Also ignore the .notmuch directory and any "tmp" directory
295          * that appears within a maildir.
296          */
297         /* XXX: Eventually we'll want more sophistication to let the
298          * user specify files to be ignored. */
299         if (strcmp (entry->d_name, ".") == 0 ||
300             strcmp (entry->d_name, "..") == 0 ||
301             (is_maildir && strcmp (entry->d_name, "tmp") == 0) ||
302             strcmp (entry->d_name, ".notmuch") ==0)
303         {
304             continue;
305         }
306
307         next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name);
308         status = add_files_recursive (notmuch, next, state);
309         if (status && ret == NOTMUCH_STATUS_SUCCESS)
310             ret = status;
311         talloc_free (next);
312         next = NULL;
313     }
314
315     /* If the directory's modification time in the filesystem is the
316      * same as what we recorded in the database the last time we
317      * scanned it, then we can skip the second pass entirely.
318      *
319      * We test for strict equality here to avoid a bug that can happen
320      * if the system clock jumps backward, (preventing new mail from
321      * being discovered until the clock catches up and the directory
322      * is modified again).
323      */
324     if (fs_mtime == db_mtime)
325         goto DONE;
326
327     /* Pass 2: Scan for new files, removed files, and removed directories. */
328     for (i = 0; i < num_fs_entries; i++)
329     {
330         if (interrupted)
331             break;
332
333         entry = fs_entries[i];
334
335         /* Check if we've walked past any names in db_files or
336          * db_subdirs. If so, these have been deleted. */
337         while (notmuch_filenames_valid (db_files) &&
338                strcmp (notmuch_filenames_get (db_files), entry->d_name) < 0)
339         {
340             char *absolute = talloc_asprintf (state->removed_files,
341                                               "%s/%s", path,
342                                               notmuch_filenames_get (db_files));
343
344             _filename_list_add (state->removed_files, absolute);
345
346             notmuch_filenames_move_to_next (db_files);
347         }
348
349         while (notmuch_filenames_valid (db_subdirs) &&
350                strcmp (notmuch_filenames_get (db_subdirs), entry->d_name) <= 0)
351         {
352             const char *filename = notmuch_filenames_get (db_subdirs);
353
354             if (strcmp (filename, entry->d_name) < 0)
355             {
356                 char *absolute = talloc_asprintf (state->removed_directories,
357                                                   "%s/%s", path, filename);
358
359                 _filename_list_add (state->removed_directories, absolute);
360             }
361
362             notmuch_filenames_move_to_next (db_subdirs);
363         }
364
365         /* If we're looking at a symlink, we only want to add it if it
366          * links to a regular file, (and not to a directory, say).
367          *
368          * Similarly, if the file is of unknown type (due to filesytem
369          * limitations), then we also need to look closer.
370          *
371          * In either case, a stat does the trick.
372          */
373         if (entry->d_type == DT_LNK || entry->d_type == DT_UNKNOWN) {
374             int err;
375
376             next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name);
377             err = stat (next, &st);
378             talloc_free (next);
379             next = NULL;
380
381             /* Don't emit an error for a link pointing nowhere, since
382              * the directory-traversal pass will have already done
383              * that. */
384             if (err)
385                 continue;
386
387             if (! S_ISREG (st.st_mode))
388                 continue;
389         } else if (entry->d_type != DT_REG) {
390             continue;
391         }
392
393         /* Don't add a file that we've added before. */
394         if (notmuch_filenames_valid (db_files) &&
395             strcmp (notmuch_filenames_get (db_files), entry->d_name) == 0)
396         {
397             notmuch_filenames_move_to_next (db_files);
398             continue;
399         }
400
401         /* We're now looking at a regular file that doesn't yet exist
402          * in the database, so add it. */
403         next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name);
404
405         state->processed_files++;
406
407         if (state->verbose) {
408             if (state->output_is_a_tty)
409                 printf("\r\033[K");
410
411             printf ("%i/%i: %s",
412                     state->processed_files,
413                     state->total_files,
414                     next);
415
416             putchar((state->output_is_a_tty) ? '\r' : '\n');
417             fflush (stdout);
418         }
419
420         status = notmuch_database_add_message (notmuch, next, &message);
421         switch (status) {
422         /* success */
423         case NOTMUCH_STATUS_SUCCESS:
424             state->added_messages++;
425             notmuch_message_freeze (message);
426             for (tag=state->new_tags; *tag != NULL; tag++)
427                 notmuch_message_add_tag (message, *tag);
428             if (state->synchronize_flags == TRUE)
429                 notmuch_message_maildir_flags_to_tags (message);
430             notmuch_message_thaw (message);
431             break;
432         /* Non-fatal issues (go on to next file) */
433         case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
434             /* Defer sync of maildir flags until after old filenames
435              * are removed in the case of a rename. */
436             if (state->synchronize_flags == TRUE)
437                 _filename_list_add (state->message_ids_to_sync,
438                                     notmuch_message_get_message_id (message));
439             break;
440         case NOTMUCH_STATUS_FILE_NOT_EMAIL:
441             fprintf (stderr, "Note: Ignoring non-mail file: %s\n",
442                      next);
443             break;
444         /* Fatal issues. Don't process anymore. */
445         case NOTMUCH_STATUS_READ_ONLY_DATABASE:
446         case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
447         case NOTMUCH_STATUS_OUT_OF_MEMORY:
448             fprintf (stderr, "Error: %s. Halting processing.\n",
449                      notmuch_status_to_string (status));
450             ret = status;
451             goto DONE;
452         default:
453         case NOTMUCH_STATUS_FILE_ERROR:
454         case NOTMUCH_STATUS_NULL_POINTER:
455         case NOTMUCH_STATUS_TAG_TOO_LONG:
456         case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
457         case NOTMUCH_STATUS_LAST_STATUS:
458             INTERNAL_ERROR ("add_message returned unexpected value: %d",  status);
459             goto DONE;
460         }
461
462         if (message) {
463             notmuch_message_destroy (message);
464             message = NULL;
465         }
466
467         if (do_print_progress) {
468             do_print_progress = 0;
469             generic_print_progress ("Processed", "files", state->tv_start,
470                                     state->processed_files, state->total_files);
471         }
472
473         talloc_free (next);
474         next = NULL;
475     }
476
477     if (interrupted)
478         goto DONE;
479
480     /* Now that we've walked the whole filesystem list, anything left
481      * over in the database lists has been deleted. */
482     while (notmuch_filenames_valid (db_files))
483     {
484         char *absolute = talloc_asprintf (state->removed_files,
485                                           "%s/%s", path,
486                                           notmuch_filenames_get (db_files));
487
488         _filename_list_add (state->removed_files, absolute);
489
490         notmuch_filenames_move_to_next (db_files);
491     }
492
493     while (notmuch_filenames_valid (db_subdirs))
494     {
495         char *absolute = talloc_asprintf (state->removed_directories,
496                                           "%s/%s", path,
497                                           notmuch_filenames_get (db_subdirs));
498
499         _filename_list_add (state->removed_directories, absolute);
500
501         notmuch_filenames_move_to_next (db_subdirs);
502     }
503
504     if (! interrupted) {
505         status = notmuch_directory_set_mtime (directory, fs_mtime);
506         if (status && ret == NOTMUCH_STATUS_SUCCESS)
507             ret = status;
508     }
509
510   DONE:
511     if (next)
512         talloc_free (next);
513     if (entry)
514         free (entry);
515     if (dir)
516         closedir (dir);
517     if (fs_entries)
518         free (fs_entries);
519     if (db_subdirs)
520         notmuch_filenames_destroy (db_subdirs);
521     if (db_files)
522         notmuch_filenames_destroy (db_files);
523     if (directory)
524         notmuch_directory_destroy (directory);
525
526     return ret;
527 }
528
529 static void
530 setup_progress_printing_timer (void)
531 {
532     struct sigaction action;
533     struct itimerval timerval;
534
535     /* Setup our handler for SIGALRM */
536     memset (&action, 0, sizeof (struct sigaction));
537     action.sa_handler = handle_sigalrm;
538     sigemptyset (&action.sa_mask);
539     action.sa_flags = SA_RESTART;
540     sigaction (SIGALRM, &action, NULL);
541
542     /* Then start a timer to send SIGALRM once per second. */
543     timerval.it_interval.tv_sec = 1;
544     timerval.it_interval.tv_usec = 0;
545     timerval.it_value.tv_sec = 1;
546     timerval.it_value.tv_usec = 0;
547     setitimer (ITIMER_REAL, &timerval, NULL);
548 }
549
550 static void
551 stop_progress_printing_timer (void)
552 {
553     struct sigaction action;
554     struct itimerval timerval;
555
556     /* Now stop the timer. */
557     timerval.it_interval.tv_sec = 0;
558     timerval.it_interval.tv_usec = 0;
559     timerval.it_value.tv_sec = 0;
560     timerval.it_value.tv_usec = 0;
561     setitimer (ITIMER_REAL, &timerval, NULL);
562
563     /* And disable the signal handler. */
564     action.sa_handler = SIG_IGN;
565     sigaction (SIGALRM, &action, NULL);
566 }
567
568
569 /* This is the top-level entry point for add_files. It does a couple
570  * of error checks and then calls into the recursive function. */
571 static notmuch_status_t
572 add_files (notmuch_database_t *notmuch,
573            const char *path,
574            add_files_state_t *state)
575 {
576     notmuch_status_t status;
577     struct stat st;
578
579     if (stat (path, &st)) {
580         fprintf (stderr, "Error reading directory %s: %s\n",
581                  path, strerror (errno));
582         return NOTMUCH_STATUS_FILE_ERROR;
583     }
584
585     if (! S_ISDIR (st.st_mode)) {
586         fprintf (stderr, "Error: %s is not a directory.\n", path);
587         return NOTMUCH_STATUS_FILE_ERROR;
588     }
589
590     status = add_files_recursive (notmuch, path, state);
591
592     return status;
593 }
594
595 /* XXX: This should be merged with the add_files function since it
596  * shares a lot of logic with it. */
597 /* Recursively count all regular files in path and all sub-directories
598  * of path.  The result is added to *count (which should be
599  * initialized to zero by the top-level caller before calling
600  * count_files). */
601 static void
602 count_files (const char *path, int *count)
603 {
604     struct dirent *entry = NULL;
605     char *next;
606     struct stat st;
607     struct dirent **fs_entries = NULL;
608     int num_fs_entries = scandir (path, &fs_entries, 0, dirent_sort_inode);
609     int i = 0;
610
611     if (num_fs_entries == -1) {
612         fprintf (stderr, "Warning: failed to open directory %s: %s\n",
613                  path, strerror (errno));
614         goto DONE;
615     }
616
617     while (!interrupted) {
618         if (i == num_fs_entries)
619             break;
620
621         entry = fs_entries[i++];
622
623         /* Ignore special directories to avoid infinite recursion.
624          * Also ignore the .notmuch directory.
625          */
626         /* XXX: Eventually we'll want more sophistication to let the
627          * user specify files to be ignored. */
628         if (strcmp (entry->d_name, ".") == 0 ||
629             strcmp (entry->d_name, "..") == 0 ||
630             strcmp (entry->d_name, ".notmuch") == 0)
631         {
632             continue;
633         }
634
635         if (asprintf (&next, "%s/%s", path, entry->d_name) == -1) {
636             next = NULL;
637             fprintf (stderr, "Error descending from %s to %s: Out of memory\n",
638                      path, entry->d_name);
639             continue;
640         }
641
642         stat (next, &st);
643
644         if (S_ISREG (st.st_mode)) {
645             *count = *count + 1;
646             if (*count % 1000 == 0) {
647                 printf ("Found %d files so far.\r", *count);
648                 fflush (stdout);
649             }
650         } else if (S_ISDIR (st.st_mode)) {
651             count_files (next, count);
652         }
653
654         free (next);
655     }
656
657   DONE:
658     if (entry)
659         free (entry);
660     if (fs_entries)
661         free (fs_entries);
662 }
663
664 static void
665 upgrade_print_progress (void *closure,
666                         double progress)
667 {
668     add_files_state_t *state = closure;
669
670     printf ("Upgrading database: %.2f%% complete", progress * 100.0);
671
672     if (progress > 0) {
673         struct timeval tv_now;
674         double elapsed, time_remaining;
675
676         gettimeofday (&tv_now, NULL);
677
678         elapsed = notmuch_time_elapsed (state->tv_start, tv_now);
679         time_remaining = (elapsed / progress) * (1.0 - progress);
680         printf (" (");
681         notmuch_time_print_formatted_seconds (time_remaining);
682         printf (" remaining)");
683     }
684
685     printf (".      \r");
686
687     fflush (stdout);
688 }
689
690 /* Recursively remove all filenames from the database referring to
691  * 'path' (or to any of its children). */
692 static void
693 _remove_directory (void *ctx,
694                    notmuch_database_t *notmuch,
695                    const char *path,
696                    int *renamed_files,
697                    int *removed_files)
698 {
699     notmuch_directory_t *directory;
700     notmuch_filenames_t *files, *subdirs;
701     notmuch_status_t status;
702     char *absolute;
703
704     directory = notmuch_database_get_directory (notmuch, path);
705
706     for (files = notmuch_directory_get_child_files (directory);
707          notmuch_filenames_valid (files);
708          notmuch_filenames_move_to_next (files))
709     {
710         absolute = talloc_asprintf (ctx, "%s/%s", path,
711                                     notmuch_filenames_get (files));
712         status = notmuch_database_remove_message (notmuch, absolute);
713         if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
714             *renamed_files = *renamed_files + 1;
715         else
716             *removed_files = *removed_files + 1;
717         talloc_free (absolute);
718     }
719
720     for (subdirs = notmuch_directory_get_child_directories (directory);
721          notmuch_filenames_valid (subdirs);
722          notmuch_filenames_move_to_next (subdirs))
723     {
724         absolute = talloc_asprintf (ctx, "%s/%s", path,
725                                     notmuch_filenames_get (subdirs));
726         _remove_directory (ctx, notmuch, absolute, renamed_files, removed_files);
727         talloc_free (absolute);
728     }
729
730     notmuch_directory_destroy (directory);
731 }
732
733 int
734 notmuch_new_command (void *ctx, int argc, char *argv[])
735 {
736     notmuch_config_t *config;
737     notmuch_database_t *notmuch;
738     add_files_state_t add_files_state;
739     double elapsed;
740     struct timeval tv_now, tv_start;
741     int ret = 0;
742     struct stat st;
743     const char *db_path;
744     char *dot_notmuch_path;
745     struct sigaction action;
746     _filename_node_t *f;
747     int renamed_files, removed_files;
748     notmuch_status_t status;
749     int i;
750     notmuch_bool_t timer_is_active = FALSE;
751
752     add_files_state.verbose = 0;
753     add_files_state.output_is_a_tty = isatty (fileno (stdout));
754
755     for (i = 0; i < argc && argv[i][0] == '-'; i++) {
756         if (STRNCMP_LITERAL (argv[i], "--verbose") == 0) {
757             add_files_state.verbose = 1;
758         } else {
759             fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
760             return 1;
761         }
762     }
763     config = notmuch_config_open (ctx, NULL, NULL);
764     if (config == NULL)
765         return 1;
766
767     add_files_state.new_tags = notmuch_config_get_new_tags (config, &add_files_state.new_tags_length);
768     add_files_state.synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);
769     add_files_state.message_ids_to_sync = _filename_list_create (ctx);
770     db_path = notmuch_config_get_database_path (config);
771
772     dot_notmuch_path = talloc_asprintf (ctx, "%s/%s", db_path, ".notmuch");
773
774     if (stat (dot_notmuch_path, &st)) {
775         int count;
776
777         count = 0;
778         count_files (db_path, &count);
779         if (interrupted)
780             return 1;
781
782         printf ("Found %d total files (that's not much mail).\n", count);
783         notmuch = notmuch_database_create (db_path);
784         add_files_state.total_files = count;
785     } else {
786         notmuch = notmuch_database_open (db_path,
787                                          NOTMUCH_DATABASE_MODE_READ_WRITE);
788         if (notmuch == NULL)
789             return 1;
790
791         if (notmuch_database_needs_upgrade (notmuch)) {
792             printf ("Welcome to a new version of notmuch! Your database will now be upgraded.\n");
793             gettimeofday (&add_files_state.tv_start, NULL);
794             notmuch_database_upgrade (notmuch, upgrade_print_progress,
795                                       &add_files_state);
796             printf ("Your notmuch database has now been upgraded to database format version %u.\n",
797                     notmuch_database_get_version (notmuch));
798         }
799
800         add_files_state.total_files = 0;
801     }
802
803     if (notmuch == NULL)
804         return 1;
805
806     /* Setup our handler for SIGINT. We do this after having
807      * potentially done a database upgrade we this interrupt handler
808      * won't support. */
809     memset (&action, 0, sizeof (struct sigaction));
810     action.sa_handler = handle_sigint;
811     sigemptyset (&action.sa_mask);
812     action.sa_flags = SA_RESTART;
813     sigaction (SIGINT, &action, NULL);
814
815     talloc_free (dot_notmuch_path);
816     dot_notmuch_path = NULL;
817
818     add_files_state.processed_files = 0;
819     add_files_state.added_messages = 0;
820     gettimeofday (&add_files_state.tv_start, NULL);
821
822     add_files_state.removed_files = _filename_list_create (ctx);
823     add_files_state.removed_directories = _filename_list_create (ctx);
824
825     if (! debugger_is_active () && add_files_state.output_is_a_tty
826         && ! add_files_state.verbose) {
827         setup_progress_printing_timer ();
828         timer_is_active = TRUE;
829     }
830
831     ret = add_files (notmuch, db_path, &add_files_state);
832
833     removed_files = 0;
834     renamed_files = 0;
835     gettimeofday (&tv_start, NULL);
836     for (f = add_files_state.removed_files->head; f; f = f->next) {
837         status = notmuch_database_remove_message (notmuch, f->filename);
838         if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
839             renamed_files++;
840         else
841             removed_files++;
842         if (do_print_progress) {
843             do_print_progress = 0;
844             generic_print_progress ("Cleaned up", "messages",
845                 tv_start, removed_files + renamed_files,
846                 add_files_state.removed_files->count);
847         }
848     }
849
850     gettimeofday (&tv_start, NULL);
851     for (f = add_files_state.removed_directories->head, i = 0; f; f = f->next, i++) {
852         _remove_directory (ctx, notmuch, f->filename,
853                            &renamed_files, &removed_files);
854         if (do_print_progress) {
855             do_print_progress = 0;
856             generic_print_progress ("Cleaned up", "directories",
857                 tv_start, i,
858                 add_files_state.removed_directories->count);
859         }
860     }
861
862     talloc_free (add_files_state.removed_files);
863     talloc_free (add_files_state.removed_directories);
864
865     /* Now that removals are done (hence the database is aware of all
866      * renames), we can synchronize maildir_flags to tags for all
867      * messages that had new filenames appear on this run. */
868     gettimeofday (&tv_start, NULL);
869     if (add_files_state.synchronize_flags) {
870         _filename_node_t *node;
871         notmuch_message_t *message;
872         for (node = add_files_state.message_ids_to_sync->head, i = 0;
873              node;
874              node = node->next, i++)
875         {
876             message = notmuch_database_find_message (notmuch, node->filename);
877             notmuch_message_maildir_flags_to_tags (message);
878             notmuch_message_destroy (message);
879             if (do_print_progress) {
880                 do_print_progress = 0;
881                 generic_print_progress (
882                     "Synchronized tags for", "messages",
883                     tv_start, i, add_files_state.message_ids_to_sync->count);
884             }
885         }
886     }
887
888     talloc_free (add_files_state.message_ids_to_sync);
889     add_files_state.message_ids_to_sync = NULL;
890
891     if (timer_is_active)
892         stop_progress_printing_timer ();
893
894     gettimeofday (&tv_now, NULL);
895     elapsed = notmuch_time_elapsed (add_files_state.tv_start,
896                                     tv_now);
897
898     if (add_files_state.processed_files) {
899         printf ("Processed %d %s in ", add_files_state.processed_files,
900                 add_files_state.processed_files == 1 ?
901                 "file" : "total files");
902         notmuch_time_print_formatted_seconds (elapsed);
903         if (elapsed > 1) {
904             printf (" (%d files/sec.).\033[K\n",
905                     (int) (add_files_state.processed_files / elapsed));
906         } else {
907             printf (".\033[K\n");
908         }
909     }
910
911     if (add_files_state.added_messages) {
912         printf ("Added %d new %s to the database.",
913                 add_files_state.added_messages,
914                 add_files_state.added_messages == 1 ?
915                 "message" : "messages");
916     } else {
917         printf ("No new mail.");
918     }
919
920     if (removed_files) {
921         printf (" Removed %d %s.",
922                 removed_files,
923                 removed_files == 1 ? "message" : "messages");
924     }
925
926     if (renamed_files) {
927         printf (" Detected %d file %s.",
928                 renamed_files,
929                 renamed_files == 1 ? "rename" : "renames");
930     }
931
932     printf ("\n");
933
934     if (ret) {
935         printf ("\nNote: At least one error was encountered: %s\n",
936                 notmuch_status_to_string (ret));
937     }
938
939     notmuch_database_close (notmuch);
940
941     return ret || interrupted;
942 }