]> git.notmuchmail.org Git - notmuch/blob - notmuch-config.c
lib: Fix endless upgrade problem
[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:          $MAILDIR, otherwise $HOME/mail
221  *
222  *              user_name:              $NAME variable if set, otherwise
223  *                                      read from /etc/passwd
224  *
225  *              user_primary_mail:      $EMAIL variable if set, otherwise
226  *                                      constructed from the username and
227  *                                      hostname of the current machine.
228  *
229  *              user_other_email:       Not set.
230  *
231  *      The default configuration also contains comments to guide the
232  *      user in editing the file directly.
233  */
234 notmuch_config_t *
235 notmuch_config_open (void *ctx,
236                      const char *filename,
237                      notmuch_bool_t create_new)
238 {
239     GError *error = NULL;
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     notmuch_config_t *config = talloc (ctx, notmuch_config_t);
249     if (config == NULL) {
250         fprintf (stderr, "Out of memory.\n");
251         return NULL;
252     }
253     
254     talloc_set_destructor (config, notmuch_config_destructor);
255
256     if (filename) {
257         config->filename = talloc_strdup (config, filename);
258     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
259         config->filename = talloc_strdup (config, notmuch_config_env);
260     } else {
261         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
262                                             getenv ("HOME"));
263     }
264
265     config->key_file = g_key_file_new ();
266
267     config->is_new = FALSE;
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 create_new is true, then the caller is prepared for a
287          * default configuration file in the case of FILE NOT
288          * FOUND. Otherwise, any read failure is an error.
289          */
290         if (create_new &&
291             error->domain == G_FILE_ERROR &&
292             error->code == G_FILE_ERROR_NOENT)
293         {
294             g_error_free (error);
295             config->is_new = TRUE;
296         }
297         else
298         {
299             fprintf (stderr, "Error reading configuration file %s: %s\n",
300                      config->filename, error->message);
301             talloc_free (config);
302             g_error_free (error);
303             return NULL;
304         }
305     }
306
307     /* Whenever we know of configuration sections that don't appear in
308      * the configuration file, we add some comments to help the user
309      * understand what can be done.
310      *
311      * It would be convenient to just add those comments now, but
312      * apparently g_key_file will clear any comments when keys are
313      * added later that create the groups. So we have to check for the
314      * groups now, but add the comments only after setting all of our
315      * values.
316      */
317     file_had_database_group = g_key_file_has_group (config->key_file,
318                                                     "database");
319     file_had_new_group = g_key_file_has_group (config->key_file, "new");
320     file_had_user_group = g_key_file_has_group (config->key_file, "user");
321     file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
322     file_had_search_group = g_key_file_has_group (config->key_file, "search");
323
324
325     if (notmuch_config_get_database_path (config) == NULL) {
326         char *path = getenv ("MAILDIR");
327         if (path)
328             path = talloc_strdup (config, path);
329         else
330             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 = getenv ("NAME");
338         if (name)
339             name = talloc_strdup (config, name);
340         else
341             name = get_name_from_passwd_file (config);
342         notmuch_config_set_user_name (config, name);
343         talloc_free (name);
344     }
345
346     if (notmuch_config_get_user_primary_email (config) == NULL) {
347         char *email = getenv ("EMAIL");
348         if (email) {
349             notmuch_config_set_user_primary_email (config, email);
350         } else {
351             char hostname[256];
352             struct hostent *hostent;
353             const char *domainname;
354
355             char *username = get_username_from_passwd_file (config);
356
357             gethostname (hostname, 256);
358             hostname[255] = '\0';
359
360             hostent = gethostbyname (hostname);
361             if (hostent && (domainname = strchr (hostent->h_name, '.')))
362                 domainname += 1;
363             else
364                 domainname = "(none)";
365
366             email = talloc_asprintf (config, "%s@%s.%s",
367                                      username, hostname, domainname);
368
369             notmuch_config_set_user_primary_email (config, email);
370
371             talloc_free (username);
372             talloc_free (email);
373         }
374     }
375
376     if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
377         const char *tags[] = { "unread", "inbox" };
378         notmuch_config_set_new_tags (config, tags, 2);
379     }
380
381     if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
382         notmuch_config_set_new_ignore (config, NULL, 0);
383     }
384
385     if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
386         if (config->is_new) {
387             const char *tags[] = { "deleted", "spam" };
388             notmuch_config_set_search_exclude_tags (config, tags, 2);
389         } else {
390             notmuch_config_set_search_exclude_tags (config, NULL, 0);
391         }
392     }
393
394     error = NULL;
395     config->maildir_synchronize_flags =
396         g_key_file_get_boolean (config->key_file,
397                                 "maildir", "synchronize_flags", &error);
398     if (error) {
399         notmuch_config_set_maildir_synchronize_flags (config, TRUE);
400         g_error_free (error);
401     }
402
403     /* Whenever we know of configuration sections that don't appear in
404      * the configuration file, we add some comments to help the user
405      * understand what can be done. */
406     if (config->is_new)
407         g_key_file_set_comment (config->key_file, NULL, NULL,
408                                 toplevel_config_comment, NULL);
409
410     if (! file_had_database_group)
411         g_key_file_set_comment (config->key_file, "database", NULL,
412                                 database_config_comment, NULL);
413
414     if (! file_had_new_group)
415         g_key_file_set_comment (config->key_file, "new", NULL,
416                                 new_config_comment, NULL);
417
418     if (! file_had_user_group)
419         g_key_file_set_comment (config->key_file, "user", NULL,
420                                 user_config_comment, NULL);
421
422     if (! file_had_maildir_group)
423         g_key_file_set_comment (config->key_file, "maildir", NULL,
424                                 maildir_config_comment, NULL);
425
426     if (! file_had_search_group)
427         g_key_file_set_comment (config->key_file, "search", NULL,
428                                 search_config_comment, NULL);
429
430     return config;
431 }
432
433 /* Close the given notmuch_config_t object, freeing all resources.
434  * 
435  * Note: Any changes made to the configuration are *not* saved by this
436  * function. To save changes, call notmuch_config_save before
437  * notmuch_config_close.
438 */
439 void
440 notmuch_config_close (notmuch_config_t *config)
441 {
442     talloc_free (config);
443 }
444
445 /* Save any changes made to the notmuch configuration.
446  *
447  * Any comments originally in the file will be preserved.
448  *
449  * Returns 0 if successful, and 1 in case of any error, (after
450  * printing a description of the error to stderr).
451  */
452 int
453 notmuch_config_save (notmuch_config_t *config)
454 {
455     size_t length;
456     char *data, *filename;
457     GError *error = NULL;
458
459     data = g_key_file_to_data (config->key_file, &length, NULL);
460     if (data == NULL) {
461         fprintf (stderr, "Out of memory.\n");
462         return 1;
463     }
464
465     /* Try not to overwrite symlinks. */
466     filename = canonicalize_file_name (config->filename);
467     if (! filename) {
468         if (errno == ENOENT) {
469             filename = strdup (config->filename);
470             if (! filename) {
471                 fprintf (stderr, "Out of memory.\n");
472                 g_free (data);
473                 return 1;
474             }
475         } else {
476             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
477                      strerror (errno));
478             g_free (data);
479             return 1;
480         }
481     }
482
483     if (! g_file_set_contents (filename, data, length, &error)) {
484         if (strcmp (filename, config->filename) != 0) {
485             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
486                      config->filename, filename, error->message);
487         } else {
488             fprintf (stderr, "Error saving configuration to %s: %s\n",
489                      filename, error->message);
490         }
491         g_error_free (error);
492         free (filename);
493         g_free (data);
494         return 1;
495     }
496
497     free (filename);
498     g_free (data);
499     return 0;
500 }
501
502 notmuch_bool_t
503 notmuch_config_is_new (notmuch_config_t *config)
504 {
505     return config->is_new;
506 }
507
508 static const char *
509 _config_get (notmuch_config_t *config, char **field,
510              const char *group, const char *key)
511 {
512     /* read from config file and cache value, if not cached already */
513     if (*field == NULL) {
514         char *value;
515         value = g_key_file_get_string (config->key_file, group, key, NULL);
516         if (value) {
517             *field = talloc_strdup (config, value);
518             free (value);
519         }
520     }
521     return *field;
522 }
523
524 static void
525 _config_set (notmuch_config_t *config, char **field,
526              const char *group, const char *key, const char *value)
527 {
528     g_key_file_set_string (config->key_file, group, key, value);
529
530     /* drop the cached value */
531     talloc_free (*field);
532     *field = NULL;
533 }
534
535 static const char **
536 _config_get_list (notmuch_config_t *config,
537                   const char *section, const char *key,
538                   const char ***outlist, size_t *list_length, size_t *ret_length)
539 {
540     assert(outlist);
541
542     /* read from config file and cache value, if not cached already */
543     if (*outlist == NULL) {
544
545         char **inlist = g_key_file_get_string_list (config->key_file,
546                                              section, key, list_length, NULL);
547         if (inlist) {
548             unsigned int i;
549
550             *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
551
552             for (i = 0; i < *list_length; i++)
553                 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
554
555             (*outlist)[i] = NULL;
556
557             g_strfreev (inlist);
558         }
559     }
560
561     if (ret_length)
562         *ret_length = *list_length;
563
564     return *outlist;
565 }
566
567 static void
568 _config_set_list (notmuch_config_t *config,
569                   const char *group, const char *name,
570                   const char *list[],
571                   size_t length, const char ***config_var )
572 {
573     g_key_file_set_string_list (config->key_file, group, name, list, length);
574
575     /* drop the cached value */
576     talloc_free (*config_var);
577     *config_var = NULL;
578 }
579
580 const char *
581 notmuch_config_get_database_path (notmuch_config_t *config)
582 {
583     return _config_get (config, &config->database_path, "database", "path");
584 }
585
586 void
587 notmuch_config_set_database_path (notmuch_config_t *config,
588                                   const char *database_path)
589 {
590     _config_set (config, &config->database_path, "database", "path", database_path);
591 }
592
593 const char *
594 notmuch_config_get_user_name (notmuch_config_t *config)
595 {
596     return _config_get (config, &config->user_name, "user", "name");
597 }
598
599 void
600 notmuch_config_set_user_name (notmuch_config_t *config,
601                               const char *user_name)
602 {
603     _config_set (config, &config->user_name, "user", "name", user_name);
604 }
605
606 const char *
607 notmuch_config_get_user_primary_email (notmuch_config_t *config)
608 {
609     return _config_get (config, &config->user_primary_email, "user", "primary_email");
610 }
611
612 void
613 notmuch_config_set_user_primary_email (notmuch_config_t *config,
614                                        const char *primary_email)
615 {
616     _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
617 }
618
619 const char **
620 notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
621 {
622     return _config_get_list (config, "user", "other_email",
623                              &(config->user_other_email),
624                              &(config->user_other_email_length), length);
625 }
626
627 const char **
628 notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
629 {
630     return _config_get_list (config, "new", "tags",
631                              &(config->new_tags),
632                              &(config->new_tags_length), length);
633 }
634
635 const char **
636 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
637 {
638     return _config_get_list (config, "new", "ignore",
639                              &(config->new_ignore),
640                              &(config->new_ignore_length), length);
641 }
642
643 void
644 notmuch_config_set_user_other_email (notmuch_config_t *config,
645                                      const char *list[],
646                                      size_t length)
647 {
648     _config_set_list (config, "user", "other_email", list, length,
649                      &(config->user_other_email));
650 }
651
652 void
653 notmuch_config_set_new_tags (notmuch_config_t *config,
654                                      const char *list[],
655                                      size_t length)
656 {
657     _config_set_list (config, "new", "tags", list, length,
658                      &(config->new_tags));
659 }
660
661 void
662 notmuch_config_set_new_ignore (notmuch_config_t *config,
663                                const char *list[],
664                                size_t length)
665 {
666     _config_set_list (config, "new", "ignore", list, length,
667                      &(config->new_ignore));
668 }
669
670 const char **
671 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
672 {
673     return _config_get_list (config, "search", "exclude_tags",
674                              &(config->search_exclude_tags),
675                              &(config->search_exclude_tags_length), length);
676 }
677
678 void
679 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
680                                       const char *list[],
681                                       size_t length)
682 {
683     _config_set_list (config, "search", "exclude_tags", list, length,
684                       &(config->search_exclude_tags));
685 }
686
687 /* Given a configuration item of the form <group>.<key> return the
688  * component group and key. If any error occurs, print a message on
689  * stderr and return 1. Otherwise, return 0.
690  *
691  * Note: This function modifies the original 'item' string.
692  */
693 static int
694 _item_split (char *item, char **group, char **key)
695 {
696     char *period;
697
698     *group = item;
699
700     period = strchr (item, '.');
701     if (period == NULL || *(period+1) == '\0') {
702         fprintf (stderr,
703                  "Invalid configuration name: %s\n"
704                  "(Should be of the form <section>.<item>)\n", item);
705         return 1;
706     }
707
708     *period = '\0';
709     *key = period + 1;
710
711     return 0;
712 }
713
714 static int
715 notmuch_config_command_get (notmuch_config_t *config, char *item)
716 {
717     if (strcmp(item, "database.path") == 0) {
718         printf ("%s\n", notmuch_config_get_database_path (config));
719     } else if (strcmp(item, "user.name") == 0) {
720         printf ("%s\n", notmuch_config_get_user_name (config));
721     } else if (strcmp(item, "user.primary_email") == 0) {
722         printf ("%s\n", notmuch_config_get_user_primary_email (config));
723     } else if (strcmp(item, "user.other_email") == 0) {
724         const char **other_email;
725         size_t i, length;
726         
727         other_email = notmuch_config_get_user_other_email (config, &length);
728         for (i = 0; i < length; i++)
729             printf ("%s\n", other_email[i]);
730     } else if (strcmp(item, "new.tags") == 0) {
731         const char **tags;
732         size_t i, length;
733
734         tags = notmuch_config_get_new_tags (config, &length);
735         for (i = 0; i < length; i++)
736             printf ("%s\n", tags[i]);
737     } else {
738         char **value;
739         size_t i, length;
740         char *group, *key;
741
742         if (_item_split (item, &group, &key))
743             return 1;
744
745         value = g_key_file_get_string_list (config->key_file,
746                                             group, key,
747                                             &length, NULL);
748         if (value == NULL) {
749             fprintf (stderr, "Unknown configuration item: %s.%s\n",
750                      group, key);
751             return 1;
752         }
753
754         for (i = 0; i < length; i++)
755             printf ("%s\n", value[i]);
756
757         g_strfreev (value);
758     }
759
760     return 0;
761 }
762
763 static int
764 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
765 {
766     char *group, *key;
767
768     if (_item_split (item, &group, &key))
769         return 1;
770
771     /* With only the name of an item, we clear it from the
772      * configuration file.
773      *
774      * With a single value, we set it as a string.
775      *
776      * With multiple values, we set them as a string list.
777      */
778     switch (argc) {
779     case 0:
780         g_key_file_remove_key (config->key_file, group, key, NULL);
781         break;
782     case 1:
783         g_key_file_set_string (config->key_file, group, key, argv[0]);
784         break;
785     default:
786         g_key_file_set_string_list (config->key_file, group, key,
787                                     (const gchar **) argv, argc);
788         break;
789     }
790
791     return notmuch_config_save (config);
792 }
793
794 static int
795 notmuch_config_command_list (notmuch_config_t *config)
796 {
797     char **groups;
798     size_t g, groups_length;
799
800     groups = g_key_file_get_groups (config->key_file, &groups_length);
801     if (groups == NULL)
802         return 1;
803
804     for (g = 0; g < groups_length; g++) {
805         char **keys;
806         size_t k, keys_length;
807
808         keys = g_key_file_get_keys (config->key_file,
809                                     groups[g], &keys_length, NULL);
810         if (keys == NULL)
811             continue;
812
813         for (k = 0; k < keys_length; k++) {
814             char *value;
815
816             value = g_key_file_get_string (config->key_file,
817                                            groups[g], keys[k], NULL);
818             if (value != NULL) {
819                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
820                 free (value);
821             }
822         }
823
824         g_strfreev (keys);
825     }
826
827     g_strfreev (groups);
828
829     return 0;
830 }
831
832 int
833 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
834 {
835     int ret;
836
837     argc--; argv++; /* skip subcommand argument */
838
839     if (argc < 1) {
840         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
841         return EXIT_FAILURE;
842     }
843
844     if (strcmp (argv[0], "get") == 0) {
845         if (argc != 2) {
846             fprintf (stderr, "Error: notmuch config get requires exactly "
847                      "one argument.\n");
848             return EXIT_FAILURE;
849         }
850         ret = notmuch_config_command_get (config, argv[1]);
851     } else if (strcmp (argv[0], "set") == 0) {
852         if (argc < 2) {
853             fprintf (stderr, "Error: notmuch config set requires at least "
854                      "one argument.\n");
855             return EXIT_FAILURE;
856         }
857         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
858     } else if (strcmp (argv[0], "list") == 0) {
859         ret = notmuch_config_command_list (config);
860     } else {
861         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
862                  argv[0]);
863         return EXIT_FAILURE;
864     }
865
866     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
867
868 }
869
870 notmuch_bool_t
871 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
872 {
873     return config->maildir_synchronize_flags;
874 }
875
876 void
877 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
878                                               notmuch_bool_t synchronize_flags)
879 {
880     g_key_file_set_boolean (config->key_file,
881                             "maildir", "synchronize_flags", synchronize_flags);
882     config->maildir_synchronize_flags = synchronize_flags;
883 }