]> git.notmuchmail.org Git - notmuch/blob - lib/config.cc
Merge tag '0.31.4'
[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, "A Xapian exception occurred getting metadata iterator: %s.\n",
156                                error.get_msg ().c_str ());
157         notmuch->exception_reported = true;
158         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
159     }
160
161     *out = list;
162
163   DONE:
164     if (status) {
165         if (list) {
166             talloc_free (list);
167             if (status != NOTMUCH_STATUS_XAPIAN_EXCEPTION)
168                 _notmuch_config_list_destroy (list);
169         }
170     }  else {
171         talloc_set_destructor (list, _notmuch_config_list_destroy);
172     }
173
174     return status;
175 }
176
177 notmuch_bool_t
178 notmuch_config_list_valid (notmuch_config_list_t *metadata)
179 {
180     if (metadata->iterator == metadata->notmuch->xapian_db->metadata_keys_end ())
181         return false;
182
183     return true;
184 }
185
186 static inline char * _key_from_iterator (notmuch_config_list_t *list) {
187     return talloc_strdup (list, (*list->iterator).c_str () + CONFIG_PREFIX.length ());
188 }
189
190 const char *
191 notmuch_config_list_key (notmuch_config_list_t *list)
192 {
193     if (list->current_key)
194         talloc_free (list->current_key);
195
196     list->current_key = _key_from_iterator (list);
197
198     return list->current_key;
199 }
200
201 const char *
202 notmuch_config_list_value (notmuch_config_list_t *list)
203 {
204     std::string strval;
205     notmuch_status_t status;
206     char *key = _key_from_iterator (list);
207
208     /* TODO: better error reporting?? */
209     status = _metadata_value (list->notmuch, key, strval);
210     if (status)
211         return NULL;
212
213     if (list->current_val)
214         talloc_free (list->current_val);
215
216     list->current_val = talloc_strdup (list, strval.c_str ());
217     talloc_free (key);
218     return list->current_val;
219 }
220
221 void
222 notmuch_config_list_move_to_next (notmuch_config_list_t *list)
223 {
224     list->iterator++;
225 }
226
227 void
228 notmuch_config_list_destroy (notmuch_config_list_t *list)
229 {
230     talloc_free (list);
231 }
232
233 notmuch_status_t
234 _notmuch_config_load_from_database (notmuch_database_t *notmuch)
235 {
236     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
237     notmuch_config_list_t *list;
238
239     if (notmuch->config == NULL)
240         notmuch->config = _notmuch_string_map_create (notmuch);
241
242     if (unlikely(notmuch->config == NULL))
243         return NOTMUCH_STATUS_OUT_OF_MEMORY;
244
245     status = notmuch_database_get_config_list (notmuch, "", &list);
246     if (status)
247         return status;
248
249     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
250         _notmuch_string_map_append (notmuch->config,
251                                     notmuch_config_list_key (list),
252                                     notmuch_config_list_value (list));
253     }
254
255     return status;
256 }
257
258 notmuch_config_values_t *
259 notmuch_config_get_values (notmuch_database_t *notmuch, notmuch_config_key_t key)
260 {
261     notmuch_config_values_t *values = NULL;
262     bool ok = false;
263
264     const char *key_str = _notmuch_config_key_to_string (key);
265
266     if (! key_str)
267         goto DONE;
268
269     values = talloc (notmuch, notmuch_config_values_t);
270     if (unlikely(! values))
271         goto DONE;
272
273     values->children = talloc_new (values);
274
275     values->string = _notmuch_string_map_get (notmuch->config, key_str);
276     if (! values->string)
277         goto DONE;
278
279     values->iterator = strsplit_len (values->string, ';', &(values->tok_len));
280     ok = true;
281
282  DONE:
283     if (!ok) {
284         if (values)
285             talloc_free(values);
286         return NULL;
287     }
288     return values;
289 }
290
291 notmuch_bool_t
292 notmuch_config_values_valid (notmuch_config_values_t *values) {
293     if (! values)
294         return false;
295
296     return (values->iterator != NULL);
297 }
298
299 const char *
300 notmuch_config_values_get (notmuch_config_values_t *values) {
301     return talloc_strndup (values, values->iterator, values->tok_len);
302 }
303
304 void
305 notmuch_config_values_start (notmuch_config_values_t *values) {
306     if (values == NULL)
307         return;
308     if (values->children) {
309         talloc_free (values->children);
310     }
311
312     values->children = talloc_new (values);
313
314     values->iterator = strsplit_len (values->string, ';', &(values->tok_len));
315 }
316
317 void
318 notmuch_config_values_move_to_next (notmuch_config_values_t *values) {
319     values->iterator += values->tok_len;
320     values->iterator = strsplit_len (values->iterator, ';', &(values->tok_len));
321 }
322
323 void
324 notmuch_config_values_destroy (notmuch_config_values_t *values) {
325     talloc_free (values);
326 }
327
328 notmuch_status_t
329 _notmuch_config_load_from_file (notmuch_database_t *notmuch,
330                                 GKeyFile *file)
331 {
332     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
333     gchar **groups, **keys, *val;
334
335     if (notmuch->config == NULL)
336         notmuch->config = _notmuch_string_map_create (notmuch);
337
338     if (unlikely(notmuch->config == NULL)) {
339         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
340         goto DONE;
341     }
342
343     for (groups = g_key_file_get_groups (file, NULL); *groups; groups++) {
344         for (keys = g_key_file_get_keys (file, *groups, NULL, NULL); *keys; keys++) {
345             char *absolute_key = talloc_asprintf(notmuch, "%s.%s", *groups,  *keys);
346             val = g_key_file_get_value (file, *groups, *keys, NULL);
347             if (! val) {
348                 status = NOTMUCH_STATUS_FILE_ERROR;
349                 goto DONE;
350             }
351             _notmuch_string_map_set (notmuch->config, absolute_key, val);
352             talloc_free (absolute_key);
353             if (status)
354                 goto DONE;
355         }
356     }
357
358  DONE:
359     return status;
360 }
361
362 notmuch_status_t
363 notmuch_config_get_bool (notmuch_database_t *notmuch, notmuch_config_key_t key, notmuch_bool_t *val)
364 {
365     const char *key_string, *val_string;
366
367     key_string = _notmuch_config_key_to_string (key);
368     if (! key_string) {
369         return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
370     }
371
372     val_string = _notmuch_string_map_get (notmuch->config, key_string);
373     if (! val_string) {
374         *val = FALSE;
375         return NOTMUCH_STATUS_SUCCESS;
376     }
377
378     if (strcase_equal (val_string, "false") || strcase_equal (val_string, "no"))
379         *val = FALSE;
380     else if (strcase_equal (val_string, "true") || strcase_equal (val_string, "yes"))
381         *val = TRUE;
382     else
383         return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
384
385     return NOTMUCH_STATUS_SUCCESS;
386 }
387
388 static const char *
389 _notmuch_config_key_to_string (notmuch_config_key_t key) {
390     switch (key) {
391     case NOTMUCH_CONFIG_DATABASE_PATH:
392         return "database.path";
393     case NOTMUCH_CONFIG_HOOK_DIR:
394         return "database.hook_dir";
395     case NOTMUCH_CONFIG_EXCLUDE_TAGS:
396         return "search.exclude_tags";
397     case NOTMUCH_CONFIG_NEW_TAGS:
398         return "new.tags";
399     case NOTMUCH_CONFIG_NEW_IGNORE:
400         return "new.ignore";
401     case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS:
402         return "maildir.synchronize_flags";
403     case NOTMUCH_CONFIG_PRIMARY_EMAIL:
404         return "user.primary_email";
405     case NOTMUCH_CONFIG_OTHER_EMAIL:
406         return "user.other_email";
407     case NOTMUCH_CONFIG_USER_NAME:
408         return "user.name";
409     default:
410         return NULL;
411     }
412 }
413
414 static const char *
415 _notmuch_config_default (void *ctx, notmuch_config_key_t key) {
416     char *path;
417
418     switch (key) {
419     case NOTMUCH_CONFIG_DATABASE_PATH:
420         path = getenv ("MAILDIR");
421         if (path)
422             path = talloc_strdup (ctx, path);
423         else
424             path = talloc_asprintf (ctx, "%s/mail",
425                                     getenv ("HOME"));
426         return path;
427     case NOTMUCH_CONFIG_EXCLUDE_TAGS:
428         return "";
429     case NOTMUCH_CONFIG_NEW_TAGS:
430         return "inbox;unread";
431     case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS:
432         return "true";
433     case NOTMUCH_CONFIG_HOOK_DIR:
434     case NOTMUCH_CONFIG_NEW_IGNORE:
435     case NOTMUCH_CONFIG_USER_NAME:
436     case NOTMUCH_CONFIG_PRIMARY_EMAIL:
437     case NOTMUCH_CONFIG_OTHER_EMAIL:
438         return NULL;
439     default:
440     case NOTMUCH_CONFIG_LAST:
441         INTERNAL_ERROR ("illegal key enum %d", key);
442    }
443 }
444
445 notmuch_status_t
446 _notmuch_config_load_defaults (notmuch_database_t *notmuch) {
447     notmuch_config_key_t key;
448     for (key = NOTMUCH_CONFIG_FIRST;
449          key < NOTMUCH_CONFIG_LAST;
450          key = notmuch_config_key_t(key + 1)) {
451         const char *val = notmuch_config_get (notmuch, key);
452         const char *key_string = _notmuch_config_key_to_string (key);
453
454         val = _notmuch_string_map_get (notmuch->config, key_string);
455         if (! val) {
456             _notmuch_string_map_set (notmuch->config, key_string, _notmuch_config_default (notmuch, key));
457         }
458     }
459     return NOTMUCH_STATUS_SUCCESS;
460 }
461
462 const char *
463 notmuch_config_get (notmuch_database_t *notmuch, notmuch_config_key_t key) {
464
465     return _notmuch_string_map_get (notmuch->config, _notmuch_config_key_to_string (key));
466 }
467
468 notmuch_status_t
469 notmuch_config_set (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val) {
470
471     return notmuch_database_set_config (notmuch, _notmuch_config_key_to_string (key), val);
472 }
473
474 void
475 _notmuch_config_cache (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val) {
476     _notmuch_string_map_set (notmuch->config, _notmuch_config_key_to_string (key), val);
477 }