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