]> git.notmuchmail.org Git - notmuch/blob - notmuch-config.c
cli/config: support user header index config
[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     {"index.header.",   true,   true,   NULL},
806     {"query.",          true,   true,   NULL},
807 };
808
809 static config_key_info_t *
810 _config_key_info (const char *item)
811 {
812     for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
813         if (config_key_table[i].prefix &&
814             strncmp (item, config_key_table[i].name,
815                      strlen(config_key_table[i].name)) == 0)
816             return config_key_table+i;
817         if (strcmp (item, config_key_table[i].name) == 0)
818             return config_key_table+i;
819     }
820     return NULL;
821 }
822
823 static bool
824 _stored_in_db (const char *item)
825 {
826     config_key_info_t *info;
827     info = _config_key_info (item);
828
829     return (info && info->in_db);
830 }
831
832 static int
833 _print_db_config(notmuch_config_t *config, const char *name)
834 {
835     notmuch_database_t *notmuch;
836     char *val;
837
838     if (notmuch_database_open (notmuch_config_get_database_path (config),
839                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
840         return EXIT_FAILURE;
841
842     /* XXX Handle UUID mismatch? */
843
844     if (print_status_database ("notmuch config", notmuch,
845                                notmuch_database_get_config (notmuch, name, &val)))
846         return EXIT_FAILURE;
847
848      puts (val);
849
850     return EXIT_SUCCESS;
851 }
852
853 static int
854 notmuch_config_command_get (notmuch_config_t *config, char *item)
855 {
856     if (strcmp(item, "database.path") == 0) {
857         printf ("%s\n", notmuch_config_get_database_path (config));
858     } else if (strcmp(item, "user.name") == 0) {
859         printf ("%s\n", notmuch_config_get_user_name (config));
860     } else if (strcmp(item, "user.primary_email") == 0) {
861         printf ("%s\n", notmuch_config_get_user_primary_email (config));
862     } else if (strcmp(item, "user.other_email") == 0) {
863         const char **other_email;
864         size_t i, length;
865         
866         other_email = notmuch_config_get_user_other_email (config, &length);
867         for (i = 0; i < length; i++)
868             printf ("%s\n", other_email[i]);
869     } else if (strcmp(item, "new.tags") == 0) {
870         const char **tags;
871         size_t i, length;
872
873         tags = notmuch_config_get_new_tags (config, &length);
874         for (i = 0; i < length; i++)
875             printf ("%s\n", tags[i]);
876     } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
877         printf ("%s\n",
878                 notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
879     } else if (_stored_in_db (item)) {
880         return _print_db_config (config, item);
881     } else {
882         char **value;
883         size_t i, length;
884         char *group, *key;
885
886         if (_item_split (item, &group, &key))
887             return 1;
888
889         value = g_key_file_get_string_list (config->key_file,
890                                             group, key,
891                                             &length, NULL);
892         if (value == NULL) {
893             fprintf (stderr, "Unknown configuration item: %s.%s\n",
894                      group, key);
895             return 1;
896         }
897
898         for (i = 0; i < length; i++)
899             printf ("%s\n", value[i]);
900
901         g_strfreev (value);
902     }
903
904     return 0;
905 }
906
907 static int
908 _set_db_config(notmuch_config_t *config, const char *key, int argc, char **argv)
909 {
910     notmuch_database_t *notmuch;
911     const char *val = "";
912
913     if (argc > 1) {
914         /* XXX handle lists? */
915         fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
916         return EXIT_FAILURE;
917     }
918
919     if (argc > 0) {
920         val = argv[0];
921     }
922
923     if (notmuch_database_open (notmuch_config_get_database_path (config),
924                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
925         return EXIT_FAILURE;
926
927     /* XXX Handle UUID mismatch? */
928
929     if (print_status_database ("notmuch config", notmuch,
930                                notmuch_database_set_config (notmuch, key, val)))
931         return EXIT_FAILURE;
932
933     if (print_status_database ("notmuch config", notmuch,
934                                notmuch_database_close (notmuch)))
935         return EXIT_FAILURE;
936
937     return EXIT_SUCCESS;
938 }
939
940 static int
941 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
942 {
943     char *group, *key;
944     config_key_info_t *key_info;
945
946     if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
947         fprintf (stderr, "Error: read only option: %s\n", item);
948         return 1;
949     }
950
951     key_info = _config_key_info (item);
952     if (key_info && key_info->validate && (! key_info->validate (item)))
953         return 1;
954
955     if (key_info && key_info->in_db) {
956         return _set_db_config (config, item, argc, argv);
957     }
958
959     if (_item_split (item, &group, &key))
960         return 1;
961
962     /* With only the name of an item, we clear it from the
963      * configuration file.
964      *
965      * With a single value, we set it as a string.
966      *
967      * With multiple values, we set them as a string list.
968      */
969     switch (argc) {
970     case 0:
971         g_key_file_remove_key (config->key_file, group, key, NULL);
972         break;
973     case 1:
974         g_key_file_set_string (config->key_file, group, key, argv[0]);
975         break;
976     default:
977         g_key_file_set_string_list (config->key_file, group, key,
978                                     (const gchar **) argv, argc);
979         break;
980     }
981
982     return notmuch_config_save (config);
983 }
984
985 static
986 void
987 _notmuch_config_list_built_with ()
988 {
989     printf("%scompact=%s\n",
990            BUILT_WITH_PREFIX,
991            notmuch_built_with ("compact") ? "true" : "false");
992     printf("%sfield_processor=%s\n",
993            BUILT_WITH_PREFIX,
994            notmuch_built_with ("field_processor") ? "true" : "false");
995     printf("%sretry_lock=%s\n",
996            BUILT_WITH_PREFIX,
997            notmuch_built_with ("retry_lock") ? "true" : "false");
998 }
999
1000 static int
1001 _list_db_config (notmuch_config_t *config)
1002 {
1003     notmuch_database_t *notmuch;
1004     notmuch_config_list_t *list;
1005
1006     if (notmuch_database_open (notmuch_config_get_database_path (config),
1007                                NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
1008         return EXIT_FAILURE;
1009
1010     /* XXX Handle UUID mismatch? */
1011
1012
1013     if (print_status_database ("notmuch config", notmuch,
1014                                notmuch_database_get_config_list (notmuch, "", &list)))
1015         return EXIT_FAILURE;
1016
1017     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
1018         printf("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
1019     }
1020     notmuch_config_list_destroy (list);
1021
1022    return EXIT_SUCCESS;
1023 }
1024
1025 static int
1026 notmuch_config_command_list (notmuch_config_t *config)
1027 {
1028     char **groups;
1029     size_t g, groups_length;
1030
1031     groups = g_key_file_get_groups (config->key_file, &groups_length);
1032     if (groups == NULL)
1033         return 1;
1034
1035     for (g = 0; g < groups_length; g++) {
1036         char **keys;
1037         size_t k, keys_length;
1038
1039         keys = g_key_file_get_keys (config->key_file,
1040                                     groups[g], &keys_length, NULL);
1041         if (keys == NULL)
1042             continue;
1043
1044         for (k = 0; k < keys_length; k++) {
1045             char *value;
1046
1047             value = g_key_file_get_string (config->key_file,
1048                                            groups[g], keys[k], NULL);
1049             if (value != NULL) {
1050                 printf ("%s.%s=%s\n", groups[g], keys[k], value);
1051                 free (value);
1052             }
1053         }
1054
1055         g_strfreev (keys);
1056     }
1057
1058     g_strfreev (groups);
1059
1060     _notmuch_config_list_built_with ();
1061     return _list_db_config (config);
1062 }
1063
1064 int
1065 notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
1066 {
1067     int ret;
1068     int opt_index;
1069
1070     opt_index = notmuch_minimal_options ("config", argc, argv);
1071     if (opt_index < 0)
1072         return EXIT_FAILURE;
1073
1074     if (notmuch_requested_db_uuid)
1075         fprintf (stderr, "Warning: ignoring --uuid=%s\n",
1076                  notmuch_requested_db_uuid);
1077
1078     /* skip at least subcommand argument */
1079     argc-= opt_index;
1080     argv+= opt_index;
1081
1082     if (argc < 1) {
1083         fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
1084         return EXIT_FAILURE;
1085     }
1086
1087     if (strcmp (argv[0], "get") == 0) {
1088         if (argc != 2) {
1089             fprintf (stderr, "Error: notmuch config get requires exactly "
1090                      "one argument.\n");
1091             return EXIT_FAILURE;
1092         }
1093         ret = notmuch_config_command_get (config, argv[1]);
1094     } else if (strcmp (argv[0], "set") == 0) {
1095         if (argc < 2) {
1096             fprintf (stderr, "Error: notmuch config set requires at least "
1097                      "one argument.\n");
1098             return EXIT_FAILURE;
1099         }
1100         ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
1101     } else if (strcmp (argv[0], "list") == 0) {
1102         ret = notmuch_config_command_list (config);
1103     } else {
1104         fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
1105                  argv[0]);
1106         return EXIT_FAILURE;
1107     }
1108
1109     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
1110
1111 }
1112
1113 bool
1114 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
1115 {
1116     return config->maildir_synchronize_flags;
1117 }
1118
1119 void
1120 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
1121                                               bool synchronize_flags)
1122 {
1123     g_key_file_set_boolean (config->key_file,
1124                             "maildir", "synchronize_flags", synchronize_flags);
1125     config->maildir_synchronize_flags = synchronize_flags;
1126 }