notmuch: Add a configuration system.
[notmuch] / notmuch-config.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2009 Carl Worth
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ .
17  *
18  * Author: Carl Worth <cworth@cworth.org>
19  */
20
21 #include "notmuch-client.h"
22
23 #include <pwd.h>
24 #include <netdb.h>
25
26 struct _notmuch_config {
27     char *filename;
28     GKeyFile *key_file;
29
30     char *database_path;
31     char *user_name;
32     char *user_primary_email;
33     char **user_other_email;
34     size_t user_other_email_length;
35 };
36
37 static int
38 notmuch_config_destructor (notmuch_config_t *config)
39 {
40     if (config->key_file)
41         g_key_file_free (config->key_file);
42
43     return 0;
44 }
45
46 static char *
47 get_name_from_passwd_file (void *ctx)
48 {
49     long pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
50     char *pw_buf = talloc_zero_size (ctx, pw_buf_size);
51     struct passwd passwd, *ignored;
52     char *name;
53
54     if (getpwuid_r (getuid (), &passwd, pw_buf, pw_buf_size, &ignored) == 0) {
55         char *comma = strchr (passwd.pw_gecos, ',');
56         if (comma)
57             name = talloc_strndup (ctx, passwd.pw_gecos,
58                                    comma - passwd.pw_gecos);
59         else
60             name = talloc_strdup (ctx, passwd.pw_gecos);
61     } else {
62         name = talloc_strdup (ctx, "");
63     }
64
65     talloc_free (pw_buf);
66
67     return name;
68 }
69
70 static char *
71 get_username_from_passwd_file (void *ctx)
72 {
73     long pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
74     char *pw_buf = talloc_zero_size (ctx, pw_buf_size);
75     struct passwd passwd, *ignored;
76     char *name;
77
78     if (getpwuid_r (getuid (), &passwd, pw_buf, pw_buf_size, &ignored) == 0)
79         name = talloc_strdup (ctx, passwd.pw_name);
80     else
81         name = talloc_strdup (ctx, "");
82
83     talloc_free (pw_buf);
84
85     return name;
86 }
87
88 /* Open the named notmuch configuration file. A filename of NULL will
89  * be interpreted as the default configuration file
90  * ($HOME/.notmuch-config).
91  *
92  * If any error occurs, (out of memory, or a permission-denied error,
93  * etc.), this function will print a message to stderr and return
94  * NULL.
95  *
96  * Note: It is *not* an error if the specified configuration file does
97  * not exist. In this case, a default configuration will be created
98  * and returned. Subsequently calling notmuch_config_save will cause
99  * the configuration to be written to the filename specified at the
100  * time of notmuch_config_open.
101  *
102  * The default configuration settings are determined as follows:
103  *
104  *      database_path:          $HOME/mail
105  *
106  *      user_name:              From /etc/passwd
107  *
108  *      user_primary_mail:      $EMAIL variable if set, otherwise
109  *                              constructed from the username and
110  *                              hostname of the current machine.
111  *
112  *      user_other_email:       Not set.
113  *
114  * The default configuration also contains comments to guide the user
115  * in editing the file directly.
116  */
117 notmuch_config_t *
118 notmuch_config_open (void *ctx, const char *filename)
119 {
120     GError *error = NULL;
121
122     notmuch_config_t *config = talloc (ctx, notmuch_config_t);
123     if (config == NULL) {
124         fprintf (stderr, "Out of memory.\n");
125         return NULL;
126     }
127     
128     talloc_set_destructor (config, notmuch_config_destructor);
129
130     if (filename)
131         config->filename = talloc_strdup (config, filename);
132     else
133         config->filename = talloc_asprintf (config, "%s/.notmuch-config",
134                                             getenv ("HOME"));
135
136     config->key_file = g_key_file_new ();
137
138     config->database_path = NULL;
139     config->user_name = NULL;
140     config->user_primary_email = NULL;
141     config->user_other_email = NULL;
142     config->user_other_email_length = 0;
143
144     if (! g_key_file_load_from_file (config->key_file,
145                                      config->filename,
146                                      G_KEY_FILE_KEEP_COMMENTS,
147                                      &error))
148     {
149         /* We are capable of dealing with a non-existent configuration
150          * file, so be silent about that. */
151         if (!(error->domain == G_FILE_ERROR &&
152               error->code == G_FILE_ERROR_NOENT))
153         {
154             fprintf (stderr, "Error reading configuration file %s: %s\n",
155                      config->filename, error->message);
156             talloc_free (config);
157             return NULL;
158         }
159     }
160
161     if (notmuch_config_get_database_path (config) == NULL) {
162         char *path = talloc_asprintf (config, "%s/mail",
163                                       getenv ("HOME"));
164         notmuch_config_set_database_path (config, path);
165         talloc_free (path);
166     }
167
168     if (notmuch_config_get_user_name (config) == NULL) {
169         char *name = get_name_from_passwd_file (config);
170         notmuch_config_set_user_name (config, name);
171         talloc_free (name);
172     }
173
174     if (notmuch_config_get_user_primary_email (config) == NULL) {
175         char *email = getenv ("EMAIL");
176         if (email) {
177             notmuch_config_set_user_primary_email (config, email);
178         } else {
179             char hostname[256];
180             struct hostent *hostent;
181             const char *domainname;
182
183             char *username = get_username_from_passwd_file (config);
184
185             gethostname (hostname, 256);
186             hostname[255] = '\0';
187
188             hostent = gethostbyname (hostname);
189             if (hostent && (domainname = strchr (hostent->h_name, '.')))
190                 domainname += 1;
191             else
192                 domainname = "(none)";
193
194             email = talloc_asprintf (config, "%s@%s.%s",
195                                      username, hostname, domainname);
196
197             notmuch_config_set_user_primary_email (config, email);
198
199             talloc_free (username);
200             talloc_free (email);
201         }
202     }
203
204     return config;
205 }
206
207 /* Close the given notmuch_config_t object, freeing all resources.
208  * 
209  * Note: Any changes made to the configuration are *not* saved by this
210  * function. To save changes, call notmuch_config_save before
211  * notmuch_config_close.
212 */
213 void
214 notmuch_config_close (notmuch_config_t *config)
215 {
216     talloc_free (config);
217 }
218
219 /* Save any changes made to the notmuch configuration.
220  *
221  * Any comments originally in the file will be preserved.
222  *
223  * Returns 0 if successful, and 1 in case of any error, (after
224  * printing a description of the error to stderr).
225  */
226 int
227 notmuch_config_save (notmuch_config_t *config)
228 {
229     size_t length;
230     char *data;
231     GError *error = NULL;
232
233     data = g_key_file_to_data (config->key_file, &length, NULL);
234     if (data == NULL) {
235         fprintf (stderr, "Out of memory.\n");
236         return 1;
237     }
238
239     if (! g_file_set_contents (config->filename, data, length, &error)) {
240         fprintf (stderr, "Error saving configuration to %s: %s\n",
241                  config->filename, error->message);
242         return 1;
243     }
244
245     return 0;
246 }
247
248 const char *
249 notmuch_config_get_database_path (notmuch_config_t *config)
250 {
251     char *path;
252
253     if (config->database_path == NULL) {
254         path = g_key_file_get_string (config->key_file,
255                                       "database", "path", NULL);
256         if (path) {
257             config->database_path = talloc_strdup (config, path);
258             free (path);
259         }
260     }
261
262     return config->database_path;
263 }
264
265 void
266 notmuch_config_set_database_path (notmuch_config_t *config,
267                                   const char *database_path)
268 {
269     g_key_file_set_string (config->key_file,
270                            "database", "path", database_path);
271
272     talloc_free (config->database_path);
273     config->database_path = NULL;
274 }
275
276 const char *
277 notmuch_config_get_user_name (notmuch_config_t *config)
278 {
279     char *name;
280
281     if (config->user_name == NULL) {
282         name = g_key_file_get_string (config->key_file,
283                                       "user", "name", NULL);
284         if (name) {
285             config->user_name = talloc_strdup (config, name);
286             free (name);
287         }
288     }
289
290     return config->user_name;
291 }
292
293 void
294 notmuch_config_set_user_name (notmuch_config_t *config,
295                               const char *user_name)
296 {
297     g_key_file_set_string (config->key_file,
298                            "user", "name", user_name);
299
300     talloc_free (config->user_name);
301     config->user_name = NULL;
302 }
303
304 const char *
305 notmuch_config_get_user_primary_email (notmuch_config_t *config)
306 {
307     char *email;
308
309     if (config->user_primary_email == NULL) {
310         email = g_key_file_get_string (config->key_file,
311                                        "user", "primary_email", NULL);
312         if (email) {
313             config->user_primary_email = talloc_strdup (config, email);
314             free (email);
315         }
316     }
317
318     return config->user_primary_email;
319 }
320
321 void
322 notmuch_config_set_user_primary_email (notmuch_config_t *config,
323                                        const char *primary_email)
324 {
325     g_key_file_set_string (config->key_file,
326                            "user", "primary_email", primary_email);
327
328     talloc_free (config->user_primary_email);
329     config->user_primary_email = NULL;
330 }
331
332 char **
333 notmuch_config_get_user_other_email (notmuch_config_t *config,
334                                      size_t *length)
335 {
336     char **emails;
337     size_t emails_length;
338     unsigned int i;
339
340     if (config->user_other_email == NULL) {
341         emails = g_key_file_get_string_list (config->key_file,
342                                              "user", "other_email",
343                                              &emails_length, NULL);
344         if (emails) {
345             config->user_other_email = talloc_size (config,
346                                                     sizeof (char *) *
347                                                     (emails_length + 1));
348             for (i = 0; i < emails_length; i++)
349                 config->user_other_email[i] = talloc_strdup (config->user_other_email,
350                                                              emails[i]);
351             config->user_other_email[i] = NULL;
352
353             g_strfreev (emails);
354
355             config->user_other_email_length = emails_length;
356         }
357     }
358
359     *length = config->user_other_email_length;
360     return config->user_other_email;
361 }
362
363 void
364 notmuch_config_set_user_other_email (notmuch_config_t *config,
365                                      const char *other_email[],
366                                      size_t length)
367 {
368     g_key_file_set_string_list (config->key_file,
369                                 "user", "other_email",
370                                 other_email, length);
371
372     talloc_free (config->user_other_email);
373     config->user_other_email = NULL;
374 }