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