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