]> git.cworth.org Git - notmuch/blob - lib/config.cc
lib: provide notmuch_database_load_config
[notmuch] / lib / config.cc
1 /* config.cc - API for database metadata
2  *
3  * Copyright © 2016 David Bremner
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: David Bremner <david@tethera.net>
19  */
20
21 #include "notmuch.h"
22 #include "notmuch-private.h"
23 #include "database-private.h"
24
25 static const std::string CONFIG_PREFIX = "C";
26
27 struct _notmuch_config_list {
28     notmuch_database_t *notmuch;
29     Xapian::TermIterator iterator;
30     char *current_key;
31     char *current_val;
32 };
33
34 struct _notmuch_config_values {
35     const char *iterator;
36     size_t tok_len;
37     const char *string;
38     void *children; /* talloc_context */
39 };
40
41 static const char *_notmuch_config_key_to_string (notmuch_config_key_t key);
42
43 static int
44 _notmuch_config_list_destroy (notmuch_config_list_t *list)
45 {
46     /* invoke destructor w/o deallocating memory */
47     list->iterator.~TermIterator();
48     return 0;
49 }
50
51 notmuch_status_t
52 notmuch_database_set_config (notmuch_database_t *notmuch,
53                              const char *key,
54                              const char *value)
55 {
56     notmuch_status_t status;
57
58     status = _notmuch_database_ensure_writable (notmuch);
59     if (status)
60         return status;
61
62     if (! notmuch->config) {
63         if ((status = _notmuch_config_load_from_database (notmuch)))
64             return status;
65     }
66
67     try {
68         notmuch->writable_xapian_db->set_metadata (CONFIG_PREFIX + key, value);
69     } catch (const Xapian::Error &error) {
70         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
71         notmuch->exception_reported = true;
72         _notmuch_database_log (notmuch, "Error: A Xapian exception occurred setting metadata: %s\n",
73                                error.get_msg ().c_str ());
74     }
75
76     if (status)
77         return status;
78
79     _notmuch_string_map_set (notmuch->config, key, value);
80
81     return NOTMUCH_STATUS_SUCCESS;
82 }
83
84 static notmuch_status_t
85 _metadata_value (notmuch_database_t *notmuch,
86                  const char *key,
87                  std::string &value)
88 {
89     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
90
91     try {
92         value = notmuch->xapian_db->get_metadata (CONFIG_PREFIX + key);
93     } catch (const Xapian::Error &error) {
94         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
95         notmuch->exception_reported = true;
96         _notmuch_database_log (notmuch, "Error: A Xapian exception occurred getting metadata: %s\n",
97                                error.get_msg ().c_str ());
98     }
99     return status;
100 }
101
102 notmuch_status_t
103 notmuch_database_get_config (notmuch_database_t *notmuch,
104                              const char *key,
105                              char **value)
106 {
107     const char *stored_val;
108     notmuch_status_t status;
109
110     if (! notmuch->config) {
111         if ((status = _notmuch_config_load_from_database (notmuch)))
112             return status;
113     }
114
115     if (! value)
116         return NOTMUCH_STATUS_NULL_POINTER;
117
118     stored_val = _notmuch_string_map_get (notmuch->config, key);
119     if (! stored_val) {
120         /* XXX in principle this API should be fixed so empty string
121          * is distinguished from not found */
122         *value = strdup ("");
123     } else {
124         *value = strdup (stored_val);
125     }
126
127     return NOTMUCH_STATUS_SUCCESS;
128 }
129
130 notmuch_status_t
131 notmuch_database_get_config_list (notmuch_database_t *notmuch,
132                                   const char *prefix,
133                                   notmuch_config_list_t **out)
134 {
135     notmuch_config_list_t *list = NULL;
136     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
137
138     list = talloc (notmuch, notmuch_config_list_t);
139     if (! list) {
140         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
141         goto DONE;
142     }
143
144     list->notmuch = notmuch;
145     list->current_key = NULL;
146     list->current_val = NULL;
147
148     try {
149
150         new(&(list->iterator)) Xapian::TermIterator (notmuch->xapian_db->metadata_keys_begin
151                                                          (CONFIG_PREFIX + (prefix ? prefix : "")));
152         talloc_set_destructor (list, _notmuch_config_list_destroy);
153
154     } catch (const Xapian::Error &error) {
155         _notmuch_database_log (notmuch,
156                                "A Xapian exception occurred getting metadata iterator: %s.\n",
157                                error.get_msg ().c_str ());
158         notmuch->exception_reported = true;
159         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
160     }
161
162     *out = list;
163
164   DONE:
165     if (status) {
166         if (list) {
167             talloc_free (list);
168             if (status != NOTMUCH_STATUS_XAPIAN_EXCEPTION)
169                 _notmuch_config_list_destroy (list);
170         }
171     } else {
172         talloc_set_destructor (list, _notmuch_config_list_destroy);
173     }
174
175     return status;
176 }
177
178 notmuch_bool_t
179 notmuch_config_list_valid (notmuch_config_list_t *metadata)
180 {
181     if (metadata->iterator == metadata->notmuch->xapian_db->metadata_keys_end ())
182         return false;
183
184     return true;
185 }
186
187 static inline char *
188 _key_from_iterator (notmuch_config_list_t *list)
189 {
190     return talloc_strdup (list, (*list->iterator).c_str () + CONFIG_PREFIX.length ());
191 }
192
193 const char *
194 notmuch_config_list_key (notmuch_config_list_t *list)
195 {
196     if (list->current_key)
197         talloc_free (list->current_key);
198
199     list->current_key = _key_from_iterator (list);
200
201     return list->current_key;
202 }
203
204 const char *
205 notmuch_config_list_value (notmuch_config_list_t *list)
206 {
207     std::string strval;
208     notmuch_status_t status;
209     char *key = _key_from_iterator (list);
210
211     /* TODO: better error reporting?? */
212     status = _metadata_value (list->notmuch, key, strval);
213     if (status)
214         return NULL;
215
216     if (list->current_val)
217         talloc_free (list->current_val);
218
219     list->current_val = talloc_strdup (list, strval.c_str ());
220     talloc_free (key);
221     return list->current_val;
222 }
223
224 void
225 notmuch_config_list_move_to_next (notmuch_config_list_t *list)
226 {
227     list->iterator++;
228 }
229
230 void
231 notmuch_config_list_destroy (notmuch_config_list_t *list)
232 {
233     talloc_free (list);
234 }
235
236 notmuch_status_t
237 _notmuch_config_load_from_database (notmuch_database_t *notmuch)
238 {
239     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
240     notmuch_config_list_t *list;
241
242     if (notmuch->config == NULL)
243         notmuch->config = _notmuch_string_map_create (notmuch);
244
245     if (unlikely (notmuch->config == NULL))
246         return NOTMUCH_STATUS_OUT_OF_MEMORY;
247
248     status = notmuch_database_get_config_list (notmuch, "", &list);
249     if (status)
250         return status;
251
252     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
253         _notmuch_string_map_append (notmuch->config,
254                                     notmuch_config_list_key (list),
255                                     notmuch_config_list_value (list));
256     }
257
258     return status;
259 }
260
261 notmuch_config_values_t *
262 notmuch_config_get_values (notmuch_database_t *notmuch, notmuch_config_key_t key)
263 {
264     notmuch_config_values_t *values = NULL;
265     bool ok = false;
266
267     const char *key_str = _notmuch_config_key_to_string (key);
268
269     if (! key_str)
270         goto DONE;
271
272     values = talloc (notmuch, notmuch_config_values_t);
273     if (unlikely (! values))
274         goto DONE;
275
276     values->children = talloc_new (values);
277
278     values->string = _notmuch_string_map_get (notmuch->config, key_str);
279     if (! values->string)
280         goto DONE;
281
282     values->iterator = strsplit_len (values->string, ';', &(values->tok_len));
283     ok = true;
284
285   DONE:
286     if (! ok) {
287         if (values)
288             talloc_free (values);
289         return NULL;
290     }
291     return values;
292 }
293
294 notmuch_bool_t
295 notmuch_config_values_valid (notmuch_config_values_t *values)
296 {
297     if (! values)
298         return false;
299
300     return (values->iterator != NULL);
301 }
302
303 const char *
304 notmuch_config_values_get (notmuch_config_values_t *values)
305 {
306     return talloc_strndup (values, values->iterator, values->tok_len);
307 }
308
309 void
310 notmuch_config_values_start (notmuch_config_values_t *values)
311 {
312     if (values == NULL)
313         return;
314     if (values->children) {
315         talloc_free (values->children);
316     }
317
318     values->children = talloc_new (values);
319
320     values->iterator = strsplit_len (values->string, ';', &(values->tok_len));
321 }
322
323 void
324 notmuch_config_values_move_to_next (notmuch_config_values_t *values)
325 {
326     values->iterator += values->tok_len;
327     values->iterator = strsplit_len (values->iterator, ';', &(values->tok_len));
328 }
329
330 void
331 notmuch_config_values_destroy (notmuch_config_values_t *values)
332 {
333     talloc_free (values);
334 }
335
336 notmuch_status_t
337 _notmuch_config_load_from_file (notmuch_database_t *notmuch,
338                                 GKeyFile *file)
339 {
340     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
341     gchar **groups = NULL, **keys, *val;
342
343     if (notmuch->config == NULL)
344         notmuch->config = _notmuch_string_map_create (notmuch);
345
346     if (unlikely (notmuch->config == NULL)) {
347         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
348         goto DONE;
349     }
350
351     groups = g_key_file_get_groups (file, NULL);
352     for (gchar **grp = groups; *grp; grp++) {
353         keys = g_key_file_get_keys (file, *grp, NULL, NULL);
354         for (gchar **keys_p = keys; *keys_p; keys_p++) {
355             char *absolute_key = talloc_asprintf (notmuch, "%s.%s", *grp,  *keys_p);
356             val = g_key_file_get_value (file, *grp, *keys_p, NULL);
357             if (! val) {
358                 status = NOTMUCH_STATUS_FILE_ERROR;
359                 goto DONE;
360             }
361             _notmuch_string_map_set (notmuch->config, absolute_key, val);
362             g_free (val);
363             talloc_free (absolute_key);
364             if (status)
365                 goto DONE;
366         }
367         g_strfreev (keys);
368     }
369
370   DONE:
371     if (groups)
372         g_strfreev (groups);
373
374     return status;
375 }
376
377 notmuch_status_t
378 notmuch_config_get_bool (notmuch_database_t *notmuch, notmuch_config_key_t key, notmuch_bool_t *val)
379 {
380     const char *key_string, *val_string;
381
382     key_string = _notmuch_config_key_to_string (key);
383     if (! key_string) {
384         return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
385     }
386
387     val_string = _notmuch_string_map_get (notmuch->config, key_string);
388     if (! val_string) {
389         *val = FALSE;
390         return NOTMUCH_STATUS_SUCCESS;
391     }
392
393     if (strcase_equal (val_string, "false") || strcase_equal (val_string, "no"))
394         *val = FALSE;
395     else if (strcase_equal (val_string, "true") || strcase_equal (val_string, "yes"))
396         *val = TRUE;
397     else
398         return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
399
400     return NOTMUCH_STATUS_SUCCESS;
401 }
402
403 static const char *
404 _notmuch_config_key_to_string (notmuch_config_key_t key)
405 {
406     switch (key) {
407     case NOTMUCH_CONFIG_DATABASE_PATH:
408         return "database.path";
409     case NOTMUCH_CONFIG_MAIL_ROOT:
410         return "database.mail_root";
411     case NOTMUCH_CONFIG_HOOK_DIR:
412         return "database.hook_dir";
413     case NOTMUCH_CONFIG_BACKUP_DIR:
414         return "database.backup_dir";
415     case NOTMUCH_CONFIG_EXCLUDE_TAGS:
416         return "search.exclude_tags";
417     case NOTMUCH_CONFIG_NEW_TAGS:
418         return "new.tags";
419     case NOTMUCH_CONFIG_NEW_IGNORE:
420         return "new.ignore";
421     case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS:
422         return "maildir.synchronize_flags";
423     case NOTMUCH_CONFIG_PRIMARY_EMAIL:
424         return "user.primary_email";
425     case NOTMUCH_CONFIG_OTHER_EMAIL:
426         return "user.other_email";
427     case NOTMUCH_CONFIG_USER_NAME:
428         return "user.name";
429     default:
430         return NULL;
431     }
432 }
433
434 static const char *
435 _notmuch_config_default (notmuch_database_t *notmuch, notmuch_config_key_t key)
436 {
437     char *path;
438
439     switch (key) {
440     case NOTMUCH_CONFIG_DATABASE_PATH:
441         path = getenv ("MAILDIR");
442         if (path)
443             path = talloc_strdup (notmuch, path);
444         else
445             path = talloc_asprintf (notmuch, "%s/mail",
446                                     getenv ("HOME"));
447         return path;
448     case NOTMUCH_CONFIG_MAIL_ROOT:
449         /* by default, mail root is the same as database path */
450         return notmuch_database_get_path (notmuch);
451     case NOTMUCH_CONFIG_EXCLUDE_TAGS:
452         return "";
453     case NOTMUCH_CONFIG_NEW_TAGS:
454         return "inbox;unread";
455     case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS:
456         return "true";
457     case NOTMUCH_CONFIG_HOOK_DIR:
458     case NOTMUCH_CONFIG_BACKUP_DIR:
459     case NOTMUCH_CONFIG_NEW_IGNORE:
460     case NOTMUCH_CONFIG_USER_NAME:
461     case NOTMUCH_CONFIG_PRIMARY_EMAIL:
462     case NOTMUCH_CONFIG_OTHER_EMAIL:
463         return NULL;
464     default:
465     case NOTMUCH_CONFIG_LAST:
466         INTERNAL_ERROR ("illegal key enum %d", key);
467     }
468 }
469
470 notmuch_status_t
471 _notmuch_config_load_defaults (notmuch_database_t *notmuch)
472 {
473     notmuch_config_key_t key;
474
475     for (key = NOTMUCH_CONFIG_FIRST;
476          key < NOTMUCH_CONFIG_LAST;
477          key = notmuch_config_key_t (key + 1)) {
478         const char *val = notmuch_config_get (notmuch, key);
479         const char *key_string = _notmuch_config_key_to_string (key);
480
481         val = _notmuch_string_map_get (notmuch->config, key_string);
482         if (! val) {
483             _notmuch_string_map_set (notmuch->config, key_string, _notmuch_config_default (notmuch,
484                                                                                            key));
485         }
486     }
487     return NOTMUCH_STATUS_SUCCESS;
488 }
489
490 const char *
491 notmuch_config_get (notmuch_database_t *notmuch, notmuch_config_key_t key)
492 {
493
494     return _notmuch_string_map_get (notmuch->config, _notmuch_config_key_to_string (key));
495 }
496
497 notmuch_status_t
498 notmuch_config_set (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val)
499 {
500
501     return notmuch_database_set_config (notmuch, _notmuch_config_key_to_string (key), val);
502 }
503
504 void
505 _notmuch_config_cache (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val)
506 {
507     if (notmuch->config == NULL)
508         notmuch->config = _notmuch_string_map_create (notmuch);
509
510     _notmuch_string_map_set (notmuch->config, _notmuch_config_key_to_string (key), val);
511 }