notmuch: Move welcome messages from "notmuch" to "notmuch setup".
[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 static notmuch_status_t
24 add_all_files (notmuch_database_t *notmuch,
25                const char *mail_directory,
26                int num_files)
27 {
28     add_files_state_t add_files_state;
29     double elapsed;
30     struct timeval tv_now;
31     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
32
33     add_files_state.ignore_read_only_directories = FALSE;
34     add_files_state.saw_read_only_directory = FALSE;
35     add_files_state.total_files = num_files;
36     add_files_state.processed_files = 0;
37     add_files_state.added_messages = 0;
38     add_files_state.callback = NULL;
39     gettimeofday (&add_files_state.tv_start, NULL);
40
41     ret = add_files (notmuch, mail_directory, &add_files_state);
42
43     gettimeofday (&tv_now, NULL);
44     elapsed = notmuch_time_elapsed (add_files_state.tv_start,
45                                     tv_now);
46     printf ("Processed %d %s in ", add_files_state.processed_files,
47             add_files_state.processed_files == 1 ?
48             "file" : "total files");
49     notmuch_time_print_formatted_seconds (elapsed);
50     if (elapsed > 1) {
51         printf (" (%d files/sec.).                 \n",
52                 (int) (add_files_state.processed_files / elapsed));
53     } else {
54         printf (".                    \n");
55     }
56     if (add_files_state.added_messages) {
57         printf ("Added %d %s to the database.\n\n",
58                 add_files_state.added_messages,
59                 add_files_state.added_messages == 1 ?
60                 "message" : "unique messages");
61     }
62
63     return ret;
64 }
65
66
67 /* XXX: This should be merged with the existing add_files function in
68  * add-files.c. */
69 /* Recursively count all regular files in path and all sub-direcotries
70  * of path.  The result is added to *count (which should be
71  * initialized to zero by the top-level caller before calling
72  * count_files). */
73 static void
74 count_files (const char *path, int *count)
75 {
76     DIR *dir;
77     struct dirent *e, *entry = NULL;
78     int entry_length;
79     int err;
80     char *next;
81     struct stat st;
82
83     dir = opendir (path);
84
85     if (dir == NULL) {
86         fprintf (stderr, "Warning: failed to open directory %s: %s\n",
87                  path, strerror (errno));
88         goto DONE;
89     }
90
91     entry_length = offsetof (struct dirent, d_name) +
92         pathconf (path, _PC_NAME_MAX) + 1;
93     entry = malloc (entry_length);
94
95     while (1) {
96         err = readdir_r (dir, entry, &e);
97         if (err) {
98             fprintf (stderr, "Error reading directory: %s\n",
99                      strerror (errno));
100             free (entry);
101             goto DONE;
102         }
103
104         if (e == NULL)
105             break;
106
107         /* Ignore special directories to avoid infinite recursion.
108          * Also ignore the .notmuch directory.
109          */
110         /* XXX: Eventually we'll want more sophistication to let the
111          * user specify files to be ignored. */
112         if (strcmp (entry->d_name, ".") == 0 ||
113             strcmp (entry->d_name, "..") == 0 ||
114             strcmp (entry->d_name, ".notmuch") == 0)
115         {
116             continue;
117         }
118
119         if (asprintf (&next, "%s/%s", path, entry->d_name) == -1) {
120             next = NULL;
121             fprintf (stderr, "Error descending from %s to %s: Out of memory\n",
122                      path, entry->d_name);
123             continue;
124         }
125
126         stat (next, &st);
127
128         if (S_ISREG (st.st_mode)) {
129             *count = *count + 1;
130             if (*count % 1000 == 0) {
131                 printf ("Found %d files so far.\r", *count);
132                 fflush (stdout);
133             }
134         } else if (S_ISDIR (st.st_mode)) {
135             count_files (next, count);
136         }
137
138         free (next);
139     }
140
141   DONE:
142     if (entry)
143         free (entry);
144
145     closedir (dir);
146 }
147
148 static const char *
149 make_path_absolute (void *ctx, const char *path)
150 {
151     char *cwd;
152
153     if (*path == '/')
154         return path;
155
156     cwd = getcwd (NULL, 0);
157     if (cwd == NULL) {
158         fprintf (stderr, "Out of memory.\n");
159         return NULL;
160     }
161
162     path = talloc_asprintf (ctx, "%s/%s", cwd, path);
163     if (path == NULL)
164         fprintf (stderr, "Out of memory.\n");
165
166     free (cwd);
167
168     return path;
169 }
170
171 static void
172 welcome_message_pre_setup (void)
173 {
174     printf (
175 "Welcome to notmuch!\n\n"
176
177 "The goal of notmuch is to help you manage and search your collection of\n"
178 "email, and to efficiently keep up with the flow of email as it comes in.\n\n"
179
180 "Notmuch needs to know a few things about you such as your name and email\n"
181 "address, as well as the directory that contains your email. This is where\n"
182 "you already have mail stored and where messages will be delivered in the\n"
183 "future. This directory can contain any number of sub-directories. Regular\n"
184 "files in these directories should be individual email messages. If there\n"
185 "are other, non-email files (such as indexes maintained by other email\n"
186 "programs) then notmuch will do its best to detect those and ignore them.\n\n"
187
188 "If you already have your email being delivered to directories in either\n"
189 "maildir or mh format, then that's perfect. Mail storage that uses mbox\n"
190 "format, (where one mbox file contains many messages), will not work with\n"
191 "notmuch. If that's how your mail is currently stored, we recommend you\n"
192 "first convert it to maildir format with a utility such as mb2md. You can\n"
193 "continue configuring notmuch now, but be sure to complete the conversion\n"
194 "before you run \"notmuch new\" for the first time.\n\n");
195 }
196
197 static void
198 welcome_message_post_setup (void)
199 {
200     printf ("\n"
201 "Notmuch is now configured, and the configuration settings are saved in\n"
202 "a file in your home directory named .notmuch-config . If you'd like to\n"
203 "change the configuration in the future, you can either edit that file\n"
204 "directly or run \"notmuch setup\".\n\n"
205
206 "The next step is to run \"notmuch new\" which will create a database\n"
207 "that indexes all of your mail. Depending on the amount of mail you have\n"
208 "the initial indexing process can take a long time, so expect that.\n"
209 "Also, the resulting database will require roughly the same amount of\n"
210 "storage space as your current collection of email. So please ensure you\n"
211 "have sufficient storage space available now.\n\n");
212 }
213
214 int
215 notmuch_setup_command (unused (void *ctx),
216                        unused (int argc), unused (char *argv[]))
217 {
218     char *response = NULL;
219     size_t response_size;
220     notmuch_config_t *config;
221     char **old_other_emails;
222     size_t old_other_emails_len;
223     GPtrArray *other_emails;
224     unsigned int i;
225     int is_new;
226
227 #define prompt(format, ...)                             \
228     do {                                                \
229         printf (format, ##__VA_ARGS__);                 \
230         fflush (stdout);                                \
231         getline (&response, &response_size, stdin);     \
232         chomp_newline (response);                       \
233     } while (0)
234
235     config = notmuch_config_open (ctx, NULL, &is_new);
236
237     if (is_new)
238         welcome_message_pre_setup ();
239
240     prompt ("Your full name [%s]: ", notmuch_config_get_user_name (config));
241     if (strlen (response))
242         notmuch_config_set_user_name (config, response);
243
244     prompt ("Your primary email address [%s]: ",
245             notmuch_config_get_user_primary_email (config));
246     if (strlen (response))
247         notmuch_config_set_user_primary_email (config, response);
248
249     other_emails = g_ptr_array_new ();
250
251     old_other_emails = notmuch_config_get_user_other_email (config,
252                                              &old_other_emails_len);
253     for (i = 0; i < old_other_emails_len; i++) {
254         prompt ("Additional email address [%s]: ", old_other_emails[i]);
255         if (strlen (response))
256             g_ptr_array_add (other_emails, talloc_strdup (ctx, response));
257         else
258             g_ptr_array_add (other_emails, talloc_strdup (ctx,
259                                                          old_other_emails[i]));
260     }
261
262     do {
263         prompt ("Additional email address [Press 'Enter' if none]: ");
264         if (strlen (response))
265             g_ptr_array_add (other_emails, talloc_strdup (ctx, response));
266     } while (strlen (response));
267     if (other_emails->len)
268         notmuch_config_set_user_other_email (config,
269                                              (const char **)
270                                              other_emails->pdata,
271                                              other_emails->len);
272     g_ptr_array_free (other_emails, TRUE);
273
274     prompt ("Top-level directory of your email archive [%s]: ",
275             notmuch_config_get_database_path (config));
276     if (strlen (response)) {
277         const char *absolute_path;
278
279         absolute_path = make_path_absolute (ctx, response);
280         notmuch_config_set_database_path (config, absolute_path);
281     }
282
283     notmuch_config_save (config);
284
285     if (is_new)
286         welcome_message_post_setup ();
287
288     return 0;
289 }