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