1 /* notmuch - Not much of an email program, (just index and search)
3 * Copyright © 2009 Carl Worth
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.
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.
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/ .
18 * Author: Carl Worth <cworth@cworth.org>
21 #include "notmuch-client.h"
27 static const char toplevel_config_comment[] =
28 " .notmuch-config - Configuration file for the notmuch mail system\n"
30 " For more information about notmuch, see http://notmuchmail.org";
32 static const char database_config_comment[] =
33 " Database configuration\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";
41 static const char new_config_comment[] =
42 " Configuration for \"notmuch new\"\n"
44 " The following options are supported here:\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"
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"
52 "\t NOTE: *Every* file/directory that goes by one of those names will\n"
53 "\t be ignored, independent of its depth/location in the mail store.\n";
55 static const char user_config_comment[] =
56 " User configuration\n"
58 " Here is where you can let notmuch know how you would like to be\n"
59 " addressed. Valid settings are\n"
61 "\tname Your full name.\n"
62 "\tprimary_email Your primary email address.\n"
63 "\tother_email A list (separated by ';') of other email addresses\n"
64 "\t at which you receive email.\n"
66 " Notmuch will use the various email addresses configured here when\n"
67 " formatting replies. It will avoid including your own addresses in the\n"
68 " recipient list of replies, and will set the From address based on the\n"
69 " address to which the original email was addressed.\n";
71 static const char maildir_config_comment[] =
72 " Maildir compatibility configuration\n"
74 " The following option is supported here:\n"
76 "\tsynchronize_flags Valid values are true and false.\n"
78 "\tIf true, then the following maildir flags (in message filenames)\n"
79 "\twill be synchronized with the corresponding notmuch tags:\n"
87 "\t\tS unread (added when 'S' flag is not present)\n"
89 "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
90 "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
91 "\tcommands will notice tag changes and update flags in filenames\n";
93 static const char search_config_comment[] =
94 " Search configuration\n"
96 " The following option is supported here:\n"
99 "\t\tA ;-separated list of tags that will be excluded from\n"
100 "\t\tsearch results by default. Using an excluded tag in a\n"
101 "\t\tquery will override that exclusion.\n";
103 struct _notmuch_config {
109 char *user_primary_email;
110 const char **user_other_email;
111 size_t user_other_email_length;
112 const char **new_tags;
113 size_t new_tags_length;
114 const char **new_ignore;
115 size_t new_ignore_length;
116 notmuch_bool_t maildir_synchronize_flags;
117 const char **search_exclude_tags;
118 size_t search_exclude_tags_length;
122 notmuch_config_destructor (notmuch_config_t *config)
124 if (config->key_file)
125 g_key_file_free (config->key_file);
131 get_name_from_passwd_file (void *ctx)
135 struct passwd passwd, *ignored;
139 pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
140 if (pw_buf_size == -1) pw_buf_size = 64;
141 pw_buf = talloc_size (ctx, pw_buf_size);
143 while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
144 pw_buf_size, &ignored)) == ERANGE) {
145 pw_buf_size = pw_buf_size * 2;
146 pw_buf = talloc_zero_size(ctx, pw_buf_size);
150 char *comma = strchr (passwd.pw_gecos, ',');
152 name = talloc_strndup (ctx, passwd.pw_gecos,
153 comma - passwd.pw_gecos);
155 name = talloc_strdup (ctx, passwd.pw_gecos);
157 name = talloc_strdup (ctx, "");
160 talloc_free (pw_buf);
166 get_username_from_passwd_file (void *ctx)
170 struct passwd passwd, *ignored;
174 pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
175 if (pw_buf_size == -1) pw_buf_size = 64;
176 pw_buf = talloc_zero_size (ctx, pw_buf_size);
178 while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
179 pw_buf_size, &ignored)) == ERANGE) {
180 pw_buf_size = pw_buf_size * 2;
181 pw_buf = talloc_zero_size(ctx, pw_buf_size);
185 name = talloc_strdup (ctx, passwd.pw_name);
187 name = talloc_strdup (ctx, "");
189 talloc_free (pw_buf);
194 /* Open the named notmuch configuration file. If the filename is NULL,
195 * the value of the environment variable $NOTMUCH_CONFIG will be used.
196 * If $NOTMUCH_CONFIG is unset, the default configuration file
197 * ($HOME/.notmuch-config) will be used.
199 * If any error occurs, (out of memory, or a permission-denied error,
200 * etc.), this function will print a message to stderr and return
203 * FILE NOT FOUND: When the specified configuration file (whether from
204 * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
205 * exist, the behavior of this function depends on the 'is_new_ret'
208 * If is_new_ret is NULL, then a "file not found" message will be
209 * printed to stderr and NULL will be returned.
211 * If is_new_ret is non-NULL then a default configuration will be
212 * returned and *is_new_ret will be set to 1 on return so that
213 * the caller can recognize this case.
215 * These default configuration settings are determined as
218 * database_path: $HOME/mail
220 * user_name: From /etc/passwd
222 * user_primary_mail: $EMAIL variable if set, otherwise
223 * constructed from the username and
224 * hostname of the current machine.
226 * user_other_email: Not set.
228 * The default configuration also contains comments to guide the
229 * user in editing the file directly.
232 notmuch_config_open (void *ctx,
233 const char *filename,
234 notmuch_bool_t *is_new_ret)
236 GError *error = NULL;
239 char *notmuch_config_env = NULL;
240 int file_had_database_group;
241 int file_had_new_group;
242 int file_had_user_group;
243 int file_had_maildir_group;
244 int file_had_search_group;
249 notmuch_config_t *config = talloc (ctx, notmuch_config_t);
250 if (config == NULL) {
251 fprintf (stderr, "Out of memory.\n");
255 talloc_set_destructor (config, notmuch_config_destructor);
258 config->filename = talloc_strdup (config, filename);
259 } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
260 config->filename = talloc_strdup (config, notmuch_config_env);
262 config->filename = talloc_asprintf (config, "%s/.notmuch-config",
266 config->key_file = g_key_file_new ();
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;
281 if (! g_key_file_load_from_file (config->key_file,
283 G_KEY_FILE_KEEP_COMMENTS,
286 /* If the caller passed a non-NULL value for is_new_ret, then
287 * the caller is prepared for a default configuration file in
288 * the case of FILE NOT FOUND. Otherwise, any read failure is
292 error->domain == G_FILE_ERROR &&
293 error->code == G_FILE_ERROR_NOENT)
295 g_error_free (error);
300 fprintf (stderr, "Error reading configuration file %s: %s\n",
301 config->filename, error->message);
302 talloc_free (config);
303 g_error_free (error);
308 /* Whenever we know of configuration sections that don't appear in
309 * the configuration file, we add some comments to help the user
310 * understand what can be done.
312 * It would be convenient to just add those comments now, but
313 * apparently g_key_file will clear any comments when keys are
314 * added later that create the groups. So we have to check for the
315 * groups now, but add the comments only after setting all of our
318 file_had_database_group = g_key_file_has_group (config->key_file,
320 file_had_new_group = g_key_file_has_group (config->key_file, "new");
321 file_had_user_group = g_key_file_has_group (config->key_file, "user");
322 file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
323 file_had_search_group = g_key_file_has_group (config->key_file, "search");
326 if (notmuch_config_get_database_path (config) == NULL) {
327 char *path = talloc_asprintf (config, "%s/mail",
329 notmuch_config_set_database_path (config, path);
333 if (notmuch_config_get_user_name (config) == NULL) {
334 char *name = get_name_from_passwd_file (config);
335 notmuch_config_set_user_name (config, name);
339 if (notmuch_config_get_user_primary_email (config) == NULL) {
340 char *email = getenv ("EMAIL");
342 notmuch_config_set_user_primary_email (config, email);
345 struct hostent *hostent;
346 const char *domainname;
348 char *username = get_username_from_passwd_file (config);
350 gethostname (hostname, 256);
351 hostname[255] = '\0';
353 hostent = gethostbyname (hostname);
354 if (hostent && (domainname = strchr (hostent->h_name, '.')))
357 domainname = "(none)";
359 email = talloc_asprintf (config, "%s@%s.%s",
360 username, hostname, domainname);
362 notmuch_config_set_user_primary_email (config, email);
364 talloc_free (username);
369 if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
370 const char *tags[] = { "unread", "inbox" };
371 notmuch_config_set_new_tags (config, tags, 2);
374 if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
375 notmuch_config_set_new_ignore (config, NULL, 0);
378 if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
380 const char *tags[] = { "deleted", "spam" };
381 notmuch_config_set_search_exclude_tags (config, tags, 2);
383 notmuch_config_set_search_exclude_tags (config, NULL, 0);
388 config->maildir_synchronize_flags =
389 g_key_file_get_boolean (config->key_file,
390 "maildir", "synchronize_flags", &error);
392 notmuch_config_set_maildir_synchronize_flags (config, TRUE);
393 g_error_free (error);
396 /* Whenever we know of configuration sections that don't appear in
397 * the configuration file, we add some comments to help the user
398 * understand what can be done. */
401 g_key_file_set_comment (config->key_file, NULL, NULL,
402 toplevel_config_comment, NULL);
405 if (! file_had_database_group)
407 g_key_file_set_comment (config->key_file, "database", NULL,
408 database_config_comment, NULL);
411 if (! file_had_new_group)
413 g_key_file_set_comment (config->key_file, "new", NULL,
414 new_config_comment, NULL);
417 if (! file_had_user_group)
419 g_key_file_set_comment (config->key_file, "user", NULL,
420 user_config_comment, NULL);
423 if (! file_had_maildir_group)
425 g_key_file_set_comment (config->key_file, "maildir", NULL,
426 maildir_config_comment, NULL);
429 if (! file_had_search_group) {
430 g_key_file_set_comment (config->key_file, "search", NULL,
431 search_config_comment, NULL);
435 *is_new_ret = is_new;
440 /* Close the given notmuch_config_t object, freeing all resources.
442 * Note: Any changes made to the configuration are *not* saved by this
443 * function. To save changes, call notmuch_config_save before
444 * notmuch_config_close.
447 notmuch_config_close (notmuch_config_t *config)
449 talloc_free (config);
452 /* Save any changes made to the notmuch configuration.
454 * Any comments originally in the file will be preserved.
456 * Returns 0 if successful, and 1 in case of any error, (after
457 * printing a description of the error to stderr).
460 notmuch_config_save (notmuch_config_t *config)
464 GError *error = NULL;
466 data = g_key_file_to_data (config->key_file, &length, NULL);
468 fprintf (stderr, "Out of memory.\n");
472 if (! g_file_set_contents (config->filename, data, length, &error)) {
473 fprintf (stderr, "Error saving configuration to %s: %s\n",
474 config->filename, error->message);
475 g_error_free (error);
485 _config_get_list (notmuch_config_t *config,
486 const char *section, const char *key,
487 const char ***outlist, size_t *list_length, size_t *ret_length)
491 if (*outlist == NULL) {
493 char **inlist = g_key_file_get_string_list (config->key_file,
494 section, key, list_length, NULL);
498 *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
500 for (i = 0; i < *list_length; i++)
501 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
503 (*outlist)[i] = NULL;
510 *ret_length = *list_length;
516 _config_set_list (notmuch_config_t *config,
517 const char *group, const char *name,
519 size_t length, const char ***config_var )
521 g_key_file_set_string_list (config->key_file, group, name, list, length);
522 talloc_free (*config_var);
527 notmuch_config_get_database_path (notmuch_config_t *config)
531 if (config->database_path == NULL) {
532 path = g_key_file_get_string (config->key_file,
533 "database", "path", NULL);
535 config->database_path = talloc_strdup (config, path);
540 return config->database_path;
544 notmuch_config_set_database_path (notmuch_config_t *config,
545 const char *database_path)
547 g_key_file_set_string (config->key_file,
548 "database", "path", database_path);
550 talloc_free (config->database_path);
551 config->database_path = NULL;
555 notmuch_config_get_user_name (notmuch_config_t *config)
559 if (config->user_name == NULL) {
560 name = g_key_file_get_string (config->key_file,
561 "user", "name", NULL);
563 config->user_name = talloc_strdup (config, name);
568 return config->user_name;
572 notmuch_config_set_user_name (notmuch_config_t *config,
573 const char *user_name)
575 g_key_file_set_string (config->key_file,
576 "user", "name", user_name);
578 talloc_free (config->user_name);
579 config->user_name = NULL;
583 notmuch_config_get_user_primary_email (notmuch_config_t *config)
587 if (config->user_primary_email == NULL) {
588 email = g_key_file_get_string (config->key_file,
589 "user", "primary_email", NULL);
591 config->user_primary_email = talloc_strdup (config, email);
596 return config->user_primary_email;
600 notmuch_config_set_user_primary_email (notmuch_config_t *config,
601 const char *primary_email)
603 g_key_file_set_string (config->key_file,
604 "user", "primary_email", primary_email);
606 talloc_free (config->user_primary_email);
607 config->user_primary_email = NULL;
611 notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length)
613 return _config_get_list (config, "user", "other_email",
614 &(config->user_other_email),
615 &(config->user_other_email_length), length);
619 notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length)
621 return _config_get_list (config, "new", "tags",
623 &(config->new_tags_length), length);
627 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
629 return _config_get_list (config, "new", "ignore",
630 &(config->new_ignore),
631 &(config->new_ignore_length), length);
635 notmuch_config_set_user_other_email (notmuch_config_t *config,
639 _config_set_list (config, "user", "other_email", list, length,
640 &(config->user_other_email));
644 notmuch_config_set_new_tags (notmuch_config_t *config,
648 _config_set_list (config, "new", "tags", list, length,
649 &(config->new_tags));
653 notmuch_config_set_new_ignore (notmuch_config_t *config,
657 _config_set_list (config, "new", "ignore", list, length,
658 &(config->new_ignore));
662 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
664 return _config_get_list (config, "search", "exclude_tags",
665 &(config->search_exclude_tags),
666 &(config->search_exclude_tags_length), length);
670 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
674 _config_set_list (config, "search", "exclude_tags", list, length,
675 &(config->search_exclude_tags));
678 /* Given a configuration item of the form <group>.<key> return the
679 * component group and key. If any error occurs, print a message on
680 * stderr and return 1. Otherwise, return 0.
682 * Note: This function modifies the original 'item' string.
685 _item_split (char *item, char **group, char **key)
691 period = index (item, '.');
692 if (period == NULL || *(period+1) == '\0') {
694 "Invalid configuration name: %s\n"
695 "(Should be of the form <section>.<item>)\n", item);
706 notmuch_config_command_get (void *ctx, char *item)
708 notmuch_config_t *config;
710 config = notmuch_config_open (ctx, NULL, NULL);
714 if (strcmp(item, "database.path") == 0) {
715 printf ("%s\n", notmuch_config_get_database_path (config));
716 } else if (strcmp(item, "user.name") == 0) {
717 printf ("%s\n", notmuch_config_get_user_name (config));
718 } else if (strcmp(item, "user.primary_email") == 0) {
719 printf ("%s\n", notmuch_config_get_user_primary_email (config));
720 } else if (strcmp(item, "user.other_email") == 0) {
721 const char **other_email;
724 other_email = notmuch_config_get_user_other_email (config, &length);
725 for (i = 0; i < length; i++)
726 printf ("%s\n", other_email[i]);
727 } else if (strcmp(item, "new.tags") == 0) {
731 tags = notmuch_config_get_new_tags (config, &length);
732 for (i = 0; i < length; i++)
733 printf ("%s\n", tags[i]);
739 if (_item_split (item, &group, &key))
742 value = g_key_file_get_string_list (config->key_file,
746 fprintf (stderr, "Unknown configuration item: %s.%s\n",
751 for (i = 0; i < length; i++)
752 printf ("%s\n", value[i]);
757 notmuch_config_close (config);
763 notmuch_config_command_set (void *ctx, char *item, int argc, char *argv[])
765 notmuch_config_t *config;
769 if (_item_split (item, &group, &key))
772 config = notmuch_config_open (ctx, NULL, NULL);
776 /* With only the name of an item, we clear it from the
777 * configuration file.
779 * With a single value, we set it as a string.
781 * With multiple values, we set them as a string list.
785 g_key_file_remove_key (config->key_file, group, key, NULL);
788 g_key_file_set_string (config->key_file, group, key, argv[0]);
791 g_key_file_set_string_list (config->key_file, group, key,
792 (const gchar **) argv, argc);
796 ret = notmuch_config_save (config);
797 notmuch_config_close (config);
803 notmuch_config_command_list (void *ctx)
805 notmuch_config_t *config;
807 size_t g, groups_length;
809 config = notmuch_config_open (ctx, NULL, NULL);
813 groups = g_key_file_get_groups (config->key_file, &groups_length);
817 for (g = 0; g < groups_length; g++) {
819 size_t k, keys_length;
821 keys = g_key_file_get_keys (config->key_file,
822 groups[g], &keys_length, NULL);
826 for (k = 0; k < keys_length; k++) {
829 value = g_key_file_get_string (config->key_file,
830 groups[g], keys[k], NULL);
832 printf ("%s.%s=%s\n", groups[g], keys[k], value);
842 notmuch_config_close (config);
848 notmuch_config_command (void *ctx, int argc, char *argv[])
850 argc--; argv++; /* skip subcommand argument */
853 fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
857 if (strcmp (argv[0], "get") == 0) {
859 fprintf (stderr, "Error: notmuch config get requires exactly "
863 return notmuch_config_command_get (ctx, argv[1]);
864 } else if (strcmp (argv[0], "set") == 0) {
866 fprintf (stderr, "Error: notmuch config set requires at least "
870 return notmuch_config_command_set (ctx, argv[1], argc - 2, argv + 2);
871 } else if (strcmp (argv[0], "list") == 0) {
872 return notmuch_config_command_list (ctx);
875 fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
881 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
883 return config->maildir_synchronize_flags;
887 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
888 notmuch_bool_t synchronize_flags)
890 g_key_file_set_boolean (config->key_file,
891 "maildir", "synchronize_flags", synchronize_flags);
892 config->maildir_synchronize_flags = synchronize_flags;