]> git.notmuchmail.org Git - notmuch/blob - notmuch-config.c
Merge branch 'release'
[notmuch] / notmuch-config.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 https://www.gnu.org/licenses/ .
17  *
18  * Author: Carl Worth <cworth@cworth.org>
19  */
20
21 #include "notmuch-client.h"
22
23 #include <pwd.h>
24 #include <netdb.h>
25 #include <assert.h>
26
27 static const char toplevel_config_comment[] =
28     " .notmuch-config - Configuration file for the notmuch mail system\n"
29     "\n"
30     " For more information about notmuch, see https://notmuchmail.org";
31
32 static const char database_config_comment[] =
33     " Database configuration\n"
34     "\n"
35     " The only value supported here is 'path' which should be the top-level\n"
36     " directory where your mail currently exists and to where mail will be\n"
37     " delivered in the future. Files should be individual email messages.\n"
38     " Notmuch will store its database within a sub-directory of the path\n"
39     " configured here named \".notmuch\".\n";
40
41 static const char new_config_comment[] =
42     " Configuration for \"notmuch new\"\n"
43     "\n"
44     " The following options are supported here:\n"
45     "\n"
46     "\ttags     A list (separated by ';') of the tags that will be\n"
47     "\t added to all messages incorporated by \"notmuch new\".\n"
48     "\n"
49     "\tignore   A list (separated by ';') of file and directory names\n"
50     "\t that will not be searched for messages by \"notmuch new\".\n"
51     "\n"
52     "\t NOTE: *Every* file/directory that goes by one of those\n"
53     "\t names will be ignored, independent of its depth/location\n"
54     "\t in the mail store.\n";
55
56 static const char user_config_comment[] =
57     " User configuration\n"
58     "\n"
59     " Here is where you can let notmuch know how you would like to be\n"
60     " addressed. Valid settings are\n"
61     "\n"
62     "\tname             Your full name.\n"
63     "\tprimary_email    Your primary email address.\n"
64     "\tother_email      A list (separated by ';') of other email addresses\n"
65     "\t         at which you receive email.\n"
66     "\n"
67     " Notmuch will use the various email addresses configured here when\n"
68     " formatting replies. It will avoid including your own addresses in the\n"
69     " recipient list of replies, and will set the From address based on the\n"
70     " address to which the original email was addressed.\n";
71
72 static const char maildir_config_comment[] =
73     " Maildir compatibility configuration\n"
74     "\n"
75     " The following option is supported here:\n"
76     "\n"
77     "\tsynchronize_flags      Valid values are true and false.\n"
78     "\n"
79     "\tIf true, then the following maildir flags (in message filenames)\n"
80     "\twill be synchronized with the corresponding notmuch tags:\n"
81     "\n"
82     "\t\tFlag   Tag\n"
83     "\t\t----   -------\n"
84     "\t\tD      draft\n"
85     "\t\tF      flagged\n"
86     "\t\tP      passed\n"
87     "\t\tR      replied\n"
88     "\t\tS      unread (added when 'S' flag is not present)\n"
89     "\n"
90     "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
91     "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
92     "\tcommands will notice tag changes and update flags in filenames\n";
93
94 static const char search_config_comment[] =
95     " Search configuration\n"
96     "\n"
97     " The following option is supported here:\n"
98     "\n"
99     "\texclude_tags\n"
100     "\t\tA ;-separated list of tags that will be excluded from\n"
101     "\t\tsearch results by default.  Using an excluded tag in a\n"
102     "\t\tquery will override that exclusion.\n";
103
104 static const char crypto_config_comment[] =
105     " Cryptography related configuration\n"
106     "\n"
107     " The following option is supported here:\n"
108     "\n"
109     "\tgpg_path\n"
110     "\t\tbinary name or full path to invoke gpg.\n";
111
112 struct _notmuch_config {
113     char *filename;
114     GKeyFile *key_file;
115     notmuch_bool_t is_new;
116
117     char *database_path;
118     char *crypto_gpg_path;
119     char *user_name;
120     char *user_primary_email;
121     const char **user_other_email;
122     size_t user_other_email_length;
123     const char **new_tags;
124     size_t new_tags_length;
125     const char **new_ignore;
126     size_t new_ignore_length;
127     notmuch_bool_t maildir_synchronize_flags;
128     const char **search_exclude_tags;
129     size_t search_exclude_tags_length;
130 };
131
132 static int
133 notmuch_config_destructor (notmuch_config_t *config)
134 {
135     if (config->key_file)
136         g_key_file_free (config->key_file);
137
138     return 0;
139 }
140
141 static char *
142 get_name_from_passwd_file (void *ctx)
143 {
144     long pw_buf_size;
145     char *pw_buf;
146     struct passwd passwd, *ignored;
147     char *name;
148     int e;
149
150     pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
151     if (pw_buf_size == -1) pw_buf_size = 64;
152     pw_buf = talloc_size (ctx, pw_buf_size);
153
154     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
155                             pw_buf_size, &ignored)) == ERANGE) {
156         pw_buf_size = pw_buf_size * 2;
157         pw_buf = talloc_zero_size(ctx, pw_buf_size);
158     }
159
160     if (e == 0) {
161         char *comma = strchr (passwd.pw_gecos, ',');
162         if (comma)
163             name = talloc_strndup (ctx, passwd.pw_gecos,
164                                    comma - passwd.pw_gecos);
165         else
166             name = talloc_strdup (ctx, passwd.pw_gecos);
167     } else {
168         name = talloc_strdup (ctx, "");
169     }
170
171     talloc_free (pw_buf);
172
173     return name;
174 }
175
176 static char *
177 get_username_from_passwd_file (void *ctx)
178 {
179     long pw_buf_size;
180     char *pw_buf;
181     struct passwd passwd, *ignored;
182     char *name;
183     int e;
184
185     pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
186     if (pw_buf_size == -1) pw_buf_size = 64;
187     pw_buf = talloc_zero_size (ctx, pw_buf_size);
188
189     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
190                             pw_buf_size, &ignored)) == ERANGE) {
191         pw_buf_size = pw_buf_size * 2;
192         pw_buf = talloc_zero_size(ctx, pw_buf_size);
193     }
194
195     if (e == 0)
196         name = talloc_strdup (ctx, passwd.pw_name);
197     else
198         name = talloc_strdup (ctx, "");
199
200     talloc_free (pw_buf);
201
202     return name;
203 }
204
205 static notmuch_bool_t
206 get_config_from_file (notmuch_config_t *config, notmuch_bool_t create_new)
207 {
208     #define BUF_SIZE 4096
209     char *config_str = NULL;
210     int config_len = 0;
211     int config_bufsize = BUF_SIZE;
212     size_t len;
213     GError *error = NULL;
214     notmuch_bool_t ret = FALSE;
215
216     FILE *fp = fopen(config->filename, "r");
217     if (fp == NULL) {
218         /* If create_new is true, then the caller is prepared for a
219          * default configuration file in the case of FILE NOT FOUND.
220          */
221         if (create_new) {
222             config->is_new = TRUE;
223             ret = TRUE;
224             goto out;
225         } else if (errno == ENOENT) {
226             fprintf (stderr, "Configuration file %s not found.\n"
227                      "Try running 'notmuch setup' to create a configuration.\n",
228                      config->filename);
229             goto out;
230         } else {
231             fprintf (stderr, "Error opening config file '%s': %s\n"
232                      "Try running 'notmuch setup' to create a configuration.\n",
233                      config->filename, strerror(errno));
234             goto out;
235         }
236     }
237
238     config_str = talloc_zero_array (config, char, config_bufsize);
239     if (config_str == NULL) {
240         fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
241         goto out;
242     }
243
244     while ((len = fread (config_str + config_len, 1,
245                          config_bufsize - config_len, fp)) > 0) {
246         config_len += len;
247         if (config_len == config_bufsize) {
248             config_bufsize += BUF_SIZE;
249             config_str = talloc_realloc (config, config_str, char, config_bufsize);
250             if (config_str == NULL) {
251                 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
252                          config->filename);
253                 goto out;
254             }
255         }
256     }
257
258     if (ferror (fp)) {
259         fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
260         goto out;
261     }
262
263     if (g_key_file_load_from_data (config->key_file, config_str, config_len,
264                                    G_KEY_FILE_KEEP_COMMENTS, &error)) {
265         ret = TRUE;
266         goto out;
267     }
268
269     fprintf (stderr, "Error parsing config file '%s': %s\n",
270              config->filename, error->message);
271
272     g_error_free (error);
273
274 out:
275     if (fp)
276         fclose(fp);
277
278     if (config_str)
279         talloc_free(config_str);
280
281     return ret;
282 }
283
284 /* Open the named notmuch configuration file. If the filename is NULL,
285  * the value of the environment variable $NOTMUCH_CONFIG will be used.
286  * If $NOTMUCH_CONFIG is unset, the default configuration file
287  * ($HOME/.notmuch-config) will be used.
288  *
289  * If any error occurs, (out of memory, or a permission-denied error,
290  * etc.), this function will print a message to stderr and return
291  * NULL.
292  *
293  * FILE NOT FOUND: When the specified configuration file (whether from
294  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
295  * exist, the behavior of this function depends on the 'is_new_ret'
296  * variable.
297  *
298  *      If is_new_ret is NULL, then a "file not found" message will be
299  *      printed to stderr and NULL will be returned.
300
301  *      If is_new_ret is non-NULL then a default configuration will be
302  *      returned and *is_new_ret will be set to 1 on return so that
303  *      the caller can recognize this case.
304  *
305  *      These default configuration settings are determined as
306  *      follows:
307  *
308  *              database_path:          $MAILDIR, otherwise $HOME/mail
309  *
310  *              user_name:              $NAME variable if set, otherwise
311  *                                      read from /etc/passwd
312  *
313  *              user_primary_mail:      $EMAIL variable if set, otherwise
314  *                                      constructed from the username and
315  *                                      hostname of the current machine.
316  *
317  *              user_other_email:       Not set.
318  *
319  *      The default configuration also contains comments to guide the
320  *      user in editing the file directly.
321  */
322 notmuch_config_t *
323 notmuch_config_open (void *ctx,
324                      const char *filename,
325                      notmuch_bool_t create_new)
326 {
327     GError *error = NULL;
328     size_t tmp;
329     char *notmuch_config_env = NULL;
330     int file_had_database_group;
331     int file_had_new_group;
332     int file_had_user_group;
333     int file_had_maildir_group;
334     int file_had_search_group;
335     int file_had_crypto_group;
336
337     notmuch_config_t *config = talloc_zero (ctx, notmuch_config_t);
338     if (config == NULL) {
339         fprintf (stderr, "Out of memory.\n");
340         return NULL;
341     }
342     
343     talloc_set_destructor (config, notmuch_config_destructor);
344
345     /* non-zero defaults */
346     config->maildir_synchronize_flags = TRUE;
347
348     if (filename) {
349         config->filename = talloc_strdup (config, filename);
350     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
351         config->filename = talloc_strdup (config, notmuch_config_env);
352     } else {
353         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
354                                             getenv ("HOME"));
355     }
356
357     config->key_file = g_key_file_new ();
358
359     if (! get_config_from_file (config, create_new)) {
360         talloc_free (config);
361         return NULL;
362     }
363
364     /* Whenever we know of configuration sections that don't appear in
365      * the configuration file, we add some comments to help the user
366      * understand what can be done.
367      *
368      * It would be convenient to just add those comments now, but
369      * apparently g_key_file will clear any comments when keys are
370      * added later that create the groups. So we have to check for the
371      * groups now, but add the comments only after setting all of our
372      * values.
373      */
374     file_had_database_group = g_key_file_has_group (config->key_file,
375                                                     "database");
376     file_had_new_group = g_key_file_has_group (config->key_file, "new");
377     file_had_user_group = g_key_file_has_group (config->key_file, "user");
378     file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
379     file_had_search_group = g_key_file_has_group (config->key_file, "search");
380     file_had_crypto_group = g_key_file_has_group (config->key_file, "crypto");
381
382     if (notmuch_config_get_database_path (config) == NULL) {
383         char *path = getenv ("MAILDIR");
384         if (path)
385             path = talloc_strdup (config, path);
386         else
387             path = talloc_asprintf (config, "%s/mail",
388                                     getenv ("HOME"));
389         notmuch_config_set_database_path (config, path);
390         talloc_free (path);
391     }
392
393     if (notmuch_config_get_user_name (config) == NULL) {
394         char *name = getenv ("NAME");
395         if (name)
396             name = talloc_strdup (config, name);
397         else
398             name = get_name_from_passwd_file (config);
399         notmuch_config_set_user_name (config, name);
400         talloc_free (name);
401     }
402
403     if (notmuch_config_get_user_primary_email (config) == NULL) {
404         char *email = getenv ("EMAIL");
405         if (email) {
406             notmuch_config_set_user_primary_email (config, email);
407         } else {
408             char hostname[256];
409             struct hostent *hostent;
410             const char *domainname;
411
412             char *username = get_username_from_passwd_file (config);
413
414             gethostname (hostname, 256);
415             hostname[255] = '\0';
416
417             hostent = gethostbyname (hostname);
418             if (hostent && (domainname = strchr (hostent->h_name, '.')))
419                 domainname += 1;
420             else
421                 domainname = "(none)";
422
423             email = talloc_asprintf (config, "%s@%s.%s",
424                                      username, hostname, domainname);
425
426             notmuch_config_set_user_primary_email (config, email);
427
428             talloc_free (username);
429             talloc_free (email);
430         }
431     }
432
433     if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
434         const char *tags[] = { "unread", "inbox" };
435         notmuch_config_set_new_tags (config, tags, 2);
436     }
437
438     if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
439         notmuch_config_set_new_ignore (config, NULL, 0);
440     }
441
442     if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
443         if (config->is_new) {
444             const char *tags[] = { "deleted", "spam" };
445             notmuch_config_set_search_exclude_tags (config, tags, 2);
446         } else {
447             notmuch_config_set_search_exclude_tags (config, NULL, 0);
448         }
449     }
450
451     error = NULL;
452     config->maildir_synchronize_flags =
453         g_key_file_get_boolean (config->key_file,
454                                 "maildir", "synchronize_flags", &error);
455     if (error) {
456         notmuch_config_set_maildir_synchronize_flags (config, TRUE);
457         g_error_free (error);
458     }
459
460     if (notmuch_config_get_crypto_gpg_path (config) == NULL) {
461         notmuch_config_set_crypto_gpg_path (config, "gpg");
462     }
463     
464     /* Whenever we know of configuration sections that don't appear in
465      * the configuration file, we add some comments to help the user
466      * understand what can be done. */
467     if (config->is_new)
468         g_key_file_set_comment (config->key_file, NULL, NULL,
469                                 toplevel_config_comment, NULL);
470
471     if (! file_had_database_group)
472         g_key_file_set_comment (config->key_file, "database", NULL,
473                                 database_config_comment, NULL);
474
475     if (! file_had_new_group)
476         g_key_file_set_comment (config->key_file, "new", NULL,
477                                 new_config_comment, NULL);
478
479     if (! file_had_user_group)
480         g_key_file_set_comment (config->key_file, "user", NULL,
481                                 user_config_comment, NULL);
482
483     if (! file_had_maildir_group)
484         g_key_file_set_comment (config->key_file, "maildir", NULL,
485                                 maildir_config_comment, NULL);
486
487     if (! file_had_search_group)
488         g_key_file_set_comment (config->key_file, "search", NULL,
489                                 search_config_comment, NULL);
490
491     if (! file_had_crypto_group)
492         g_key_file_set_comment (config->key_file, "crypto", NULL,
493                                 crypto_config_comment, NULL);
494
495     return config;
496 }
497
498 /* Close the given notmuch_config_t object, freeing all resources.
499  * 
500  * Note: Any changes made to the configuration are *not* saved by this
501  * function. To save changes, call notmuch_config_save before
502  * notmuch_config_close.
503 */
504 void
505 notmuch_config_close (notmuch_config_t *config)
506 {
507     talloc_free (config);
508 }
509
510 /* Save any changes made to the notmuch configuration.
511  *
512  * Any comments originally in the file will be preserved.
513  *
514  * Returns 0 if successful, and 1 in case of any error, (after
515  * printing a description of the error to stderr).
516  */
517 int
518 notmuch_config_save (notmuch_config_t *config)
519 {
520     size_t length;
521     char *data, *filename;
522     GError *error = NULL;
523
524     data = g_key_file_to_data (config->key_file, &length, NULL);
525     if (data == NULL) {
526         fprintf (stderr, "Out of memory.\n");
527         return 1;
528     }
529
530     /* Try not to overwrite symlinks. */
531     filename = canonicalize_file_name (config->filename);
532     if (! filename) {
533         if (errno == ENOENT) {
534             filename = strdup (config->filename);
535             if (! filename) {
536                 fprintf (stderr, "Out of memory.\n");
537                 g_free (data);
538                 return 1;
539             }
540         } else {
541             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
542                      strerror (errno));
543             g_free (data);
544             return 1;
545         }
546     }
547
548     if (! g_file_set_contents (filename, data, length, &error)) {
549         if (strcmp (filename, config->filename) != 0) {
550             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
551                      config->filename, filename, error->message);
552         } else {
553             fprintf (stderr, "Error saving configuration to %s: %s\n",
554                      filename, error->message);
555         }
556         g_error_free (error);
557         free (filename);
558         g_free (data);
559         return 1;
560     }
561
562     free (filename);
563     g_free (data);
564     return 0;
565 }
566
567 notmuch_bool_t
568 notmuch_config_is_new (notmuch_config_t *config)
569 {
570     return config->is_new;
571 }
572
573 static const char *
574 _config_get (notmuch_config_t *config, char **field,
575              const char *group, const char *key)
576 {
577     /* read from config file and cache value, if not cached already */
578     if (*field == NULL) {
579         char *value;
580         value = g_key_file_get_string (config->key_file, group, key, NULL);
581         if (value) {
582             *field = talloc_strdup (config, value);
583             free (value);
584         }
585     }
586     return *field;
587 }
588
589 static void
590 _config_set (notmuch_config_t *config, char **field,
591              const char *group, const char *key, const char *value)
592 {
593     g_key_file_set_string (config->key_file, group, key, value);
594
595     /* drop the cached value */
596     talloc_free (*field);
597     *field = NULL;
598 }
599
600 static const char **
601 _config_get_list (notmuch_config_t *config,
602                   const char *section, const char *key,
603                   const char ***outlist, size_t *list_length, size_t *ret_length)
604 {
605     assert(outlist);
606
607     /* read from config file and cache value, if not cached already */
608     if (*outlist == NULL) {
609
610         char **inlist = g_key_file_get_string_list (config->key_file,
611                                              section, key, list_length, NULL);
612         if (inlist) {
613             unsigned int i;
614
615             *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
616
617             for (i = 0; i < *list_length; i++)
618                 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
619
620             (*outlist)[i] = NULL;
621
622             g_strfreev (inlist);
623         }
624     }
625
626     if (ret_length)
627         *ret_length = *list_length;
628
629     return *outlist;
630 }
631
632 static void
633 _config_set_list (notmuch_config_t *config,
634                   const char *group, const char *key,
635                   const char *list[],
636                   size_t length, const char ***config_var )
637 {
638     g_key_file_set_string_list (config->key_file, group, key, list, length);
639
640     /* drop the cached value */
641     talloc_free (*config_var);
642     *config_var = NULL;
643 }
644
645 const char *
646 notmuch_config_get_database_path (notmuch_config_t *config)
647 {
648     return _config_get (config, &config->database_path, "database", "path");
649 }
650
651 void
652 notmuch_config_set_database_path (notmuch_config_t *config,
653                                   const char *database_path)
654 {
655     _config_set (config, &config->database_path, "database", "path", database_path);
656 }
657
658 const char *
659 notmuch_config_get_user_name (notmuch_config_t *config)
660 {
661     return _config_get (config, &config->user_name, "user", "name");
662 }
663
664 void
665 notmuch_config_set_user_name (notmuch_config_t *config,
666                               const char *user_name)
667 {
668     _config_set (config, &config->user_name, "user", "name", user_name);
669 }
670
671 const char *
672 notmuch_config_get_user_primary_email (notmuch_config_t *config)
673 {
674     return _config_get (config, &config->user_primary_email, "user", "primary_email");
675 }
676
677 void
678 notmuch_config_set_user_primary_email (notmuch_config_t *config,
679                                        const char *primary_email)
680 {
681     _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
682 }
683
684 const char **
685 notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
686 {
687     return _config_get_list (config, "user", "other_email",
688                              &(config->user_other_email),
689                              &(config->user_other_email_length), length);
690 }
691
692 const char **
693 notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
694 {
695     return _config_get_list (config, "new", "tags",
696                              &(config->new_tags),
697                              &(config->new_tags_length), length);
698 }
699
700 const char **
701 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
702 {
703     return _config_get_list (config, "new", "ignore",
704                              &(config->new_ignore),
705                              &(config->new_ignore_length), length);
706 }
707
708 void
709 notmuch_config_set_user_other_email (notmuch_config_t *config,
710                                      const char *list[],
711                                      size_t length)
712 {
713     _config_set_list (config, "user", "other_email", list, length,
714                      &(config->user_other_email));
715 }
716
717 void
718 notmuch_config_set_new_tags (notmuch_config_t *config,
719                                      const char *list[],
720                                      size_t length)
721 {
722     _config_set_list (config, "new", "tags", list, length,
723                      &(config->new_tags));
724 }
725
726 void
727 notmuch_config_set_new_ignore (notmuch_config_t *config,
728                                const char *list[],
729                                size_t length)
730 {
731     _config_set_list (config, "new", "ignore", list, length,
732                      &(config->new_ignore));
733 }
734
735 const char **
736 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
737 {
738     return _config_get_list (config, "search", "exclude_tags",
739                              &(config->search_exclude_tags),
740                              &(config->search_exclude_tags_length), length);
741 }
742
743 void
744 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
745                                       const char *list[],
746                                       size_t length)
747 {
748     _config_set_list (config, "search", "exclude_tags", list, length,
749                       &(config->search_exclude_tags));
750 }
751
752 const char *
753 notmuch_config_get_crypto_gpg_path (notmuch_config_t *config)
754 {
755     return _config_get (config, &config->crypto_gpg_path, "crypto", "gpg_path");
756 }
757
758 void
759 notmuch_config_set_crypto_gpg_path (notmuch_config_t *config,
760                               const char *gpg_path)
761 {
762     _config_set (config, &config->crypto_gpg_path, "crypto", "gpg_path", gpg_path);
763 }
764
765
766 /* Given a configuration item of the form <group>.<key> return the
767  * component group and key. If any error occurs, print a message on
768  * stderr and return 1. Otherwise, return 0.
769  *
770  * Note: This function modifies the original 'item' string.
771  */
772 static int
773 _item_split (char *item, char **group, char **key)
774 {
775     char *period;
776
777     *group = item;
778
779     period = strchr (item, '.');
780     if (period == NULL || *(period+1) == '\0') {
781         fprintf (stderr,
782                  "Invalid configuration name: %s\n"
783                  "(Should be of the form <section>.<item>)\n", item);
784         return 1;
785     }
786
787     *period = '\0';
788     *key = period + 1;
789
790     return 0;
791 }
792
793 #define BUILT_WITH_PREFIX "built_with."
794 #define QUERY_PREFIX "query."
795
796 static int
797 _print_db_config(notmuch_config_t *config, const char *name)
798 {
799     notmuch_database_t *notmuch;
800     char *val;
801
802     if (notmuch_database_open (notmuch_config_get_database_path (config),
803                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
804         return EXIT_FAILURE;
805
806     /* XXX Handle UUID mismatch? */
807
808     if (print_status_database ("notmuch config", notmuch,
809                                notmuch_database_get_config (notmuch, name, &val)))
810         return EXIT_FAILURE;
811
812      puts (val);
813
814     return EXIT_SUCCESS;
815 }
816
817 static int
818 notmuch_config_command_get (notmuch_config_t *config, char *item)
819 {
820     if (strcmp(item, "database.path") == 0) {
821         printf ("%s\n", notmuch_config_get_database_path (config));
822     } else if (strcmp(item, "user.name") == 0) {
823         printf ("%s\n", notmuch_config_get_user_name (config));
824     } else if (strcmp(item, "user.primary_email") == 0) {
825         printf ("%s\n", notmuch_config_get_user_primary_email (config));
826     } else if (strcmp(item, "user.other_email") == 0) {
827         const char **other_email;
828         size_t i, length;
829         
830         other_email = notmuch_config_get_user_other_email (config, &length);
831         for (i = 0; i < length; i++)
832             printf ("%s\n", other_email[i]);
833     } else if (strcmp(item, "new.tags") == 0) {
834         const char **tags;
835         size_t i, length;
836
837         tags = notmuch_config_get_new_tags (config, &length);
838         for (i = 0; i < length; i++)
839             printf ("%s\n", tags[i]);
840     } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
841         printf ("%s\n",
842                 notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
843     } else if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) {
844         return _print_db_config (config, item);
845     } else {
846         char **value;
847         size_t i, length;
848         char *group, *key;
849
850         if (_item_split (item, &group, &key))
851             return 1;
852
853         value = g_key_file_get_string_list (config->key_file,
854                                             group, key,
855                                             &length, NULL);
856         if (value == NULL) {
857             fprintf (stderr, "Unknown configuration item: %s.%s\n",
858                      group, key);
859             return 1;
860         }
861
862         for (i = 0; i < length; i++)
863             printf ("%s\n", value[i]);
864
865         g_strfreev (value);
866     }
867
868     return 0;
869 }
870
871 static int
872 _set_db_config(notmuch_config_t *config, const char *key, int argc, char **argv)
873 {
874     notmuch_database_t *notmuch;
875     const char *val = "";
876
877     if (argc > 1) {
878         /* XXX handle lists? */
879         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
880         return EXIT_FAILURE;
881     }
882
883     if (argc > 0) {
884         val = argv[0];
885     }
886
887     if (notmuch_database_open (notmuch_config_get_database_path (config),
888                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
889         return EXIT_FAILURE;
890
891     /* XXX Handle UUID mismatch? */
892
893     if (print_status_database ("notmuch config", notmuch,
894                                notmuch_database_set_config (notmuch, key, val)))
895         return EXIT_FAILURE;
896
897     if (print_status_database ("notmuch config", notmuch,
898                                notmuch_database_close (notmuch)))
899         return EXIT_FAILURE;
900
901     return EXIT_SUCCESS;
902 }
903
904 static int
905 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
906 {
907     char *group, *key;
908
909     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
910         fprintf (stderr, "Error: read only option: %s\n", item);
911         return 1;
912     }
913
914     if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) {
915         return _set_db_config (config, item, argc, argv);
916     }
917
918     if (_item_split (item, &group, &key))
919         return 1;
920
921     /* With only the name of an item, we clear it from the
922      * configuration file.
923      *
924      * With a single value, we set it as a string.
925      *
926      * With multiple values, we set them as a string list.
927      */
928     switch (argc) {
929     case 0:
930         g_key_file_remove_key (config->key_file, group, key, NULL);
931         break;
932     case 1:
933         g_key_file_set_string (config->key_file, group, key, argv[0]);
934         break;
935     default:
936         g_key_file_set_string_list (config->key_file, group, key,
937                                     (const gchar **) argv, argc);
938         break;
939     }
940
941     return notmuch_config_save (config);
942 }
943
944 static
945 void
946 _notmuch_config_list_built_with ()
947 {
948     printf("%scompact=%s\n",
949            BUILT_WITH_PREFIX,
950            notmuch_built_with ("compact") ? "true" : "false");
951     printf("%sfield_processor=%s\n",
952            BUILT_WITH_PREFIX,
953            notmuch_built_with ("field_processor") ? "true" : "false");
954     printf("%sretry_lock=%s\n",
955            BUILT_WITH_PREFIX,
956            notmuch_built_with ("retry_lock") ? "true" : "false");
957 }
958
959 static int
960 _list_db_config (notmuch_config_t *config)
961 {
962     notmuch_database_t *notmuch;
963     notmuch_config_list_t *list;
964
965     if (notmuch_database_open (notmuch_config_get_database_path (config),
966                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
967         return EXIT_FAILURE;
968
969     /* XXX Handle UUID mismatch? */
970
971
972     if (print_status_database ("notmuch config", notmuch,
973                                notmuch_database_get_config_list (notmuch, "", &list)))
974         return EXIT_FAILURE;
975
976     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
977         printf("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
978     }
979     notmuch_config_list_destroy (list);
980
981    return EXIT_SUCCESS;
982 }
983
984 static int
985 notmuch_config_command_list (notmuch_config_t *config)
986 {
987     char **groups;
988     size_t g, groups_length;
989
990     groups = g_key_file_get_groups (config->key_file, &groups_length);
991     if (groups == NULL)
992         return 1;
993
994     for (g = 0; g < groups_length; g++) {
995         char **keys;
996         size_t k, keys_length;
997
998         keys = g_key_file_get_keys (config->key_file,
999                                     groups[g], &keys_length, NULL);
1000         if (keys == NULL)
1001             continue;
1002
1003         for (k = 0; k < keys_length; k++) {
1004             char *value;
1005
1006             value = g_key_file_get_string (config->key_file,
1007                                            groups[g], keys[k], NULL);
1008             if (value != NULL) {
1009                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
1010                 free (value);
1011             }
1012         }
1013
1014         g_strfreev (keys);
1015     }
1016
1017     g_strfreev (groups);
1018
1019     _notmuch_config_list_built_with ();
1020     return _list_db_config (config);
1021 }
1022
1023 int
1024 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
1025 {
1026     int ret;
1027     int opt_index;
1028
1029     opt_index = notmuch_minimal_options ("config", argc, argv);
1030     if (opt_index < 0)
1031         return EXIT_FAILURE;
1032
1033     if (notmuch_requested_db_uuid)
1034         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
1035                  notmuch_requested_db_uuid);
1036
1037     /* skip at least subcommand argument */
1038     argc-= opt_index;
1039     argv+= opt_index;
1040
1041     if (argc < 1) {
1042         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
1043         return EXIT_FAILURE;
1044     }
1045
1046     if (strcmp (argv[0], "get") == 0) {
1047         if (argc != 2) {
1048             fprintf (stderr, "Error: notmuch config get requires exactly "
1049                      "one argument.\n");
1050             return EXIT_FAILURE;
1051         }
1052         ret = notmuch_config_command_get (config, argv[1]);
1053     } else if (strcmp (argv[0], "set") == 0) {
1054         if (argc < 2) {
1055             fprintf (stderr, "Error: notmuch config set requires at least "
1056                      "one argument.\n");
1057             return EXIT_FAILURE;
1058         }
1059         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
1060     } else if (strcmp (argv[0], "list") == 0) {
1061         ret = notmuch_config_command_list (config);
1062     } else {
1063         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
1064                  argv[0]);
1065         return EXIT_FAILURE;
1066     }
1067
1068     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
1069
1070 }
1071
1072 notmuch_bool_t
1073 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
1074 {
1075     return config->maildir_synchronize_flags;
1076 }
1077
1078 void
1079 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
1080                                               notmuch_bool_t synchronize_flags)
1081 {
1082     g_key_file_set_boolean (config->key_file,
1083                             "maildir", "synchronize_flags", synchronize_flags);
1084     config->maildir_synchronize_flags = synchronize_flags;
1085 }