notmuch: Break notmuch.c up into several smaller files.
[notmuch] / notmuch-setup.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 /* Recursively count all regular files in path and all sub-direcotries
24  * of path.  The result is added to *count (which should be
25  * initialized to zero by the top-level caller before calling
26  * count_files). */
27 static void
28 count_files (const char *path, int *count)
29 {
30     DIR *dir;
31     struct dirent *e, *entry = NULL;
32     int entry_length;
33     int err;
34     char *next;
35     struct stat st;
36
37     dir = opendir (path);
38
39     if (dir == NULL) {
40         fprintf (stderr, "Warning: failed to open directory %s: %s\n",
41                  path, strerror (errno));
42         goto DONE;
43     }
44
45     entry_length = offsetof (struct dirent, d_name) +
46         pathconf (path, _PC_NAME_MAX) + 1;
47     entry = malloc (entry_length);
48
49     while (1) {
50         err = readdir_r (dir, entry, &e);
51         if (err) {
52             fprintf (stderr, "Error reading directory: %s\n",
53                      strerror (errno));
54             free (entry);
55             goto DONE;
56         }
57
58         if (e == NULL)
59             break;
60
61         /* Ignore special directories to avoid infinite recursion.
62          * Also ignore the .notmuch directory.
63          */
64         /* XXX: Eventually we'll want more sophistication to let the
65          * user specify files to be ignored. */
66         if (strcmp (entry->d_name, ".") == 0 ||
67             strcmp (entry->d_name, "..") == 0 ||
68             strcmp (entry->d_name, ".notmuch") == 0)
69         {
70             continue;
71         }
72
73         if (asprintf (&next, "%s/%s", path, entry->d_name) == -1) {
74             next = NULL;
75             fprintf (stderr, "Error descending from %s to %s: Out of memory\n",
76                      path, entry->d_name);
77             continue;
78         }
79
80         stat (next, &st);
81
82         if (S_ISREG (st.st_mode)) {
83             *count = *count + 1;
84             if (*count % 1000 == 0) {
85                 printf ("Found %d files so far.\r", *count);
86                 fflush (stdout);
87             }
88         } else if (S_ISDIR (st.st_mode)) {
89             count_files (next, count);
90         }
91
92         free (next);
93     }
94
95   DONE:
96     if (entry)
97         free (entry);
98
99     closedir (dir);
100 }
101
102 int
103 notmuch_setup_command (unused (void *ctx),
104                        unused (int argc), unused (char *argv[]))
105 {
106     notmuch_database_t *notmuch = NULL;
107     char *default_path, *mail_directory = NULL;
108     size_t line_size;
109     int count;
110     add_files_state_t add_files_state;
111     double elapsed;
112     struct timeval tv_now;
113     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
114
115     printf ("Welcome to notmuch!\n\n");
116
117     printf ("The goal of notmuch is to help you manage and search your collection of\n"
118             "email, and to efficiently keep up with the flow of email as it comes in.\n\n");
119
120     printf ("Notmuch needs to know the top-level directory of your email archive,\n"
121             "(where you already have mail stored and where messages will be delivered\n"
122             "in the future). This directory can contain any number of sub-directories\n"
123             "and primarily just files with indvidual email messages (eg. maildir or mh\n"
124             "archives are perfect). If there are other, non-email files (such as\n"
125             "indexes maintained by other email programs) then notmuch will do its\n"
126             "best to detect those and ignore them.\n\n");
127
128     printf ("Mail storage that uses mbox format, (where one mbox file contains many\n"
129             "messages), will not work with notmuch. If that's how your mail is currently\n"
130             "stored, we recommend you first convert it to maildir format with a utility\n"
131             "such as mb2md. In that case, press Control-C now and run notmuch again\n"
132             "once the conversion is complete.\n\n");
133
134
135     default_path = notmuch_database_default_path ();
136     printf ("Top-level mail directory [%s]: ", default_path);
137     fflush (stdout);
138
139     getline (&mail_directory, &line_size, stdin);
140     chomp_newline (mail_directory);
141
142     printf ("\n");
143
144     if (mail_directory == NULL || strlen (mail_directory) == 0) {
145         if (mail_directory)
146             free (mail_directory);
147         mail_directory = default_path;
148     } else {
149         /* XXX: Instead of telling the user to use an environment
150          * variable here, we should really be writing out a configuration
151          * file and loading that on the next run. */
152         if (strcmp (mail_directory, default_path)) {
153             printf ("Note: Since you are not using the default path, you will want to set\n"
154                     "the NOTMUCH_BASE environment variable to %s so that\n"
155                     "future calls to notmuch commands will know where to find your mail.\n",
156                     mail_directory);
157             printf ("For example, if you are using bash for your shell, add:\n\n");
158             printf ("\texport NOTMUCH_BASE=%s\n\n", mail_directory);
159             printf ("to your ~/.bashrc file.\n\n");
160         }
161         free (default_path);
162     }
163
164     /* Coerce the directory into an absolute directory name. */
165     if (*mail_directory != '/') {
166         char *cwd, *absolute_mail_directory;
167
168         cwd = getcwd (NULL, 0);
169         if (cwd == NULL) {
170             fprintf (stderr, "Out of memory.\n");
171             exit (1);
172         }
173
174         if (asprintf (&absolute_mail_directory, "%s/%s",
175                       cwd, mail_directory) < 0)
176         {
177             fprintf (stderr, "Out of memory.\n");
178             exit (1);
179         }
180
181         free (cwd);
182         free (mail_directory);
183         mail_directory = absolute_mail_directory;
184     }
185
186     notmuch = notmuch_database_create (mail_directory);
187     if (notmuch == NULL) {
188         fprintf (stderr, "Failed to create new notmuch database at %s\n",
189                  mail_directory);
190         ret = NOTMUCH_STATUS_FILE_ERROR;
191         goto DONE;
192     }
193
194     printf ("OK. Let's take a look at the mail we can find in the directory\n");
195     printf ("%s ...\n", mail_directory);
196
197     count = 0;
198     count_files (mail_directory, &count);
199
200     printf ("Found %d total files. That's not much mail.\n\n", count);
201
202     printf ("Next, we'll inspect the messages and create a database of threads:\n");
203
204     add_files_state.ignore_read_only_directories = FALSE;
205     add_files_state.saw_read_only_directory = FALSE;
206     add_files_state.total_files = count;
207     add_files_state.processed_files = 0;
208     add_files_state.added_messages = 0;
209     add_files_state.callback = NULL;
210     gettimeofday (&add_files_state.tv_start, NULL);
211
212     ret = add_files (notmuch, mail_directory, &add_files_state);
213
214     gettimeofday (&tv_now, NULL);
215     elapsed = notmuch_time_elapsed (add_files_state.tv_start,
216                                     tv_now);
217     printf ("Processed %d %s in ", add_files_state.processed_files,
218             add_files_state.processed_files == 1 ?
219             "file" : "total files");
220     notmuch_time_print_formatted_seconds (elapsed);
221     if (elapsed > 1) {
222         printf (" (%d files/sec.).                 \n",
223                 (int) (add_files_state.processed_files / elapsed));
224     } else {
225         printf (".                    \n");
226     }
227     if (add_files_state.added_messages) {
228         printf ("Added %d %s to the database.\n\n",
229                 add_files_state.added_messages,
230                 add_files_state.added_messages == 1 ?
231                 "message" : "unique messages");
232     }
233
234     printf ("When new mail is delivered to %s in the future,\n"
235             "run \"notmuch new\" to add it to the database.\n\n",
236             mail_directory);
237
238     if (ret) {
239         printf ("Note: At least one error was encountered: %s\n",
240                 notmuch_status_to_string (ret));
241     }
242
243   DONE:
244     if (mail_directory)
245         free (mail_directory);
246     if (notmuch)
247         notmuch_database_close (notmuch);
248
249     return ret;
250 }