e733e92976ade780c9694d2dc088a84f5b464976
[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 http://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 http://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 struct _notmuch_config {
105     char *filename;
106     GKeyFile *key_file;
107     notmuch_bool_t is_new;
108
109     char *database_path;
110     char *user_name;
111     char *user_primary_email;
112     const char **user_other_email;
113     size_t user_other_email_length;
114     const char **new_tags;
115     size_t new_tags_length;
116     const char **new_ignore;
117     size_t new_ignore_length;
118     notmuch_bool_t maildir_synchronize_flags;
119     const char **search_exclude_tags;
120     size_t search_exclude_tags_length;
121 };
122
123 static int
124 notmuch_config_destructor (notmuch_config_t *config)
125 {
126     if (config->key_file)
127         g_key_file_free (config->key_file);
128
129     return 0;
130 }
131
132 static char *
133 get_name_from_passwd_file (void *ctx)
134 {
135     long pw_buf_size;
136     char *pw_buf;
137     struct passwd passwd, *ignored;
138     char *name;
139     int e;
140
141     pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
142     if (pw_buf_size == -1) pw_buf_size = 64;
143     pw_buf = talloc_size (ctx, pw_buf_size);
144
145     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
146                             pw_buf_size, &ignored)) == ERANGE) {
147         pw_buf_size = pw_buf_size * 2;
148         pw_buf = talloc_zero_size(ctx, pw_buf_size);
149     }
150
151     if (e == 0) {
152         char *comma = strchr (passwd.pw_gecos, ',');
153         if (comma)
154             name = talloc_strndup (ctx, passwd.pw_gecos,
155                                    comma - passwd.pw_gecos);
156         else
157             name = talloc_strdup (ctx, passwd.pw_gecos);
158     } else {
159         name = talloc_strdup (ctx, "");
160     }
161
162     talloc_free (pw_buf);
163
164     return name;
165 }
166
167 static char *
168 get_username_from_passwd_file (void *ctx)
169 {
170     long pw_buf_size;
171     char *pw_buf;
172     struct passwd passwd, *ignored;
173     char *name;
174     int e;
175
176     pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
177     if (pw_buf_size == -1) pw_buf_size = 64;
178     pw_buf = talloc_zero_size (ctx, pw_buf_size);
179
180     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
181                             pw_buf_size, &ignored)) == ERANGE) {
182         pw_buf_size = pw_buf_size * 2;
183         pw_buf = talloc_zero_size(ctx, pw_buf_size);
184     }
185
186     if (e == 0)
187         name = talloc_strdup (ctx, passwd.pw_name);
188     else
189         name = talloc_strdup (ctx, "");
190
191     talloc_free (pw_buf);
192
193     return name;
194 }
195
196 /* Open the named notmuch configuration file. If the filename is NULL,
197  * the value of the environment variable $NOTMUCH_CONFIG will be used.
198  * If $NOTMUCH_CONFIG is unset, the default configuration file
199  * ($HOME/.notmuch-config) will be used.
200  *
201  * If any error occurs, (out of memory, or a permission-denied error,
202  * etc.), this function will print a message to stderr and return
203  * NULL.
204  *
205  * FILE NOT FOUND: When the specified configuration file (whether from
206  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
207  * exist, the behavior of this function depends on the 'is_new_ret'
208  * variable.
209  *
210  *      If is_new_ret is NULL, then a "file not found" message will be
211  *      printed to stderr and NULL will be returned.
212
213  *      If is_new_ret is non-NULL then a default configuration will be
214  *      returned and *is_new_ret will be set to 1 on return so that
215  *      the caller can recognize this case.
216  *
217  *      These default configuration settings are determined as
218  *      follows:
219  *
220  *              database_path:          $HOME/mail
221  *
222  *              user_name:              From /etc/passwd
223  *
224  *              user_primary_mail:      $EMAIL variable if set, otherwise
225  *                                      constructed from the username and
226  *                                      hostname of the current machine.
227  *
228  *              user_other_email:       Not set.
229  *
230  *      The default configuration also contains comments to guide the
231  *      user in editing the file directly.
232  */
233 notmuch_config_t *
234 notmuch_config_open (void *ctx,
235                      const char *filename,
236                      notmuch_bool_t *is_new_ret)
237 {
238     GError *error = NULL;
239     int is_new = 0;
240     size_t tmp;
241     char *notmuch_config_env = NULL;
242     int file_had_database_group;
243     int file_had_new_group;
244     int file_had_user_group;
245     int file_had_maildir_group;
246     int file_had_search_group;
247
248     if (is_new_ret)
249         *is_new_ret = 0;
250
251     notmuch_config_t *config = talloc (ctx, notmuch_config_t);
252     if (config == NULL) {
253         fprintf (stderr, "Out of memory.\n");
254         return NULL;
255     }
256     
257     talloc_set_destructor (config, notmuch_config_destructor);
258
259     if (filename) {
260         config->filename = talloc_strdup (config, filename);
261     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
262         config->filename = talloc_strdup (config, notmuch_config_env);
263     } else {
264         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
265                                             getenv ("HOME"));
266     }
267
268     config->key_file = g_key_file_new ();
269
270     config->is_new = FALSE;
271     config->database_path = NULL;
272     config->user_name = NULL;
273     config->user_primary_email = NULL;
274     config->user_other_email = NULL;
275     config->user_other_email_length = 0;
276     config->new_tags = NULL;
277     config->new_tags_length = 0;
278     config->new_ignore = NULL;
279     config->new_ignore_length = 0;
280     config->maildir_synchronize_flags = TRUE;
281     config->search_exclude_tags = NULL;
282     config->search_exclude_tags_length = 0;
283
284     if (! g_key_file_load_from_file (config->key_file,
285                                      config->filename,
286                                      G_KEY_FILE_KEEP_COMMENTS,
287                                      &error))
288     {
289         /* If the caller passed a non-NULL value for is_new_ret, then
290          * the caller is prepared for a default configuration file in
291          * the case of FILE NOT FOUND. Otherwise, any read failure is
292          * an error.
293          */
294         if (is_new_ret &&
295             error->domain == G_FILE_ERROR &&
296             error->code == G_FILE_ERROR_NOENT)
297         {
298             g_error_free (error);
299             is_new = 1;
300         }
301         else
302         {
303             fprintf (stderr, "Error reading configuration file %s: %s\n",
304                      config->filename, error->message);
305             talloc_free (config);
306             g_error_free (error);
307             return NULL;
308         }
309     }
310
311     /* Whenever we know of configuration sections that don't appear in
312      * the configuration file, we add some comments to help the user
313      * understand what can be done.
314      *
315      * It would be convenient to just add those comments now, but
316      * apparently g_key_file will clear any comments when keys are
317      * added later that create the groups. So we have to check for the
318      * groups now, but add the comments only after setting all of our
319      * values.
320      */
321     file_had_database_group = g_key_file_has_group (config->key_file,
322                                                     "database");
323     file_had_new_group = g_key_file_has_group (config->key_file, "new");
324     file_had_user_group = g_key_file_has_group (config->key_file, "user");
325     file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
326     file_had_search_group = g_key_file_has_group (config->key_file, "search");
327
328
329     if (notmuch_config_get_database_path (config) == NULL) {
330         char *path = talloc_asprintf (config, "%s/mail",
331                                       getenv ("HOME"));
332         notmuch_config_set_database_path (config, path);
333         talloc_free (path);
334     }
335
336     if (notmuch_config_get_user_name (config) == NULL) {
337         char *name = get_name_from_passwd_file (config);
338         notmuch_config_set_user_name (config, name);
339         talloc_free (name);
340     }
341
342     if (notmuch_config_get_user_primary_email (config) == NULL) {
343         char *email = getenv ("EMAIL");
344         if (email) {
345             notmuch_config_set_user_primary_email (config, email);
346         } else {
347             char hostname[256];
348             struct hostent *hostent;
349             const char *domainname;
350
351             char *username = get_username_from_passwd_file (config);
352
353             gethostname (hostname, 256);
354             hostname[255] = '\0';
355
356             hostent = gethostbyname (hostname);
357             if (hostent && (domainname = strchr (hostent->h_name, '.')))
358                 domainname += 1;
359             else
360                 domainname = "(none)";
361
362             email = talloc_asprintf (config, "%s@%s.%s",
363                                      username, hostname, domainname);
364
365             notmuch_config_set_user_primary_email (config, email);
366
367             talloc_free (username);
368             talloc_free (email);
369         }
370     }
371
372     if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
373         const char *tags[] = { "unread", "inbox" };
374         notmuch_config_set_new_tags (config, tags, 2);
375     }
376
377     if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
378         notmuch_config_set_new_ignore (config, NULL, 0);
379     }
380
381     if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
382         if (is_new) {
383             const char *tags[] = { "deleted", "spam" };
384             notmuch_config_set_search_exclude_tags (config, tags, 2);
385         } else {
386             notmuch_config_set_search_exclude_tags (config, NULL, 0);
387         }
388     }
389
390     error = NULL;
391     config->maildir_synchronize_flags =
392         g_key_file_get_boolean (config->key_file,
393                                 "maildir", "synchronize_flags", &error);
394     if (error) {
395         notmuch_config_set_maildir_synchronize_flags (config, TRUE);
396         g_error_free (error);
397     }
398
399     /* Whenever we know of configuration sections that don't appear in
400      * the configuration file, we add some comments to help the user
401      * understand what can be done. */
402     if (is_new)
403     {
404         g_key_file_set_comment (config->key_file, NULL, NULL,
405                                 toplevel_config_comment, NULL);
406     }
407
408     if (! file_had_database_group)
409     {
410         g_key_file_set_comment (config->key_file, "database", NULL,
411                                 database_config_comment, NULL);
412     }
413
414     if (! file_had_new_group)
415     {
416         g_key_file_set_comment (config->key_file, "new", NULL,
417                                 new_config_comment, NULL);
418     }
419
420     if (! file_had_user_group)
421     {
422         g_key_file_set_comment (config->key_file, "user", NULL,
423                                 user_config_comment, NULL);
424     }
425
426     if (! file_had_maildir_group)
427     {
428         g_key_file_set_comment (config->key_file, "maildir", NULL,
429                                 maildir_config_comment, NULL);
430     }
431
432     if (! file_had_search_group) {
433         g_key_file_set_comment (config->key_file, "search", NULL,
434                                 search_config_comment, NULL);
435     }
436
437     if (is_new_ret)
438         *is_new_ret = is_new;
439
440     config->is_new = is_new;
441
442     return config;
443 }
444
445 /* Close the given notmuch_config_t object, freeing all resources.
446  * 
447  * Note: Any changes made to the configuration are *not* saved by this
448  * function. To save changes, call notmuch_config_save before
449  * notmuch_config_close.
450 */
451 void
452 notmuch_config_close (notmuch_config_t *config)
453 {
454     talloc_free (config);
455 }
456
457 /* Save any changes made to the notmuch configuration.
458  *
459  * Any comments originally in the file will be preserved.
460  *
461  * Returns 0 if successful, and 1 in case of any error, (after
462  * printing a description of the error to stderr).
463  */
464 int
465 notmuch_config_save (notmuch_config_t *config)
466 {
467     size_t length;
468     char *data;
469     GError *error = NULL;
470
471     data = g_key_file_to_data (config->key_file, &length, NULL);
472     if (data == NULL) {
473         fprintf (stderr, "Out of memory.\n");
474         return 1;
475     }
476
477     if (! g_file_set_contents (config->filename, data, length, &error)) {
478         fprintf (stderr, "Error saving configuration to %s: %s\n",
479                  config->filename, error->message);
480         g_error_free (error);
481         g_free (data);
482         return 1;
483     }
484
485     g_free (data);
486     return 0;
487 }
488
489 notmuch_bool_t
490 notmuch_config_is_new (notmuch_config_t *config)
491 {
492     return config->is_new;
493 }
494
495
496 static const char **
497 _config_get_list (notmuch_config_t *config,
498                   const char *section, const char *key,
499                   const char ***outlist, size_t *list_length, size_t *ret_length)
500 {
501     assert(outlist);
502
503     if (*outlist == NULL) {
504
505         char **inlist = g_key_file_get_string_list (config->key_file,
506                                              section, key, list_length, NULL);
507         if (inlist) {
508             unsigned int i;
509
510             *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
511
512             for (i = 0; i < *list_length; i++)
513                 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
514
515             (*outlist)[i] = NULL;
516
517             g_strfreev (inlist);
518         }
519     }
520
521     if (ret_length)
522         *ret_length = *list_length;
523
524     return *outlist;
525 }
526
527 static void
528 _config_set_list (notmuch_config_t *config,
529                   const char *group, const char *name,
530                   const char *list[],
531                   size_t length, const char ***config_var )
532 {
533     g_key_file_set_string_list (config->key_file, group, name, list, length);
534     talloc_free (*config_var);
535     *config_var = NULL;
536 }
537
538 const char *
539 notmuch_config_get_database_path (notmuch_config_t *config)
540 {
541     char *path;
542
543     if (config->database_path == NULL) {
544         path = g_key_file_get_string (config->key_file,
545                                       "database", "path", NULL);
546         if (path) {
547             config->database_path = talloc_strdup (config, path);
548             free (path);
549         }
550     }
551
552     return config->database_path;
553 }
554
555 void
556 notmuch_config_set_database_path (notmuch_config_t *config,
557                                   const char *database_path)
558 {
559     g_key_file_set_string (config->key_file,
560                            "database", "path", database_path);
561
562     talloc_free (config->database_path);
563     config->database_path = NULL;
564 }
565
566 const char *
567 notmuch_config_get_user_name (notmuch_config_t *config)
568 {
569     char *name;
570
571     if (config->user_name == NULL) {
572         name = g_key_file_get_string (config->key_file,
573                                       "user", "name", NULL);
574         if (name) {
575             config->user_name = talloc_strdup (config, name);
576             free (name);
577         }
578     }
579
580     return config->user_name;
581 }
582
583 void
584 notmuch_config_set_user_name (notmuch_config_t *config,
585                               const char *user_name)
586 {
587     g_key_file_set_string (config->key_file,
588                            "user", "name", user_name);
589
590     talloc_free (config->user_name);
591     config->user_name = NULL;
592 }
593
594 const char *
595 notmuch_config_get_user_primary_email (notmuch_config_t *config)
596 {
597     char *email;
598
599     if (config->user_primary_email == NULL) {
600         email = g_key_file_get_string (config->key_file,
601                                        "user", "primary_email", NULL);
602         if (email) {
603             config->user_primary_email = talloc_strdup (config, email);
604             free (email);
605         }
606     }
607
608     return config->user_primary_email;
609 }
610
611 void
612 notmuch_config_set_user_primary_email (notmuch_config_t *config,
613                                        const char *primary_email)
614 {
615     g_key_file_set_string (config->key_file,
616                            "user", "primary_email", primary_email);
617
618     talloc_free (config->user_primary_email);
619     config->user_primary_email = NULL;
620 }
621
622 const char **
623 notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
624 {
625     return _config_get_list (config, "user", "other_email",
626                              &(config->user_other_email),
627                              &(config->user_other_email_length), length);
628 }
629
630 const char **
631 notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
632 {
633     return _config_get_list (config, "new", "tags",
634                              &(config->new_tags),
635                              &(config->new_tags_length), length);
636 }
637
638 const char **
639 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
640 {
641     return _config_get_list (config, "new", "ignore",
642                              &(config->new_ignore),
643                              &(config->new_ignore_length), length);
644 }
645
646 void
647 notmuch_config_set_user_other_email (notmuch_config_t *config,
648                                      const char *list[],
649                                      size_t length)
650 {
651     _config_set_list (config, "user", "other_email", list, length,
652                      &(config->user_other_email));
653 }
654
655 void
656 notmuch_config_set_new_tags (notmuch_config_t *config,
657                                      const char *list[],
658                                      size_t length)
659 {
660     _config_set_list (config, "new", "tags", list, length,
661                      &(config->new_tags));
662 }
663
664 void
665 notmuch_config_set_new_ignore (notmuch_config_t *config,
666                                const char *list[],
667                                size_t length)
668 {
669     _config_set_list (config, "new", "ignore", list, length,
670                      &(config->new_ignore));
671 }
672
673 const char **
674 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
675 {
676     return _config_get_list (config, "search", "exclude_tags",
677                              &(config->search_exclude_tags),
678                              &(config->search_exclude_tags_length), length);
679 }
680
681 void
682 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
683                                       const char *list[],
684                                       size_t length)
685 {
686     _config_set_list (config, "search", "exclude_tags", list, length,
687                       &(config->search_exclude_tags));
688 }
689
690 /* Given a configuration item of the form <group>.<key> return the
691  * component group and key. If any error occurs, print a message on
692  * stderr and return 1. Otherwise, return 0.
693  *
694  * Note: This function modifies the original 'item' string.
695  */
696 static int
697 _item_split (char *item, char **group, char **key)
698 {
699     char *period;
700
701     *group = item;
702
703     period = index (item, '.');
704     if (period == NULL || *(period+1) == '\0') {
705         fprintf (stderr,
706                  "Invalid configuration name: %s\n"
707                  "(Should be of the form <section>.<item>)\n", item);
708         return 1;
709     }
710
711     *period = '\0';
712     *key = period + 1;
713
714     return 0;
715 }
716
717 static int
718 notmuch_config_command_get (void *ctx, char *item)
719 {
720     notmuch_config_t *config;
721
722     config = notmuch_config_open (ctx, NULL, NULL);
723     if (config == NULL)
724         return 1;
725
726     if (strcmp(item, "database.path") == 0) {
727         printf ("%s\n", notmuch_config_get_database_path (config));
728     } else if (strcmp(item, "user.name") == 0) {
729         printf ("%s\n", notmuch_config_get_user_name (config));
730     } else if (strcmp(item, "user.primary_email") == 0) {
731         printf ("%s\n", notmuch_config_get_user_primary_email (config));
732     } else if (strcmp(item, "user.other_email") == 0) {
733         const char **other_email;
734         size_t i, length;
735         
736         other_email = notmuch_config_get_user_other_email (config, &length);
737         for (i = 0; i < length; i++)
738             printf ("%s\n", other_email[i]);
739     } else if (strcmp(item, "new.tags") == 0) {
740         const char **tags;
741         size_t i, length;
742
743         tags = notmuch_config_get_new_tags (config, &length);
744         for (i = 0; i < length; i++)
745             printf ("%s\n", tags[i]);
746     } else {
747         char **value;
748         size_t i, length;
749         char *group, *key;
750
751         if (_item_split (item, &group, &key))
752             return 1;
753
754         value = g_key_file_get_string_list (config->key_file,
755                                             group, key,
756                                             &length, NULL);
757         if (value == NULL) {
758             fprintf (stderr, "Unknown configuration item: %s.%s\n",
759                      group, key);
760             return 1;
761         }
762
763         for (i = 0; i < length; i++)
764             printf ("%s\n", value[i]);
765
766         g_strfreev (value);
767     }
768
769     notmuch_config_close (config);
770
771     return 0;
772 }
773
774 static int
775 notmuch_config_command_set (void *ctx, char *item, int argc, char *argv[])
776 {
777     notmuch_config_t *config;
778     char *group, *key;
779     int ret;
780
781     if (_item_split (item, &group, &key))
782         return 1;
783
784     config = notmuch_config_open (ctx, NULL, NULL);
785     if (config == NULL)
786         return 1;
787
788     /* With only the name of an item, we clear it from the
789      * configuration file.
790      *
791      * With a single value, we set it as a string.
792      *
793      * With multiple values, we set them as a string list.
794      */
795     switch (argc) {
796     case 0:
797         g_key_file_remove_key (config->key_file, group, key, NULL);
798         break;
799     case 1:
800         g_key_file_set_string (config->key_file, group, key, argv[0]);
801         break;
802     default:
803         g_key_file_set_string_list (config->key_file, group, key,
804                                     (const gchar **) argv, argc);
805         break;
806     }
807
808     ret = notmuch_config_save (config);
809     notmuch_config_close (config);
810
811     return ret;
812 }
813
814 static int
815 notmuch_config_command_list (void *ctx)
816 {
817     notmuch_config_t *config;
818     char **groups;
819     size_t g, groups_length;
820
821     config = notmuch_config_open (ctx, NULL, NULL);
822     if (config == NULL)
823         return 1;
824
825     groups = g_key_file_get_groups (config->key_file, &groups_length);
826     if (groups == NULL)
827         return 1;
828
829     for (g = 0; g < groups_length; g++) {
830         char **keys;
831         size_t k, keys_length;
832
833         keys = g_key_file_get_keys (config->key_file,
834                                     groups[g], &keys_length, NULL);
835         if (keys == NULL)
836             continue;
837
838         for (k = 0; k < keys_length; k++) {
839             char *value;
840
841             value = g_key_file_get_string (config->key_file,
842                                            groups[g], keys[k], NULL);
843             if (value != NULL) {
844                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
845                 free (value);
846             }
847         }
848
849         g_strfreev (keys);
850     }
851
852     g_strfreev (groups);
853
854     notmuch_config_close (config);
855
856     return 0;
857 }
858
859 int
860 notmuch_config_command (void *ctx, int argc, char *argv[])
861 {
862     argc--; argv++; /* skip subcommand argument */
863
864     if (argc < 1) {
865         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
866         return 1;
867     }
868
869     if (strcmp (argv[0], "get") == 0) {
870         if (argc != 2) {
871             fprintf (stderr, "Error: notmuch config get requires exactly "
872                      "one argument.\n");
873             return 1;
874         }
875         return notmuch_config_command_get (ctx, argv[1]);
876     } else if (strcmp (argv[0], "set") == 0) {
877         if (argc < 2) {
878             fprintf (stderr, "Error: notmuch config set requires at least "
879                      "one argument.\n");
880             return 1;
881         }
882         return notmuch_config_command_set (ctx, argv[1], argc - 2, argv + 2);
883     } else if (strcmp (argv[0], "list") == 0) {
884         return notmuch_config_command_list (ctx);
885     }
886
887     fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
888              argv[0]);
889     return 1;
890 }
891
892 notmuch_bool_t
893 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
894 {
895     return config->maildir_synchronize_flags;
896 }
897
898 void
899 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
900                                               notmuch_bool_t synchronize_flags)
901 {
902     g_key_file_set_boolean (config->key_file,
903                             "maildir", "synchronize_flags", synchronize_flags);
904     config->maildir_synchronize_flags = synchronize_flags;
905 }