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