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\n"
53 "\t names will be ignored, independent of its depth/location\n"
54 "\t in the mail store.\n";
56 static const char user_config_comment[] =
57 " User configuration\n"
59 " Here is where you can let notmuch know how you would like to be\n"
60 " addressed. Valid settings are\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"
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";
72 static const char maildir_config_comment[] =
73 " Maildir compatibility configuration\n"
75 " The following option is supported here:\n"
77 "\tsynchronize_flags Valid values are true and false.\n"
79 "\tIf true, then the following maildir flags (in message filenames)\n"
80 "\twill be synchronized with the corresponding notmuch tags:\n"
88 "\t\tS unread (added when 'S' flag is not present)\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";
94 static const char search_config_comment[] =
95 " Search configuration\n"
97 " The following option is supported here:\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";
104 struct _notmuch_config {
107 notmuch_bool_t is_new;
111 char *user_primary_email;
112 const char **user_other_email;
113 size_t user_other_email_length;
114 const char **new_tags;
115 size_t new_tags_length;
116 const char **new_ignore;
117 size_t new_ignore_length;
118 notmuch_bool_t maildir_synchronize_flags;
119 const char **search_exclude_tags;
120 size_t search_exclude_tags_length;
124 notmuch_config_destructor (notmuch_config_t *config)
126 if (config->key_file)
127 g_key_file_free (config->key_file);
133 get_name_from_passwd_file (void *ctx)
137 struct passwd passwd, *ignored;
141 pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
142 if (pw_buf_size == -1) pw_buf_size = 64;
143 pw_buf = talloc_size (ctx, pw_buf_size);
145 while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
146 pw_buf_size, &ignored)) == ERANGE) {
147 pw_buf_size = pw_buf_size * 2;
148 pw_buf = talloc_zero_size(ctx, pw_buf_size);
152 char *comma = strchr (passwd.pw_gecos, ',');
154 name = talloc_strndup (ctx, passwd.pw_gecos,
155 comma - passwd.pw_gecos);
157 name = talloc_strdup (ctx, passwd.pw_gecos);
159 name = talloc_strdup (ctx, "");
162 talloc_free (pw_buf);
168 get_username_from_passwd_file (void *ctx)
172 struct passwd passwd, *ignored;
176 pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
177 if (pw_buf_size == -1) pw_buf_size = 64;
178 pw_buf = talloc_zero_size (ctx, pw_buf_size);
180 while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
181 pw_buf_size, &ignored)) == ERANGE) {
182 pw_buf_size = pw_buf_size * 2;
183 pw_buf = talloc_zero_size(ctx, pw_buf_size);
187 name = talloc_strdup (ctx, passwd.pw_name);
189 name = talloc_strdup (ctx, "");
191 talloc_free (pw_buf);
196 /* Open the named notmuch configuration file. If the filename is NULL,
197 * the value of the environment variable $NOTMUCH_CONFIG will be used.
198 * If $NOTMUCH_CONFIG is unset, the default configuration file
199 * ($HOME/.notmuch-config) will be used.
201 * If any error occurs, (out of memory, or a permission-denied error,
202 * etc.), this function will print a message to stderr and return
205 * FILE NOT FOUND: When the specified configuration file (whether from
206 * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
207 * exist, the behavior of this function depends on the 'is_new_ret'
210 * If is_new_ret is NULL, then a "file not found" message will be
211 * printed to stderr and NULL will be returned.
213 * If is_new_ret is non-NULL then a default configuration will be
214 * returned and *is_new_ret will be set to 1 on return so that
215 * the caller can recognize this case.
217 * These default configuration settings are determined as
220 * database_path: $HOME/mail
222 * user_name: From /etc/passwd
224 * user_primary_mail: $EMAIL variable if set, otherwise
225 * constructed from the username and
226 * hostname of the current machine.
228 * user_other_email: Not set.
230 * The default configuration also contains comments to guide the
231 * user in editing the file directly.
234 notmuch_config_open (void *ctx,
235 const char *filename,
236 notmuch_bool_t *is_new_ret)
238 GError *error = NULL;
241 char *notmuch_config_env = NULL;
242 int file_had_database_group;
243 int file_had_new_group;
244 int file_had_user_group;
245 int file_had_maildir_group;
246 int file_had_search_group;
251 notmuch_config_t *config = talloc (ctx, notmuch_config_t);
252 if (config == NULL) {
253 fprintf (stderr, "Out of memory.\n");
257 talloc_set_destructor (config, notmuch_config_destructor);
260 config->filename = talloc_strdup (config, filename);
261 } else if ((notmuch_config_env = getenv ("NOTMUCH_CONFIG"))) {
262 config->filename = talloc_strdup (config, notmuch_config_env);
264 config->filename = talloc_asprintf (config, "%s/.notmuch-config",
268 config->key_file = g_key_file_new ();
270 config->is_new = FALSE;
271 config->database_path = NULL;
272 config->user_name = NULL;
273 config->user_primary_email = NULL;
274 config->user_other_email = NULL;
275 config->user_other_email_length = 0;
276 config->new_tags = NULL;
277 config->new_tags_length = 0;
278 config->new_ignore = NULL;
279 config->new_ignore_length = 0;
280 config->maildir_synchronize_flags = TRUE;
281 config->search_exclude_tags = NULL;
282 config->search_exclude_tags_length = 0;
284 if (! g_key_file_load_from_file (config->key_file,
286 G_KEY_FILE_KEEP_COMMENTS,
289 /* If the caller passed a non-NULL value for is_new_ret, then
290 * the caller is prepared for a default configuration file in
291 * the case of FILE NOT FOUND. Otherwise, any read failure is
295 error->domain == G_FILE_ERROR &&
296 error->code == G_FILE_ERROR_NOENT)
298 g_error_free (error);
303 fprintf (stderr, "Error reading configuration file %s: %s\n",
304 config->filename, error->message);
305 talloc_free (config);
306 g_error_free (error);
311 /* Whenever we know of configuration sections that don't appear in
312 * the configuration file, we add some comments to help the user
313 * understand what can be done.
315 * It would be convenient to just add those comments now, but
316 * apparently g_key_file will clear any comments when keys are
317 * added later that create the groups. So we have to check for the
318 * groups now, but add the comments only after setting all of our
321 file_had_database_group = g_key_file_has_group (config->key_file,
323 file_had_new_group = g_key_file_has_group (config->key_file, "new");
324 file_had_user_group = g_key_file_has_group (config->key_file, "user");
325 file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
326 file_had_search_group = g_key_file_has_group (config->key_file, "search");
329 if (notmuch_config_get_database_path (config) == NULL) {
330 char *path = talloc_asprintf (config, "%s/mail",
332 notmuch_config_set_database_path (config, path);
336 if (notmuch_config_get_user_name (config) == NULL) {
337 char *name = get_name_from_passwd_file (config);
338 notmuch_config_set_user_name (config, name);
342 if (notmuch_config_get_user_primary_email (config) == NULL) {
343 char *email = getenv ("EMAIL");
345 notmuch_config_set_user_primary_email (config, email);
348 struct hostent *hostent;
349 const char *domainname;
351 char *username = get_username_from_passwd_file (config);
353 gethostname (hostname, 256);
354 hostname[255] = '\0';
356 hostent = gethostbyname (hostname);
357 if (hostent && (domainname = strchr (hostent->h_name, '.')))
360 domainname = "(none)";
362 email = talloc_asprintf (config, "%s@%s.%s",
363 username, hostname, domainname);
365 notmuch_config_set_user_primary_email (config, email);
367 talloc_free (username);
372 if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
373 const char *tags[] = { "unread", "inbox" };
374 notmuch_config_set_new_tags (config, tags, 2);
377 if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
378 notmuch_config_set_new_ignore (config, NULL, 0);
381 if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
383 const char *tags[] = { "deleted", "spam" };
384 notmuch_config_set_search_exclude_tags (config, tags, 2);
386 notmuch_config_set_search_exclude_tags (config, NULL, 0);
391 config->maildir_synchronize_flags =
392 g_key_file_get_boolean (config->key_file,
393 "maildir", "synchronize_flags", &error);
395 notmuch_config_set_maildir_synchronize_flags (config, TRUE);
396 g_error_free (error);
399 /* Whenever we know of configuration sections that don't appear in
400 * the configuration file, we add some comments to help the user
401 * understand what can be done. */
404 g_key_file_set_comment (config->key_file, NULL, NULL,
405 toplevel_config_comment, NULL);
408 if (! file_had_database_group)
410 g_key_file_set_comment (config->key_file, "database", NULL,
411 database_config_comment, NULL);
414 if (! file_had_new_group)
416 g_key_file_set_comment (config->key_file, "new", NULL,
417 new_config_comment, NULL);
420 if (! file_had_user_group)
422 g_key_file_set_comment (config->key_file, "user", NULL,
423 user_config_comment, NULL);
426 if (! file_had_maildir_group)
428 g_key_file_set_comment (config->key_file, "maildir", NULL,
429 maildir_config_comment, NULL);
432 if (! file_had_search_group) {
433 g_key_file_set_comment (config->key_file, "search", NULL,
434 search_config_comment, NULL);
438 *is_new_ret = is_new;
440 config->is_new = is_new;
445 /* Close the given notmuch_config_t object, freeing all resources.
447 * Note: Any changes made to the configuration are *not* saved by this
448 * function. To save changes, call notmuch_config_save before
449 * notmuch_config_close.
452 notmuch_config_close (notmuch_config_t *config)
454 talloc_free (config);
457 /* Save any changes made to the notmuch configuration.
459 * Any comments originally in the file will be preserved.
461 * Returns 0 if successful, and 1 in case of any error, (after
462 * printing a description of the error to stderr).
465 notmuch_config_save (notmuch_config_t *config)
469 GError *error = NULL;
471 data = g_key_file_to_data (config->key_file, &length, NULL);
473 fprintf (stderr, "Out of memory.\n");
477 if (! g_file_set_contents (config->filename, data, length, &error)) {
478 fprintf (stderr, "Error saving configuration to %s: %s\n",
479 config->filename, error->message);
480 g_error_free (error);
490 notmuch_config_is_new (notmuch_config_t *config)
492 return config->is_new;
497 _config_get_list (notmuch_config_t *config,
498 const char *section, const char *key,
499 const char ***outlist, size_t *list_length, size_t *ret_length)
503 if (*outlist == NULL) {
505 char **inlist = g_key_file_get_string_list (config->key_file,
506 section, key, list_length, NULL);
510 *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
512 for (i = 0; i < *list_length; i++)
513 (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
515 (*outlist)[i] = NULL;
522 *ret_length = *list_length;
528 _config_set_list (notmuch_config_t *config,
529 const char *group, const char *name,
531 size_t length, const char ***config_var )
533 g_key_file_set_string_list (config->key_file, group, name, list, length);
534 talloc_free (*config_var);
539 notmuch_config_get_database_path (notmuch_config_t *config)
543 if (config->database_path == NULL) {
544 path = g_key_file_get_string (config->key_file,
545 "database", "path", NULL);
547 config->database_path = talloc_strdup (config, path);
552 return config->database_path;
556 notmuch_config_set_database_path (notmuch_config_t *config,
557 const char *database_path)
559 g_key_file_set_string (config->key_file,
560 "database", "path", database_path);
562 talloc_free (config->database_path);
563 config->database_path = NULL;
567 notmuch_config_get_user_name (notmuch_config_t *config)
571 if (config->user_name == NULL) {
572 name = g_key_file_get_string (config->key_file,
573 "user", "name", NULL);
575 config->user_name = talloc_strdup (config, name);
580 return config->user_name;
584 notmuch_config_set_user_name (notmuch_config_t *config,
585 const char *user_name)
587 g_key_file_set_string (config->key_file,
588 "user", "name", user_name);
590 talloc_free (config->user_name);
591 config->user_name = NULL;
595 notmuch_config_get_user_primary_email (notmuch_config_t *config)
599 if (config->user_primary_email == NULL) {
600 email = g_key_file_get_string (config->key_file,
601 "user", "primary_email", NULL);
603 config->user_primary_email = talloc_strdup (config, email);
608 return config->user_primary_email;
612 notmuch_config_set_user_primary_email (notmuch_config_t *config,
613 const char *primary_email)
615 g_key_file_set_string (config->key_file,
616 "user", "primary_email", primary_email);
618 talloc_free (config->user_primary_email);
619 config->user_primary_email = NULL;
623 notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length)
625 return _config_get_list (config, "user", "other_email",
626 &(config->user_other_email),
627 &(config->user_other_email_length), length);
631 notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length)
633 return _config_get_list (config, "new", "tags",
635 &(config->new_tags_length), length);
639 notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
641 return _config_get_list (config, "new", "ignore",
642 &(config->new_ignore),
643 &(config->new_ignore_length), length);
647 notmuch_config_set_user_other_email (notmuch_config_t *config,
651 _config_set_list (config, "user", "other_email", list, length,
652 &(config->user_other_email));
656 notmuch_config_set_new_tags (notmuch_config_t *config,
660 _config_set_list (config, "new", "tags", list, length,
661 &(config->new_tags));
665 notmuch_config_set_new_ignore (notmuch_config_t *config,
669 _config_set_list (config, "new", "ignore", list, length,
670 &(config->new_ignore));
674 notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
676 return _config_get_list (config, "search", "exclude_tags",
677 &(config->search_exclude_tags),
678 &(config->search_exclude_tags_length), length);
682 notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
686 _config_set_list (config, "search", "exclude_tags", list, length,
687 &(config->search_exclude_tags));
690 /* Given a configuration item of the form <group>.<key> return the
691 * component group and key. If any error occurs, print a message on
692 * stderr and return 1. Otherwise, return 0.
694 * Note: This function modifies the original 'item' string.
697 _item_split (char *item, char **group, char **key)
703 period = index (item, '.');
704 if (period == NULL || *(period+1) == '\0') {
706 "Invalid configuration name: %s\n"
707 "(Should be of the form <section>.<item>)\n", item);
718 notmuch_config_command_get (void *ctx, char *item)
720 notmuch_config_t *config;
722 config = notmuch_config_open (ctx, NULL, NULL);
726 if (strcmp(item, "database.path") == 0) {
727 printf ("%s\n", notmuch_config_get_database_path (config));
728 } else if (strcmp(item, "user.name") == 0) {
729 printf ("%s\n", notmuch_config_get_user_name (config));
730 } else if (strcmp(item, "user.primary_email") == 0) {
731 printf ("%s\n", notmuch_config_get_user_primary_email (config));
732 } else if (strcmp(item, "user.other_email") == 0) {
733 const char **other_email;
736 other_email = notmuch_config_get_user_other_email (config, &length);
737 for (i = 0; i < length; i++)
738 printf ("%s\n", other_email[i]);
739 } else if (strcmp(item, "new.tags") == 0) {
743 tags = notmuch_config_get_new_tags (config, &length);
744 for (i = 0; i < length; i++)
745 printf ("%s\n", tags[i]);
751 if (_item_split (item, &group, &key))
754 value = g_key_file_get_string_list (config->key_file,
758 fprintf (stderr, "Unknown configuration item: %s.%s\n",
763 for (i = 0; i < length; i++)
764 printf ("%s\n", value[i]);
769 notmuch_config_close (config);
775 notmuch_config_command_set (void *ctx, char *item, int argc, char *argv[])
777 notmuch_config_t *config;
781 if (_item_split (item, &group, &key))
784 config = notmuch_config_open (ctx, NULL, NULL);
788 /* With only the name of an item, we clear it from the
789 * configuration file.
791 * With a single value, we set it as a string.
793 * With multiple values, we set them as a string list.
797 g_key_file_remove_key (config->key_file, group, key, NULL);
800 g_key_file_set_string (config->key_file, group, key, argv[0]);
803 g_key_file_set_string_list (config->key_file, group, key,
804 (const gchar **) argv, argc);
808 ret = notmuch_config_save (config);
809 notmuch_config_close (config);
815 notmuch_config_command_list (void *ctx)
817 notmuch_config_t *config;
819 size_t g, groups_length;
821 config = notmuch_config_open (ctx, NULL, NULL);
825 groups = g_key_file_get_groups (config->key_file, &groups_length);
829 for (g = 0; g < groups_length; g++) {
831 size_t k, keys_length;
833 keys = g_key_file_get_keys (config->key_file,
834 groups[g], &keys_length, NULL);
838 for (k = 0; k < keys_length; k++) {
841 value = g_key_file_get_string (config->key_file,
842 groups[g], keys[k], NULL);
844 printf ("%s.%s=%s\n", groups[g], keys[k], value);
854 notmuch_config_close (config);
860 notmuch_config_command (void *ctx, int argc, char *argv[])
862 argc--; argv++; /* skip subcommand argument */
865 fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
869 if (strcmp (argv[0], "get") == 0) {
871 fprintf (stderr, "Error: notmuch config get requires exactly "
875 return notmuch_config_command_get (ctx, argv[1]);
876 } else if (strcmp (argv[0], "set") == 0) {
878 fprintf (stderr, "Error: notmuch config set requires at least "
882 return notmuch_config_command_set (ctx, argv[1], argc - 2, argv + 2);
883 } else if (strcmp (argv[0], "list") == 0) {
884 return notmuch_config_command_list (ctx);
887 fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
893 notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
895 return config->maildir_synchronize_flags;
899 notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
900 notmuch_bool_t synchronize_flags)
902 g_key_file_set_boolean (config->key_file,
903 "maildir", "synchronize_flags", synchronize_flags);
904 config->maildir_synchronize_flags = synchronize_flags;