]> git.cworth.org Git - notmuch/blob - notmuch-config.c
cli/config: refactor _stored_in_db
[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 https://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 https://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 static const char crypto_config_comment[] =
105     " Cryptography related configuration\n"
106     "\n"
107     " The following old option is now ignored:\n"
108     "\n"
109     "\tgpgpath\n"
110     "\t\tThis option was used by older builds of notmuch to choose\n"
111     "\t\tthe version of gpg to use.\n"
112     "\t\tSetting $PATH is a better approach.\n";
113
114 struct _notmuch_config {
115     char *filename;
116     GKeyFile *key_file;
117     bool is_new;
118
119     char *database_path;
120     char *crypto_gpg_path;
121     char *user_name;
122     char *user_primary_email;
123     const char **user_other_email;
124     size_t user_other_email_length;
125     const char **new_tags;
126     size_t new_tags_length;
127     const char **new_ignore;
128     size_t new_ignore_length;
129     bool maildir_synchronize_flags;
130     const char **search_exclude_tags;
131     size_t search_exclude_tags_length;
132 };
133
134 static int
135 notmuch_config_destructor (notmuch_config_t *config)
136 {
137     if (config->key_file)
138         g_key_file_free (config->key_file);
139
140     return 0;
141 }
142
143 static char *
144 get_name_from_passwd_file (void *ctx)
145 {
146     long pw_buf_size;
147     char *pw_buf;
148     struct passwd passwd, *ignored;
149     char *name;
150     int e;
151
152     pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
153     if (pw_buf_size == -1) pw_buf_size = 64;
154     pw_buf = talloc_size (ctx, pw_buf_size);
155
156     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
157                             pw_buf_size, &ignored)) == ERANGE) {
158         pw_buf_size = pw_buf_size * 2;
159         pw_buf = talloc_zero_size(ctx, pw_buf_size);
160     }
161
162     if (e == 0) {
163         char *comma = strchr (passwd.pw_gecos, ',');
164         if (comma)
165             name = talloc_strndup (ctx, passwd.pw_gecos,
166                                    comma - passwd.pw_gecos);
167         else
168             name = talloc_strdup (ctx, passwd.pw_gecos);
169     } else {
170         name = talloc_strdup (ctx, "");
171     }
172
173     talloc_free (pw_buf);
174
175     return name;
176 }
177
178 static char *
179 get_username_from_passwd_file (void *ctx)
180 {
181     long pw_buf_size;
182     char *pw_buf;
183     struct passwd passwd, *ignored;
184     char *name;
185     int e;
186
187     pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
188     if (pw_buf_size == -1) pw_buf_size = 64;
189     pw_buf = talloc_zero_size (ctx, pw_buf_size);
190
191     while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
192                             pw_buf_size, &ignored)) == ERANGE) {
193         pw_buf_size = pw_buf_size * 2;
194         pw_buf = talloc_zero_size(ctx, pw_buf_size);
195     }
196
197     if (e == 0)
198         name = talloc_strdup (ctx, passwd.pw_name);
199     else
200         name = talloc_strdup (ctx, "");
201
202     talloc_free (pw_buf);
203
204     return name;
205 }
206
207 static bool
208 get_config_from_file (notmuch_config_t *config, bool create_new)
209 {
210     #define BUF_SIZE 4096
211     char *config_str = NULL;
212     int config_len = 0;
213     int config_bufsize = BUF_SIZE;
214     size_t len;
215     GError *error = NULL;
216     bool ret = false;
217
218     FILE *fp = fopen(config->filename, "r");
219     if (fp == NULL) {
220         if (errno == ENOENT) {
221             /* If create_new is true, then the caller is prepared for a
222              * default configuration file in the case of FILE NOT FOUND.
223              */
224             if (create_new) {
225                 config->is_new = true;
226                 ret = true;
227             } else {
228                 fprintf (stderr, "Configuration file %s not found.\n"
229                          "Try running 'notmuch setup' to create a configuration.\n",
230                          config->filename);
231             }
232         } else {
233             fprintf (stderr, "Error opening config file '%s': %s\n",
234                      config->filename, strerror(errno));
235         }
236         goto out;
237     }
238
239     config_str = talloc_zero_array (config, char, config_bufsize);
240     if (config_str == NULL) {
241         fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
242         goto out;
243     }
244
245     while ((len = fread (config_str + config_len, 1,
246                          config_bufsize - config_len, fp)) > 0) {
247         config_len += len;
248         if (config_len == config_bufsize) {
249             config_bufsize += BUF_SIZE;
250             config_str = talloc_realloc (config, config_str, char, config_bufsize);
251             if (config_str == NULL) {
252                 fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
253                          config->filename);
254                 goto out;
255             }
256         }
257     }
258
259     if (ferror (fp)) {
260         fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
261         goto out;
262     }
263
264     if (g_key_file_load_from_data (config->key_file, config_str, config_len,
265                                    G_KEY_FILE_KEEP_COMMENTS, &error)) {
266         ret = true;
267         goto out;
268     }
269
270     fprintf (stderr, "Error parsing config file '%s': %s\n",
271              config->filename, error->message);
272
273     g_error_free (error);
274
275 out:
276     if (fp)
277         fclose(fp);
278
279     if (config_str)
280         talloc_free(config_str);
281
282     return ret;
283 }
284
285 /* Open the named notmuch configuration file. If the filename is NULL,
286  * the value of the environment variable $NOTMUCH_CONFIG will be used.
287  * If $NOTMUCH_CONFIG is unset, the default configuration file
288  * ($HOME/.notmuch-config) will be used.
289  *
290  * If any error occurs, (out of memory, or a permission-denied error,
291  * etc.), this function will print a message to stderr and return
292  * NULL.
293  *
294  * FILE NOT FOUND: When the specified configuration file (whether from
295  * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
296  * exist, the behavior of this function depends on the 'is_new_ret'
297  * variable.
298  *
299  *      If is_new_ret is NULL, then a "file not found" message will be
300  *      printed to stderr and NULL will be returned.
301
302  *      If is_new_ret is non-NULL then a default configuration will be
303  *      returned and *is_new_ret will be set to 1 on return so that
304  *      the caller can recognize this case.
305  *
306  *      These default configuration settings are determined as
307  *      follows:
308  *
309  *              database_path:          $MAILDIR, otherwise $HOME/mail
310  *
311  *              user_name:              $NAME variable if set, otherwise
312  *                                      read from /etc/passwd
313  *
314  *              user_primary_mail:      $EMAIL variable if set, otherwise
315  *                                      constructed from the username and
316  *                                      hostname of the current machine.
317  *
318  *              user_other_email:       Not set.
319  *
320  *      The default configuration also contains comments to guide the
321  *      user in editing the file directly.
322  */
323 notmuch_config_t *
324 notmuch_config_open (void *ctx,
325                      const char *filename,
326                      notmuch_config_mode_t config_mode)
327 {
328     GError *error = NULL;
329     size_t tmp;
330     char *notmuch_config_env = NULL;
331     int file_had_database_group;
332     int file_had_new_group;
333     int file_had_user_group;
334     int file_had_maildir_group;
335     int file_had_search_group;
336     int file_had_crypto_group;
337
338     notmuch_config_t *config = talloc_zero (ctx, notmuch_config_t);
339     if (config == NULL) {
340         fprintf (stderr, "Out of memory.\n");
341         return NULL;
342     }
343     
344     talloc_set_destructor (config, notmuch_config_destructor);
345
346     /* non-zero defaults */
347     config->maildir_synchronize_flags = true;
348
349     if (filename) {
350         config->filename = talloc_strdup (config, filename);
351     } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
352         config->filename = talloc_strdup (config, notmuch_config_env);
353     } else {
354         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
355                                             getenv ("HOME"));
356     }
357
358     config->key_file = g_key_file_new ();
359
360     if (config_mode & NOTMUCH_CONFIG_OPEN) {
361         bool create_new = (config_mode & NOTMUCH_CONFIG_CREATE) != 0;
362
363         if (! get_config_from_file (config, create_new)) {
364             talloc_free (config);
365             return NULL;
366         }
367     }
368
369     /* Whenever we know of configuration sections that don't appear in
370      * the configuration file, we add some comments to help the user
371      * understand what can be done.
372      *
373      * It would be convenient to just add those comments now, but
374      * apparently g_key_file will clear any comments when keys are
375      * added later that create the groups. So we have to check for the
376      * groups now, but add the comments only after setting all of our
377      * values.
378      */
379     file_had_database_group = g_key_file_has_group (config->key_file,
380                                                     "database");
381     file_had_new_group = g_key_file_has_group (config->key_file, "new");
382     file_had_user_group = g_key_file_has_group (config->key_file, "user");
383     file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
384     file_had_search_group = g_key_file_has_group (config->key_file, "search");
385     file_had_crypto_group = g_key_file_has_group (config->key_file, "crypto");
386
387     if (notmuch_config_get_database_path (config) == NULL) {
388         char *path = getenv ("MAILDIR");
389         if (path)
390             path = talloc_strdup (config, path);
391         else
392             path = talloc_asprintf (config, "%s/mail",
393                                     getenv ("HOME"));
394         notmuch_config_set_database_path (config, path);
395         talloc_free (path);
396     }
397
398     if (notmuch_config_get_user_name (config) == NULL) {
399         char *name = getenv ("NAME");
400         if (name)
401             name = talloc_strdup (config, name);
402         else
403             name = get_name_from_passwd_file (config);
404         notmuch_config_set_user_name (config, name);
405         talloc_free (name);
406     }
407
408     if (notmuch_config_get_user_primary_email (config) == NULL) {
409         char *email = getenv ("EMAIL");
410         if (email) {
411             notmuch_config_set_user_primary_email (config, email);
412         } else {
413             char hostname[256];
414             struct hostent *hostent;
415             const char *domainname;
416
417             char *username = get_username_from_passwd_file (config);
418
419             gethostname (hostname, 256);
420             hostname[255] = '\0';
421
422             hostent = gethostbyname (hostname);
423             if (hostent && (domainname = strchr (hostent->h_name, '.')))
424                 domainname += 1;
425             else
426                 domainname = "(none)";
427
428             email = talloc_asprintf (config, "%s@%s.%s",
429                                      username, hostname, domainname);
430
431             notmuch_config_set_user_primary_email (config, email);
432
433             talloc_free (username);
434             talloc_free (email);
435         }
436     }
437
438     if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
439         const char *tags[] = { "unread", "inbox" };
440         notmuch_config_set_new_tags (config, tags, 2);
441     }
442
443     if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
444         notmuch_config_set_new_ignore (config, NULL, 0);
445     }
446
447     if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
448         if (config->is_new) {
449             const char *tags[] = { "deleted", "spam" };
450             notmuch_config_set_search_exclude_tags (config, tags, 2);
451         } else {
452             notmuch_config_set_search_exclude_tags (config, NULL, 0);
453         }
454     }
455
456     error = NULL;
457     config->maildir_synchronize_flags =
458         g_key_file_get_boolean (config->key_file,
459                                 "maildir", "synchronize_flags", &error);
460     if (error) {
461         notmuch_config_set_maildir_synchronize_flags (config, true);
462         g_error_free (error);
463     }
464
465     /* Whenever we know of configuration sections that don't appear in
466      * the configuration file, we add some comments to help the user
467      * understand what can be done. */
468     if (config->is_new)
469         g_key_file_set_comment (config->key_file, NULL, NULL,
470                                 toplevel_config_comment, NULL);
471
472     if (! file_had_database_group)
473         g_key_file_set_comment (config->key_file, "database", NULL,
474                                 database_config_comment, NULL);
475
476     if (! file_had_new_group)
477         g_key_file_set_comment (config->key_file, "new", NULL,
478                                 new_config_comment, NULL);
479
480     if (! file_had_user_group)
481         g_key_file_set_comment (config->key_file, "user", NULL,
482                                 user_config_comment, NULL);
483
484     if (! file_had_maildir_group)
485         g_key_file_set_comment (config->key_file, "maildir", NULL,
486                                 maildir_config_comment, NULL);
487
488     if (! file_had_search_group)
489         g_key_file_set_comment (config->key_file, "search", NULL,
490                                 search_config_comment, NULL);
491
492     if (! file_had_crypto_group)
493         g_key_file_set_comment (config->key_file, "crypto", NULL,
494                                 crypto_config_comment, NULL);
495
496     return config;
497 }
498
499 /* Close the given notmuch_config_t object, freeing all resources.
500  * 
501  * Note: Any changes made to the configuration are *not* saved by this
502  * function. To save changes, call notmuch_config_save before
503  * notmuch_config_close.
504 */
505 void
506 notmuch_config_close (notmuch_config_t *config)
507 {
508     talloc_free (config);
509 }
510
511 /* Save any changes made to the notmuch configuration.
512  *
513  * Any comments originally in the file will be preserved.
514  *
515  * Returns 0 if successful, and 1 in case of any error, (after
516  * printing a description of the error to stderr).
517  */
518 int
519 notmuch_config_save (notmuch_config_t *config)
520 {
521     size_t length;
522     char *data, *filename;
523     GError *error = NULL;
524
525     data = g_key_file_to_data (config->key_file, &length, NULL);
526     if (data == NULL) {
527         fprintf (stderr, "Out of memory.\n");
528         return 1;
529     }
530
531     /* Try not to overwrite symlinks. */
532     filename = canonicalize_file_name (config->filename);
533     if (! filename) {
534         if (errno == ENOENT) {
535             filename = strdup (config->filename);
536             if (! filename) {
537                 fprintf (stderr, "Out of memory.\n");
538                 g_free (data);
539                 return 1;
540             }
541         } else {
542             fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
543                      strerror (errno));
544             g_free (data);
545             return 1;
546         }
547     }
548
549     if (! g_file_set_contents (filename, data, length, &error)) {
550         if (strcmp (filename, config->filename) != 0) {
551             fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
552                      config->filename, filename, error->message);
553         } else {
554             fprintf (stderr, "Error saving configuration to %s: %s\n",
555                      filename, error->message);
556         }
557         g_error_free (error);
558         free (filename);
559         g_free (data);
560         return 1;
561     }
562
563     free (filename);
564     g_free (data);
565     return 0;
566 }
567
568 bool
569 notmuch_config_is_new (notmuch_config_t *config)
570 {
571     return config->is_new;
572 }
573
574 static const char *
575 _config_get (notmuch_config_t *config, char **field,
576              const char *group, const char *key)
577 {
578     /* read from config file and cache value, if not cached already */
579     if (*field == NULL) {
580         char *value;
581         value = g_key_file_get_string (config->key_file, group, key, NULL);
582         if (value) {
583             *field = talloc_strdup (config, value);
584             free (value);
585         }
586     }
587     return *field;
588 }
589
590 static void
591 _config_set (notmuch_config_t *config, char **field,
592              const char *group, const char *key, const char *value)
593 {
594     g_key_file_set_string (config->key_file, group, key, value);
595
596     /* drop the cached value */
597     talloc_free (*field);
598     *field = NULL;
599 }
600
601 static const char **
602 _config_get_list (notmuch_config_t *config,
603                   const char *section, const char *key,
604                   const char ***outlist, size_t *list_length, size_t *ret_length)
605 {
606     assert(outlist);
607
608     /* read from config file and cache value, if not cached already */
609     if (*outlist == NULL) {
610
611         char **inlist = g_key_file_get_string_list (config->key_file,
612                                              section, key, list_length, NULL);
613         if (inlist) {
614             unsigned int i;
615
616             *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
617
618             for (i = 0; i < *list_length; i++)
619                 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
620
621             (*outlist)[i] = NULL;
622
623             g_strfreev (inlist);
624         }
625     }
626
627     if (ret_length)
628         *ret_length = *list_length;
629
630     return *outlist;
631 }
632
633 static void
634 _config_set_list (notmuch_config_t *config,
635                   const char *group, const char *key,
636                   const char *list[],
637                   size_t length, const char ***config_var )
638 {
639     g_key_file_set_string_list (config->key_file, group, key, list, length);
640
641     /* drop the cached value */
642     talloc_free (*config_var);
643     *config_var = NULL;
644 }
645
646 const char *
647 notmuch_config_get_database_path (notmuch_config_t *config)
648 {
649     char *db_path = (char *)_config_get (config, &config->database_path, "database", "path");
650
651     if (db_path && *db_path != '/') {
652         /* If the path in the configuration file begins with any
653          * character other than /, presume that it is relative to
654          * $HOME and update as appropriate.
655          */
656         char *abs_path = talloc_asprintf (config, "%s/%s", getenv ("HOME"), db_path);
657         talloc_free (db_path);
658         db_path = config->database_path = abs_path;
659     }
660
661     return db_path;
662 }
663
664 void
665 notmuch_config_set_database_path (notmuch_config_t *config,
666                                   const char *database_path)
667 {
668     _config_set (config, &config->database_path, "database", "path", database_path);
669 }
670
671 const char *
672 notmuch_config_get_user_name (notmuch_config_t *config)
673 {
674     return _config_get (config, &config->user_name, "user", "name");
675 }
676
677 void
678 notmuch_config_set_user_name (notmuch_config_t *config,
679                               const char *user_name)
680 {
681     _config_set (config, &config->user_name, "user", "name", user_name);
682 }
683
684 const char *
685 notmuch_config_get_user_primary_email (notmuch_config_t *config)
686 {
687     return _config_get (config, &config->user_primary_email, "user", "primary_email");
688 }
689
690 void
691 notmuch_config_set_user_primary_email (notmuch_config_t *config,
692                                        const char *primary_email)
693 {
694     _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
695 }
696
697 const char **
698 notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
699 {
700     return _config_get_list (config, "user", "other_email",
701                              &(config->user_other_email),
702                              &(config->user_other_email_length), length);
703 }
704
705 const char **
706 notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
707 {
708     return _config_get_list (config, "new", "tags",
709                              &(config->new_tags),
710                              &(config->new_tags_length), length);
711 }
712
713 const char **
714 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
715 {
716     return _config_get_list (config, "new", "ignore",
717                              &(config->new_ignore),
718                              &(config->new_ignore_length), length);
719 }
720
721 void
722 notmuch_config_set_user_other_email (notmuch_config_t *config,
723                                      const char *list[],
724                                      size_t length)
725 {
726     _config_set_list (config, "user", "other_email", list, length,
727                      &(config->user_other_email));
728 }
729
730 void
731 notmuch_config_set_new_tags (notmuch_config_t *config,
732                                      const char *list[],
733                                      size_t length)
734 {
735     _config_set_list (config, "new", "tags", list, length,
736                      &(config->new_tags));
737 }
738
739 void
740 notmuch_config_set_new_ignore (notmuch_config_t *config,
741                                const char *list[],
742                                size_t length)
743 {
744     _config_set_list (config, "new", "ignore", list, length,
745                      &(config->new_ignore));
746 }
747
748 const char **
749 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
750 {
751     return _config_get_list (config, "search", "exclude_tags",
752                              &(config->search_exclude_tags),
753                              &(config->search_exclude_tags_length), length);
754 }
755
756 void
757 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
758                                       const char *list[],
759                                       size_t length)
760 {
761     _config_set_list (config, "search", "exclude_tags", list, length,
762                       &(config->search_exclude_tags));
763 }
764
765
766 /* Given a configuration item of the form <group>.<key> return the
767  * component group and key. If any error occurs, print a message on
768  * stderr and return 1. Otherwise, return 0.
769  *
770  * Note: This function modifies the original 'item' string.
771  */
772 static int
773 _item_split (char *item, char **group, char **key)
774 {
775     char *period;
776
777     *group = item;
778
779     period = strchr (item, '.');
780     if (period == NULL || *(period+1) == '\0') {
781         fprintf (stderr,
782                  "Invalid configuration name: %s\n"
783                  "(Should be of the form <section>.<item>)\n", item);
784         return 1;
785     }
786
787     *period = '\0';
788     *key = period + 1;
789
790     return 0;
791 }
792
793 #define BUILT_WITH_PREFIX "built_with."
794
795 typedef struct config_key {
796     const char *name;
797     bool in_db;
798     bool prefix;
799     bool (*validate)(const char *);
800 } config_key_info_t;
801
802 static struct config_key
803 config_key_table[] = {
804     {"index.decrypt",   true,   false,  NULL},
805     {"query.",          true,   true,   NULL},
806 };
807
808 static config_key_info_t *
809 _config_key_info (const char *item)
810 {
811     for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
812         if (config_key_table[i].prefix &&
813             strncmp (item, config_key_table[i].name,
814                      strlen(config_key_table[i].name)) == 0)
815             return config_key_table+i;
816         if (strcmp (item, config_key_table[i].name) == 0)
817             return config_key_table+i;
818     }
819     return NULL;
820 }
821
822 static bool
823 _stored_in_db (const char *item)
824 {
825     config_key_info_t *info;
826     info = _config_key_info (item);
827
828     return (info && info->in_db);
829 }
830
831 static int
832 _print_db_config(notmuch_config_t *config, const char *name)
833 {
834     notmuch_database_t *notmuch;
835     char *val;
836
837     if (notmuch_database_open (notmuch_config_get_database_path (config),
838                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
839         return EXIT_FAILURE;
840
841     /* XXX Handle UUID mismatch? */
842
843     if (print_status_database ("notmuch config", notmuch,
844                                notmuch_database_get_config (notmuch, name, &val)))
845         return EXIT_FAILURE;
846
847      puts (val);
848
849     return EXIT_SUCCESS;
850 }
851
852 static int
853 notmuch_config_command_get (notmuch_config_t *config, char *item)
854 {
855     if (strcmp(item, "database.path") == 0) {
856         printf ("%s\n", notmuch_config_get_database_path (config));
857     } else if (strcmp(item, "user.name") == 0) {
858         printf ("%s\n", notmuch_config_get_user_name (config));
859     } else if (strcmp(item, "user.primary_email") == 0) {
860         printf ("%s\n", notmuch_config_get_user_primary_email (config));
861     } else if (strcmp(item, "user.other_email") == 0) {
862         const char **other_email;
863         size_t i, length;
864         
865         other_email = notmuch_config_get_user_other_email (config, &length);
866         for (i = 0; i < length; i++)
867             printf ("%s\n", other_email[i]);
868     } else if (strcmp(item, "new.tags") == 0) {
869         const char **tags;
870         size_t i, length;
871
872         tags = notmuch_config_get_new_tags (config, &length);
873         for (i = 0; i < length; i++)
874             printf ("%s\n", tags[i]);
875     } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
876         printf ("%s\n",
877                 notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
878     } else if (_stored_in_db (item)) {
879         return _print_db_config (config, item);
880     } else {
881         char **value;
882         size_t i, length;
883         char *group, *key;
884
885         if (_item_split (item, &group, &key))
886             return 1;
887
888         value = g_key_file_get_string_list (config->key_file,
889                                             group, key,
890                                             &length, NULL);
891         if (value == NULL) {
892             fprintf (stderr, "Unknown configuration item: %s.%s\n",
893                      group, key);
894             return 1;
895         }
896
897         for (i = 0; i < length; i++)
898             printf ("%s\n", value[i]);
899
900         g_strfreev (value);
901     }
902
903     return 0;
904 }
905
906 static int
907 _set_db_config(notmuch_config_t *config, const char *key, int argc, char **argv)
908 {
909     notmuch_database_t *notmuch;
910     const char *val = "";
911
912     if (argc > 1) {
913         /* XXX handle lists? */
914         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
915         return EXIT_FAILURE;
916     }
917
918     if (argc > 0) {
919         val = argv[0];
920     }
921
922     if (notmuch_database_open (notmuch_config_get_database_path (config),
923                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
924         return EXIT_FAILURE;
925
926     /* XXX Handle UUID mismatch? */
927
928     if (print_status_database ("notmuch config", notmuch,
929                                notmuch_database_set_config (notmuch, key, val)))
930         return EXIT_FAILURE;
931
932     if (print_status_database ("notmuch config", notmuch,
933                                notmuch_database_close (notmuch)))
934         return EXIT_FAILURE;
935
936     return EXIT_SUCCESS;
937 }
938
939 static int
940 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
941 {
942     char *group, *key;
943     config_key_info_t *key_info;
944
945     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
946         fprintf (stderr, "Error: read only option: %s\n", item);
947         return 1;
948     }
949
950     key_info = _config_key_info (item);
951     if (key_info && key_info->validate && (! key_info->validate (item)))
952         return 1;
953
954     if (key_info && key_info->in_db) {
955         return _set_db_config (config, item, argc, argv);
956     }
957
958     if (_item_split (item, &group, &key))
959         return 1;
960
961     /* With only the name of an item, we clear it from the
962      * configuration file.
963      *
964      * With a single value, we set it as a string.
965      *
966      * With multiple values, we set them as a string list.
967      */
968     switch (argc) {
969     case 0:
970         g_key_file_remove_key (config->key_file, group, key, NULL);
971         break;
972     case 1:
973         g_key_file_set_string (config->key_file, group, key, argv[0]);
974         break;
975     default:
976         g_key_file_set_string_list (config->key_file, group, key,
977                                     (const gchar **) argv, argc);
978         break;
979     }
980
981     return notmuch_config_save (config);
982 }
983
984 static
985 void
986 _notmuch_config_list_built_with ()
987 {
988     printf("%scompact=%s\n",
989            BUILT_WITH_PREFIX,
990            notmuch_built_with ("compact") ? "true" : "false");
991     printf("%sfield_processor=%s\n",
992            BUILT_WITH_PREFIX,
993            notmuch_built_with ("field_processor") ? "true" : "false");
994     printf("%sretry_lock=%s\n",
995            BUILT_WITH_PREFIX,
996            notmuch_built_with ("retry_lock") ? "true" : "false");
997 }
998
999 static int
1000 _list_db_config (notmuch_config_t *config)
1001 {
1002     notmuch_database_t *notmuch;
1003     notmuch_config_list_t *list;
1004
1005     if (notmuch_database_open (notmuch_config_get_database_path (config),
1006                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
1007         return EXIT_FAILURE;
1008
1009     /* XXX Handle UUID mismatch? */
1010
1011
1012     if (print_status_database ("notmuch config", notmuch,
1013                                notmuch_database_get_config_list (notmuch, "", &list)))
1014         return EXIT_FAILURE;
1015
1016     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
1017         printf("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
1018     }
1019     notmuch_config_list_destroy (list);
1020
1021    return EXIT_SUCCESS;
1022 }
1023
1024 static int
1025 notmuch_config_command_list (notmuch_config_t *config)
1026 {
1027     char **groups;
1028     size_t g, groups_length;
1029
1030     groups = g_key_file_get_groups (config->key_file, &groups_length);
1031     if (groups == NULL)
1032         return 1;
1033
1034     for (g = 0; g < groups_length; g++) {
1035         char **keys;
1036         size_t k, keys_length;
1037
1038         keys = g_key_file_get_keys (config->key_file,
1039                                     groups[g], &keys_length, NULL);
1040         if (keys == NULL)
1041             continue;
1042
1043         for (k = 0; k < keys_length; k++) {
1044             char *value;
1045
1046             value = g_key_file_get_string (config->key_file,
1047                                            groups[g], keys[k], NULL);
1048             if (value != NULL) {
1049                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
1050                 free (value);
1051             }
1052         }
1053
1054         g_strfreev (keys);
1055     }
1056
1057     g_strfreev (groups);
1058
1059     _notmuch_config_list_built_with ();
1060     return _list_db_config (config);
1061 }
1062
1063 int
1064 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
1065 {
1066     int ret;
1067     int opt_index;
1068
1069     opt_index = notmuch_minimal_options ("config", argc, argv);
1070     if (opt_index < 0)
1071         return EXIT_FAILURE;
1072
1073     if (notmuch_requested_db_uuid)
1074         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
1075                  notmuch_requested_db_uuid);
1076
1077     /* skip at least subcommand argument */
1078     argc-= opt_index;
1079     argv+= opt_index;
1080
1081     if (argc < 1) {
1082         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
1083         return EXIT_FAILURE;
1084     }
1085
1086     if (strcmp (argv[0], "get") == 0) {
1087         if (argc != 2) {
1088             fprintf (stderr, "Error: notmuch config get requires exactly "
1089                      "one argument.\n");
1090             return EXIT_FAILURE;
1091         }
1092         ret = notmuch_config_command_get (config, argv[1]);
1093     } else if (strcmp (argv[0], "set") == 0) {
1094         if (argc < 2) {
1095             fprintf (stderr, "Error: notmuch config set requires at least "
1096                      "one argument.\n");
1097             return EXIT_FAILURE;
1098         }
1099         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
1100     } else if (strcmp (argv[0], "list") == 0) {
1101         ret = notmuch_config_command_list (config);
1102     } else {
1103         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
1104                  argv[0]);
1105         return EXIT_FAILURE;
1106     }
1107
1108     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
1109
1110 }
1111
1112 bool
1113 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
1114 {
1115     return config->maildir_synchronize_flags;
1116 }
1117
1118 void
1119 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
1120                                               bool synchronize_flags)
1121 {
1122     g_key_file_set_boolean (config->key_file,
1123                             "maildir", "synchronize_flags", synchronize_flags);
1124     config->maildir_synchronize_flags = synchronize_flags;
1125 }