]> git.notmuchmail.org Git - notmuch/blob - lib/config.cc
lib/config: add notmuch_config_get_values_string
[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     const char *key_str = _notmuch_config_key_to_string (key);
265
266     if (! key_str)
267         return NULL;
268
269     return notmuch_config_get_values_string (notmuch, key_str);
270 }
271
272 notmuch_config_values_t *
273 notmuch_config_get_values_string (notmuch_database_t *notmuch, const char *key_str)
274 {
275     notmuch_config_values_t *values = NULL;
276     bool ok = false;
277
278     values = talloc (notmuch, notmuch_config_values_t);
279     if (unlikely (! values))
280         goto DONE;
281
282     values->children = talloc_new (values);
283
284     values->string = _notmuch_string_map_get (notmuch->config, key_str);
285     if (! values->string)
286         goto DONE;
287
288     values->iterator = strsplit_len (values->string, ';', &(values->tok_len));
289     ok = true;
290
291   DONE:
292     if (! ok) {
293         if (values)
294             talloc_free (values);
295         return NULL;
296     }
297     return values;
298 }
299
300 notmuch_bool_t
301 notmuch_config_values_valid (notmuch_config_values_t *values)
302 {
303     if (! values)
304         return false;
305
306     return (values->iterator != NULL);
307 }
308
309 const char *
310 notmuch_config_values_get (notmuch_config_values_t *values)
311 {
312     return talloc_strndup (values, values->iterator, values->tok_len);
313 }
314
315 void
316 notmuch_config_values_start (notmuch_config_values_t *values)
317 {
318     if (values == NULL)
319         return;
320     if (values->children) {
321         talloc_free (values->children);
322     }
323
324     values->children = talloc_new (values);
325
326     values->iterator = strsplit_len (values->string, ';', &(values->tok_len));
327 }
328
329 void
330 notmuch_config_values_move_to_next (notmuch_config_values_t *values)
331 {
332     values->iterator += values->tok_len;
333     values->iterator = strsplit_len (values->iterator, ';', &(values->tok_len));
334 }
335
336 void
337 notmuch_config_values_destroy (notmuch_config_values_t *values)
338 {
339     talloc_free (values);
340 }
341
342 notmuch_status_t
343 _notmuch_config_load_from_file (notmuch_database_t *notmuch,
344                                 GKeyFile *file)
345 {
346     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
347     gchar **groups = NULL, **keys, *val;
348
349     if (notmuch->config == NULL)
350         notmuch->config = _notmuch_string_map_create (notmuch);
351
352     if (unlikely (notmuch->config == NULL)) {
353         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
354         goto DONE;
355     }
356
357     groups = g_key_file_get_groups (file, NULL);
358     for (gchar **grp = groups; *grp; grp++) {
359         keys = g_key_file_get_keys (file, *grp, NULL, NULL);
360         for (gchar **keys_p = keys; *keys_p; keys_p++) {
361             char *absolute_key = talloc_asprintf (notmuch, "%s.%s", *grp,  *keys_p);
362             val = g_key_file_get_value (file, *grp, *keys_p, NULL);
363             if (! val) {
364                 status = NOTMUCH_STATUS_FILE_ERROR;
365                 goto DONE;
366             }
367             _notmuch_string_map_set (notmuch->config, absolute_key, val);
368             g_free (val);
369             talloc_free (absolute_key);
370             if (status)
371                 goto DONE;
372         }
373         g_strfreev (keys);
374     }
375
376   DONE:
377     if (groups)
378         g_strfreev (groups);
379
380     return status;
381 }
382
383 notmuch_status_t
384 notmuch_config_get_bool (notmuch_database_t *notmuch, notmuch_config_key_t key, notmuch_bool_t *val)
385 {
386     const char *key_string, *val_string;
387
388     key_string = _notmuch_config_key_to_string (key);
389     if (! key_string) {
390         return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
391     }
392
393     val_string = _notmuch_string_map_get (notmuch->config, key_string);
394     if (! val_string) {
395         *val = FALSE;
396         return NOTMUCH_STATUS_SUCCESS;
397     }
398
399     if (strcase_equal (val_string, "false") || strcase_equal (val_string, "no"))
400         *val = FALSE;
401     else if (strcase_equal (val_string, "true") || strcase_equal (val_string, "yes"))
402         *val = TRUE;
403     else
404         return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
405
406     return NOTMUCH_STATUS_SUCCESS;
407 }
408
409 static const char *
410 _notmuch_config_key_to_string (notmuch_config_key_t key)
411 {
412     switch (key) {
413     case NOTMUCH_CONFIG_DATABASE_PATH:
414         return "database.path";
415     case NOTMUCH_CONFIG_MAIL_ROOT:
416         return "database.mail_root";
417     case NOTMUCH_CONFIG_HOOK_DIR:
418         return "database.hook_dir";
419     case NOTMUCH_CONFIG_BACKUP_DIR:
420         return "database.backup_dir";
421     case NOTMUCH_CONFIG_EXCLUDE_TAGS:
422         return "search.exclude_tags";
423     case NOTMUCH_CONFIG_NEW_TAGS:
424         return "new.tags";
425     case NOTMUCH_CONFIG_NEW_IGNORE:
426         return "new.ignore";
427     case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS:
428         return "maildir.synchronize_flags";
429     case NOTMUCH_CONFIG_PRIMARY_EMAIL:
430         return "user.primary_email";
431     case NOTMUCH_CONFIG_OTHER_EMAIL:
432         return "user.other_email";
433     case NOTMUCH_CONFIG_USER_NAME:
434         return "user.name";
435     default:
436         return NULL;
437     }
438 }
439
440 static const char *
441 _notmuch_config_default (notmuch_database_t *notmuch, notmuch_config_key_t key)
442 {
443     char *path;
444
445     switch (key) {
446     case NOTMUCH_CONFIG_DATABASE_PATH:
447         path = getenv ("MAILDIR");
448         if (path)
449             path = talloc_strdup (notmuch, path);
450         else
451             path = talloc_asprintf (notmuch, "%s/mail",
452                                     getenv ("HOME"));
453         return path;
454     case NOTMUCH_CONFIG_MAIL_ROOT:
455         /* by default, mail root is the same as database path */
456         return notmuch_database_get_path (notmuch);
457     case NOTMUCH_CONFIG_EXCLUDE_TAGS:
458         return "";
459     case NOTMUCH_CONFIG_NEW_TAGS:
460         return "inbox;unread";
461     case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS:
462         return "true";
463     case NOTMUCH_CONFIG_HOOK_DIR:
464     case NOTMUCH_CONFIG_BACKUP_DIR:
465     case NOTMUCH_CONFIG_NEW_IGNORE:
466     case NOTMUCH_CONFIG_USER_NAME:
467     case NOTMUCH_CONFIG_PRIMARY_EMAIL:
468     case NOTMUCH_CONFIG_OTHER_EMAIL:
469         return NULL;
470     default:
471     case NOTMUCH_CONFIG_LAST:
472         INTERNAL_ERROR ("illegal key enum %d", key);
473     }
474 }
475
476 notmuch_status_t
477 _notmuch_config_load_defaults (notmuch_database_t *notmuch)
478 {
479     notmuch_config_key_t key;
480
481     for (key = NOTMUCH_CONFIG_FIRST;
482          key < NOTMUCH_CONFIG_LAST;
483          key = notmuch_config_key_t (key + 1)) {
484         const char *val = notmuch_config_get (notmuch, key);
485         const char *key_string = _notmuch_config_key_to_string (key);
486
487         val = _notmuch_string_map_get (notmuch->config, key_string);
488         if (! val) {
489             _notmuch_string_map_set (notmuch->config, key_string, _notmuch_config_default (notmuch,
490                                                                                            key));
491         }
492     }
493     return NOTMUCH_STATUS_SUCCESS;
494 }
495
496 const char *
497 notmuch_config_get (notmuch_database_t *notmuch, notmuch_config_key_t key)
498 {
499
500     return _notmuch_string_map_get (notmuch->config, _notmuch_config_key_to_string (key));
501 }
502
503 notmuch_status_t
504 notmuch_config_set (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val)
505 {
506
507     return notmuch_database_set_config (notmuch, _notmuch_config_key_to_string (key), val);
508 }
509
510 void
511 _notmuch_config_cache (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val)
512 {
513     if (notmuch->config == NULL)
514         notmuch->config = _notmuch_string_map_create (notmuch);
515
516     _notmuch_string_map_set (notmuch->config, _notmuch_config_key_to_string (key), val);
517 }