]> git.cworth.org Git - notmuch/blob - notmuch-config.c
cli: abstract config file reading to a separate function
[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     GError *error = NULL;
209     notmuch_bool_t ret = FALSE;
210
211     if (g_key_file_load_from_file (config->key_file, config->filename,
212                                    G_KEY_FILE_KEEP_COMMENTS, &error))
213         return TRUE;
214
215     if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT) {
216         /* If create_new is true, then the caller is prepared for a
217          * default configuration file in the case of FILE NOT FOUND.
218          */
219         if (create_new) {
220             config->is_new = TRUE;
221             ret = TRUE;
222         } else {
223             fprintf (stderr, "Configuration file %s not found.\n"
224                      "Try running 'notmuch setup' to create a configuration.\n",
225                      config->filename);
226         }
227     } else {
228         fprintf (stderr, "Error reading configuration file %s: %s\n",
229                  config->filename, error->message);
230     }
231
232     g_error_free (error);
233
234     return ret;
235 }
236
237 /* Open the named notmuch configuration file. If the filename is NULL,
238  * the value of the environment variable $NOTMUCH_CONFIG will be used.
239  * If $NOTMUCH_CONFIG is unset, the default configuration file
240  * ($HOME/.notmuch-config) will be used.
241  *
242  * If any error occurs, (out of memory, or a permission-denied error,
243  * etc.), this function will print a message to stderr and return
244  * NULL.
245  *
246  * FILE NOT FOUND: When the specified configuration file (whether from
247  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
248  * exist, the behavior of this function depends on the 'is_new_ret'
249  * variable.
250  *
251  *      If is_new_ret is NULL, then a "file not found" message will be
252  *      printed to stderr and NULL will be returned.
253
254  *      If is_new_ret is non-NULL then a default configuration will be
255  *      returned and *is_new_ret will be set to 1 on return so that
256  *      the caller can recognize this case.
257  *
258  *      These default configuration settings are determined as
259  *      follows:
260  *
261  *              database_path:          $MAILDIR, otherwise $HOME/mail
262  *
263  *              user_name:              $NAME variable if set, otherwise
264  *                                      read from /etc/passwd
265  *
266  *              user_primary_mail:      $EMAIL variable if set, otherwise
267  *                                      constructed from the username and
268  *                                      hostname of the current machine.
269  *
270  *              user_other_email:       Not set.
271  *
272  *      The default configuration also contains comments to guide the
273  *      user in editing the file directly.
274  */
275 notmuch_config_t *
276 notmuch_config_open (void *ctx,
277                      const char *filename,
278                      notmuch_bool_t create_new)
279 {
280     GError *error = NULL;
281     size_t tmp;
282     char *notmuch_config_env = NULL;
283     int file_had_database_group;
284     int file_had_new_group;
285     int file_had_user_group;
286     int file_had_maildir_group;
287     int file_had_search_group;
288     int file_had_crypto_group;
289
290     notmuch_config_t *config = talloc (ctx, notmuch_config_t);
291     if (config == NULL) {
292         fprintf (stderr, "Out of memory.\n");
293         return NULL;
294     }
295     
296     talloc_set_destructor (config, notmuch_config_destructor);
297
298     if (filename) {
299         config->filename = talloc_strdup (config, filename);
300     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
301         config->filename = talloc_strdup (config, notmuch_config_env);
302     } else {
303         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
304                                             getenv ("HOME"));
305     }
306
307     config->key_file = g_key_file_new ();
308
309     config->is_new = FALSE;
310     config->database_path = NULL;
311     config->user_name = NULL;
312     config->user_primary_email = NULL;
313     config->user_other_email = NULL;
314     config->user_other_email_length = 0;
315     config->new_tags = NULL;
316     config->new_tags_length = 0;
317     config->new_ignore = NULL;
318     config->new_ignore_length = 0;
319     config->maildir_synchronize_flags = TRUE;
320     config->search_exclude_tags = NULL;
321     config->search_exclude_tags_length = 0;
322     config->crypto_gpg_path = NULL;
323
324     if (! get_config_from_file (config, create_new)) {
325         talloc_free (config);
326         return NULL;
327     }
328
329     /* Whenever we know of configuration sections that don't appear in
330      * the configuration file, we add some comments to help the user
331      * understand what can be done.
332      *
333      * It would be convenient to just add those comments now, but
334      * apparently g_key_file will clear any comments when keys are
335      * added later that create the groups. So we have to check for the
336      * groups now, but add the comments only after setting all of our
337      * values.
338      */
339     file_had_database_group = g_key_file_has_group (config->key_file,
340                                                     "database");
341     file_had_new_group = g_key_file_has_group (config->key_file, "new");
342     file_had_user_group = g_key_file_has_group (config->key_file, "user");
343     file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
344     file_had_search_group = g_key_file_has_group (config->key_file, "search");
345     file_had_crypto_group = g_key_file_has_group (config->key_file, "crypto");
346
347     if (notmuch_config_get_database_path (config) == NULL) {
348         char *path = getenv ("MAILDIR");
349         if (path)
350             path = talloc_strdup (config, path);
351         else
352             path = talloc_asprintf (config, "%s/mail",
353                                     getenv ("HOME"));
354         notmuch_config_set_database_path (config, path);
355         talloc_free (path);
356     }
357
358     if (notmuch_config_get_user_name (config) == NULL) {
359         char *name = getenv ("NAME");
360         if (name)
361             name = talloc_strdup (config, name);
362         else
363             name = get_name_from_passwd_file (config);
364         notmuch_config_set_user_name (config, name);
365         talloc_free (name);
366     }
367
368     if (notmuch_config_get_user_primary_email (config) == NULL) {
369         char *email = getenv ("EMAIL");
370         if (email) {
371             notmuch_config_set_user_primary_email (config, email);
372         } else {
373             char hostname[256];
374             struct hostent *hostent;
375             const char *domainname;
376
377             char *username = get_username_from_passwd_file (config);
378
379             gethostname (hostname, 256);
380             hostname[255] = '\0';
381
382             hostent = gethostbyname (hostname);
383             if (hostent && (domainname = strchr (hostent->h_name, '.')))
384                 domainname += 1;
385             else
386                 domainname = "(none)";
387
388             email = talloc_asprintf (config, "%s@%s.%s",
389                                      username, hostname, domainname);
390
391             notmuch_config_set_user_primary_email (config, email);
392
393             talloc_free (username);
394             talloc_free (email);
395         }
396     }
397
398     if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
399         const char *tags[] = { "unread", "inbox" };
400         notmuch_config_set_new_tags (config, tags, 2);
401     }
402
403     if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
404         notmuch_config_set_new_ignore (config, NULL, 0);
405     }
406
407     if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
408         if (config->is_new) {
409             const char *tags[] = { "deleted", "spam" };
410             notmuch_config_set_search_exclude_tags (config, tags, 2);
411         } else {
412             notmuch_config_set_search_exclude_tags (config, NULL, 0);
413         }
414     }
415
416     error = NULL;
417     config->maildir_synchronize_flags =
418         g_key_file_get_boolean (config->key_file,
419                                 "maildir", "synchronize_flags", &error);
420     if (error) {
421         notmuch_config_set_maildir_synchronize_flags (config, TRUE);
422         g_error_free (error);
423     }
424
425     if (notmuch_config_get_crypto_gpg_path (config) == NULL) {
426         notmuch_config_set_crypto_gpg_path (config, "gpg");
427     }
428     
429     /* Whenever we know of configuration sections that don't appear in
430      * the configuration file, we add some comments to help the user
431      * understand what can be done. */
432     if (config->is_new)
433         g_key_file_set_comment (config->key_file, NULL, NULL,
434                                 toplevel_config_comment, NULL);
435
436     if (! file_had_database_group)
437         g_key_file_set_comment (config->key_file, "database", NULL,
438                                 database_config_comment, NULL);
439
440     if (! file_had_new_group)
441         g_key_file_set_comment (config->key_file, "new", NULL,
442                                 new_config_comment, NULL);
443
444     if (! file_had_user_group)
445         g_key_file_set_comment (config->key_file, "user", NULL,
446                                 user_config_comment, NULL);
447
448     if (! file_had_maildir_group)
449         g_key_file_set_comment (config->key_file, "maildir", NULL,
450                                 maildir_config_comment, NULL);
451
452     if (! file_had_search_group)
453         g_key_file_set_comment (config->key_file, "search", NULL,
454                                 search_config_comment, NULL);
455
456     if (! file_had_crypto_group)
457         g_key_file_set_comment (config->key_file, "crypto", NULL,
458                                 crypto_config_comment, NULL);
459
460     return config;
461 }
462
463 /* Close the given notmuch_config_t object, freeing all resources.
464  * 
465  * Note: Any changes made to the configuration are *not* saved by this
466  * function. To save changes, call notmuch_config_save before
467  * notmuch_config_close.
468 */
469 void
470 notmuch_config_close (notmuch_config_t *config)
471 {
472     talloc_free (config);
473 }
474
475 /* Save any changes made to the notmuch configuration.
476  *
477  * Any comments originally in the file will be preserved.
478  *
479  * Returns 0 if successful, and 1 in case of any error, (after
480  * printing a description of the error to stderr).
481  */
482 int
483 notmuch_config_save (notmuch_config_t *config)
484 {
485     size_t length;
486     char *data, *filename;
487     GError *error = NULL;
488
489     data = g_key_file_to_data (config->key_file, &length, NULL);
490     if (data == NULL) {
491         fprintf (stderr, "Out of memory.\n");
492         return 1;
493     }
494
495     /* Try not to overwrite symlinks. */
496     filename = canonicalize_file_name (config->filename);
497     if (! filename) {
498         if (errno == ENOENT) {
499             filename = strdup (config->filename);
500             if (! filename) {
501                 fprintf (stderr, "Out of memory.\n");
502                 g_free (data);
503                 return 1;
504             }
505         } else {
506             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
507                      strerror (errno));
508             g_free (data);
509             return 1;
510         }
511     }
512
513     if (! g_file_set_contents (filename, data, length, &error)) {
514         if (strcmp (filename, config->filename) != 0) {
515             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
516                      config->filename, filename, error->message);
517         } else {
518             fprintf (stderr, "Error saving configuration to %s: %s\n",
519                      filename, error->message);
520         }
521         g_error_free (error);
522         free (filename);
523         g_free (data);
524         return 1;
525     }
526
527     free (filename);
528     g_free (data);
529     return 0;
530 }
531
532 notmuch_bool_t
533 notmuch_config_is_new (notmuch_config_t *config)
534 {
535     return config->is_new;
536 }
537
538 static const char *
539 _config_get (notmuch_config_t *config, char **field,
540              const char *group, const char *key)
541 {
542     /* read from config file and cache value, if not cached already */
543     if (*field == NULL) {
544         char *value;
545         value = g_key_file_get_string (config->key_file, group, key, NULL);
546         if (value) {
547             *field = talloc_strdup (config, value);
548             free (value);
549         }
550     }
551     return *field;
552 }
553
554 static void
555 _config_set (notmuch_config_t *config, char **field,
556              const char *group, const char *key, const char *value)
557 {
558     g_key_file_set_string (config->key_file, group, key, value);
559
560     /* drop the cached value */
561     talloc_free (*field);
562     *field = NULL;
563 }
564
565 static const char **
566 _config_get_list (notmuch_config_t *config,
567                   const char *section, const char *key,
568                   const char ***outlist, size_t *list_length, size_t *ret_length)
569 {
570     assert(outlist);
571
572     /* read from config file and cache value, if not cached already */
573     if (*outlist == NULL) {
574
575         char **inlist = g_key_file_get_string_list (config->key_file,
576                                              section, key, list_length, NULL);
577         if (inlist) {
578             unsigned int i;
579
580             *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
581
582             for (i = 0; i < *list_length; i++)
583                 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
584
585             (*outlist)[i] = NULL;
586
587             g_strfreev (inlist);
588         }
589     }
590
591     if (ret_length)
592         *ret_length = *list_length;
593
594     return *outlist;
595 }
596
597 static void
598 _config_set_list (notmuch_config_t *config,
599                   const char *group, const char *name,
600                   const char *list[],
601                   size_t length, const char ***config_var )
602 {
603     g_key_file_set_string_list (config->key_file, group, name, list, length);
604
605     /* drop the cached value */
606     talloc_free (*config_var);
607     *config_var = NULL;
608 }
609
610 const char *
611 notmuch_config_get_database_path (notmuch_config_t *config)
612 {
613     return _config_get (config, &config->database_path, "database", "path");
614 }
615
616 void
617 notmuch_config_set_database_path (notmuch_config_t *config,
618                                   const char *database_path)
619 {
620     _config_set (config, &config->database_path, "database", "path", database_path);
621 }
622
623 const char *
624 notmuch_config_get_user_name (notmuch_config_t *config)
625 {
626     return _config_get (config, &config->user_name, "user", "name");
627 }
628
629 void
630 notmuch_config_set_user_name (notmuch_config_t *config,
631                               const char *user_name)
632 {
633     _config_set (config, &config->user_name, "user", "name", user_name);
634 }
635
636 const char *
637 notmuch_config_get_user_primary_email (notmuch_config_t *config)
638 {
639     return _config_get (config, &config->user_primary_email, "user", "primary_email");
640 }
641
642 void
643 notmuch_config_set_user_primary_email (notmuch_config_t *config,
644                                        const char *primary_email)
645 {
646     _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
647 }
648
649 const char **
650 notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
651 {
652     return _config_get_list (config, "user", "other_email",
653                              &(config->user_other_email),
654                              &(config->user_other_email_length), length);
655 }
656
657 const char **
658 notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
659 {
660     return _config_get_list (config, "new", "tags",
661                              &(config->new_tags),
662                              &(config->new_tags_length), length);
663 }
664
665 const char **
666 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
667 {
668     return _config_get_list (config, "new", "ignore",
669                              &(config->new_ignore),
670                              &(config->new_ignore_length), length);
671 }
672
673 void
674 notmuch_config_set_user_other_email (notmuch_config_t *config,
675                                      const char *list[],
676                                      size_t length)
677 {
678     _config_set_list (config, "user", "other_email", list, length,
679                      &(config->user_other_email));
680 }
681
682 void
683 notmuch_config_set_new_tags (notmuch_config_t *config,
684                                      const char *list[],
685                                      size_t length)
686 {
687     _config_set_list (config, "new", "tags", list, length,
688                      &(config->new_tags));
689 }
690
691 void
692 notmuch_config_set_new_ignore (notmuch_config_t *config,
693                                const char *list[],
694                                size_t length)
695 {
696     _config_set_list (config, "new", "ignore", list, length,
697                      &(config->new_ignore));
698 }
699
700 const char **
701 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
702 {
703     return _config_get_list (config, "search", "exclude_tags",
704                              &(config->search_exclude_tags),
705                              &(config->search_exclude_tags_length), length);
706 }
707
708 void
709 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
710                                       const char *list[],
711                                       size_t length)
712 {
713     _config_set_list (config, "search", "exclude_tags", list, length,
714                       &(config->search_exclude_tags));
715 }
716
717 const char *
718 notmuch_config_get_crypto_gpg_path (notmuch_config_t *config)
719 {
720     return _config_get (config, &config->crypto_gpg_path, "crypto", "gpg_path");
721 }
722
723 void
724 notmuch_config_set_crypto_gpg_path (notmuch_config_t *config,
725                               const char *gpg_path)
726 {
727     _config_set (config, &config->crypto_gpg_path, "crypto", "gpg_path", gpg_path);
728 }
729
730
731 /* Given a configuration item of the form <group>.<key> return the
732  * component group and key. If any error occurs, print a message on
733  * stderr and return 1. Otherwise, return 0.
734  *
735  * Note: This function modifies the original 'item' string.
736  */
737 static int
738 _item_split (char *item, char **group, char **key)
739 {
740     char *period;
741
742     *group = item;
743
744     period = strchr (item, '.');
745     if (period == NULL || *(period+1) == '\0') {
746         fprintf (stderr,
747                  "Invalid configuration name: %s\n"
748                  "(Should be of the form <section>.<item>)\n", item);
749         return 1;
750     }
751
752     *period = '\0';
753     *key = period + 1;
754
755     return 0;
756 }
757
758 #define BUILT_WITH_PREFIX "built_with."
759 #define QUERY_PREFIX "query."
760
761 static int
762 _print_db_config(notmuch_config_t *config, const char *name)
763 {
764     notmuch_database_t *notmuch;
765     char *val;
766
767     if (notmuch_database_open (notmuch_config_get_database_path (config),
768                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
769         return EXIT_FAILURE;
770
771     /* XXX Handle UUID mismatch? */
772
773     if (print_status_database ("notmuch config", notmuch,
774                                notmuch_database_get_config (notmuch, name, &val)))
775         return EXIT_FAILURE;
776
777      puts (val);
778
779     return EXIT_SUCCESS;
780 }
781
782 static int
783 notmuch_config_command_get (notmuch_config_t *config, char *item)
784 {
785     if (strcmp(item, "database.path") == 0) {
786         printf ("%s\n", notmuch_config_get_database_path (config));
787     } else if (strcmp(item, "user.name") == 0) {
788         printf ("%s\n", notmuch_config_get_user_name (config));
789     } else if (strcmp(item, "user.primary_email") == 0) {
790         printf ("%s\n", notmuch_config_get_user_primary_email (config));
791     } else if (strcmp(item, "user.other_email") == 0) {
792         const char **other_email;
793         size_t i, length;
794         
795         other_email = notmuch_config_get_user_other_email (config, &length);
796         for (i = 0; i < length; i++)
797             printf ("%s\n", other_email[i]);
798     } else if (strcmp(item, "new.tags") == 0) {
799         const char **tags;
800         size_t i, length;
801
802         tags = notmuch_config_get_new_tags (config, &length);
803         for (i = 0; i < length; i++)
804             printf ("%s\n", tags[i]);
805     } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
806         printf ("%s\n",
807                 notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
808     } else if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) {
809         return _print_db_config (config, item);
810     } else {
811         char **value;
812         size_t i, length;
813         char *group, *key;
814
815         if (_item_split (item, &group, &key))
816             return 1;
817
818         value = g_key_file_get_string_list (config->key_file,
819                                             group, key,
820                                             &length, NULL);
821         if (value == NULL) {
822             fprintf (stderr, "Unknown configuration item: %s.%s\n",
823                      group, key);
824             return 1;
825         }
826
827         for (i = 0; i < length; i++)
828             printf ("%s\n", value[i]);
829
830         g_strfreev (value);
831     }
832
833     return 0;
834 }
835
836 static int
837 _set_db_config(notmuch_config_t *config, const char *key, int argc, char **argv)
838 {
839     notmuch_database_t *notmuch;
840     const char *val = "";
841
842     if (argc > 1) {
843         /* XXX handle lists? */
844         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
845         return EXIT_FAILURE;
846     }
847
848     if (argc > 0) {
849         val = argv[0];
850     }
851
852     if (notmuch_database_open (notmuch_config_get_database_path (config),
853                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
854         return EXIT_FAILURE;
855
856     /* XXX Handle UUID mismatch? */
857
858     if (print_status_database ("notmuch config", notmuch,
859                                notmuch_database_set_config (notmuch, key, val)))
860         return EXIT_FAILURE;
861
862     if (print_status_database ("notmuch config", notmuch,
863                                notmuch_database_close (notmuch)))
864         return EXIT_FAILURE;
865
866     return EXIT_SUCCESS;
867 }
868
869 static int
870 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
871 {
872     char *group, *key;
873
874     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
875         fprintf (stderr, "Error: read only option: %s\n", item);
876         return 1;
877     }
878
879     if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) {
880         return _set_db_config (config, item, argc, argv);
881     }
882
883     if (_item_split (item, &group, &key))
884         return 1;
885
886     /* With only the name of an item, we clear it from the
887      * configuration file.
888      *
889      * With a single value, we set it as a string.
890      *
891      * With multiple values, we set them as a string list.
892      */
893     switch (argc) {
894     case 0:
895         g_key_file_remove_key (config->key_file, group, key, NULL);
896         break;
897     case 1:
898         g_key_file_set_string (config->key_file, group, key, argv[0]);
899         break;
900     default:
901         g_key_file_set_string_list (config->key_file, group, key,
902                                     (const gchar **) argv, argc);
903         break;
904     }
905
906     return notmuch_config_save (config);
907 }
908
909 static
910 void
911 _notmuch_config_list_built_with ()
912 {
913     printf("%scompact=%s\n",
914            BUILT_WITH_PREFIX,
915            notmuch_built_with ("compact") ? "true" : "false");
916     printf("%sfield_processor=%s\n",
917            BUILT_WITH_PREFIX,
918            notmuch_built_with ("field_processor") ? "true" : "false");
919     printf("%sretry_lock=%s\n",
920            BUILT_WITH_PREFIX,
921            notmuch_built_with ("retry_lock") ? "true" : "false");
922 }
923
924 static int
925 _list_db_config (notmuch_config_t *config)
926 {
927     notmuch_database_t *notmuch;
928     notmuch_config_list_t *list;
929
930     if (notmuch_database_open (notmuch_config_get_database_path (config),
931                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
932         return EXIT_FAILURE;
933
934     /* XXX Handle UUID mismatch? */
935
936
937     if (print_status_database ("notmuch config", notmuch,
938                                notmuch_database_get_config_list (notmuch, "", &list)))
939         return EXIT_FAILURE;
940
941     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
942         printf("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
943     }
944     notmuch_config_list_destroy (list);
945
946    return EXIT_SUCCESS;
947 }
948
949 static int
950 notmuch_config_command_list (notmuch_config_t *config)
951 {
952     char **groups;
953     size_t g, groups_length;
954
955     groups = g_key_file_get_groups (config->key_file, &groups_length);
956     if (groups == NULL)
957         return 1;
958
959     for (g = 0; g < groups_length; g++) {
960         char **keys;
961         size_t k, keys_length;
962
963         keys = g_key_file_get_keys (config->key_file,
964                                     groups[g], &keys_length, NULL);
965         if (keys == NULL)
966             continue;
967
968         for (k = 0; k < keys_length; k++) {
969             char *value;
970
971             value = g_key_file_get_string (config->key_file,
972                                            groups[g], keys[k], NULL);
973             if (value != NULL) {
974                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
975                 free (value);
976             }
977         }
978
979         g_strfreev (keys);
980     }
981
982     g_strfreev (groups);
983
984     _notmuch_config_list_built_with ();
985     return _list_db_config (config);
986 }
987
988 int
989 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
990 {
991     int ret;
992     int opt_index;
993
994     opt_index = notmuch_minimal_options ("config", argc, argv);
995     if (opt_index < 0)
996         return EXIT_FAILURE;
997
998     if (notmuch_requested_db_uuid)
999         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
1000                  notmuch_requested_db_uuid);
1001
1002     /* skip at least subcommand argument */
1003     argc-= opt_index;
1004     argv+= opt_index;
1005
1006     if (argc < 1) {
1007         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
1008         return EXIT_FAILURE;
1009     }
1010
1011     if (strcmp (argv[0], "get") == 0) {
1012         if (argc != 2) {
1013             fprintf (stderr, "Error: notmuch config get requires exactly "
1014                      "one argument.\n");
1015             return EXIT_FAILURE;
1016         }
1017         ret = notmuch_config_command_get (config, argv[1]);
1018     } else if (strcmp (argv[0], "set") == 0) {
1019         if (argc < 2) {
1020             fprintf (stderr, "Error: notmuch config set requires at least "
1021                      "one argument.\n");
1022             return EXIT_FAILURE;
1023         }
1024         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
1025     } else if (strcmp (argv[0], "list") == 0) {
1026         ret = notmuch_config_command_list (config);
1027     } else {
1028         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
1029                  argv[0]);
1030         return EXIT_FAILURE;
1031     }
1032
1033     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
1034
1035 }
1036
1037 notmuch_bool_t
1038 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
1039 {
1040     return config->maildir_synchronize_flags;
1041 }
1042
1043 void
1044 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
1045                                               notmuch_bool_t synchronize_flags)
1046 {
1047     g_key_file_set_boolean (config->key_file,
1048                             "maildir", "synchronize_flags", synchronize_flags);
1049     config->maildir_synchronize_flags = synchronize_flags;
1050 }