]> git.notmuchmail.org Git - notmuch/blob - lib/config.cc
lib/config: add config values iterator
[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 };
38
39 static const char * _notmuch_config_key_to_string (notmuch_config_key_t key);
40
41 static int
42 _notmuch_config_list_destroy (notmuch_config_list_t *list)
43 {
44     /* invoke destructor w/o deallocating memory */
45     list->iterator.~TermIterator();
46     return 0;
47 }
48
49 notmuch_status_t
50 notmuch_database_set_config (notmuch_database_t *notmuch,
51                              const char *key,
52                              const char *value)
53 {
54     notmuch_status_t status;
55
56     status = _notmuch_database_ensure_writable (notmuch);
57     if (status)
58         return status;
59
60     if (! notmuch->config) {
61         if ((status = _notmuch_config_load_from_database (notmuch)))
62             return status;
63     }
64
65     try {
66         notmuch->writable_xapian_db->set_metadata (CONFIG_PREFIX + key, value);
67     } catch (const Xapian::Error &error) {
68         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
69         notmuch->exception_reported = true;
70         _notmuch_database_log (notmuch, "Error: A Xapian exception occurred setting metadata: %s\n",
71                                error.get_msg ().c_str ());
72     }
73
74     if (status)
75         return status;
76
77     _notmuch_string_map_set (notmuch->config, key, value);
78
79     return NOTMUCH_STATUS_SUCCESS;
80 }
81
82 static notmuch_status_t
83 _metadata_value (notmuch_database_t *notmuch,
84                  const char *key,
85                  std::string &value)
86 {
87     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
88
89     try {
90         value = notmuch->xapian_db->get_metadata (CONFIG_PREFIX + key);
91     } catch (const Xapian::Error &error) {
92         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
93         notmuch->exception_reported = true;
94         _notmuch_database_log (notmuch, "Error: A Xapian exception occurred getting metadata: %s\n",
95                                error.get_msg ().c_str ());
96     }
97     return status;
98 }
99
100 notmuch_status_t
101 notmuch_database_get_config (notmuch_database_t *notmuch,
102                              const char *key,
103                              char **value)
104 {
105     const char* stored_val;
106     notmuch_status_t status;
107
108     if (! notmuch->config) {
109         if ((status = _notmuch_config_load_from_database (notmuch)))
110             return status;
111     }
112
113     if (! value)
114         return NOTMUCH_STATUS_NULL_POINTER;
115
116     stored_val = _notmuch_string_map_get (notmuch->config, key);
117     if (! stored_val) {
118         /* XXX in principle this API should be fixed so empty string
119          * is distinguished from not found */
120         *value = strdup("");
121     } else {
122         *value = strdup (stored_val);
123     }
124
125     return NOTMUCH_STATUS_SUCCESS;
126 }
127
128 notmuch_status_t
129 notmuch_database_get_config_list (notmuch_database_t *notmuch,
130                                   const char *prefix,
131                                   notmuch_config_list_t **out)
132 {
133     notmuch_config_list_t *list = NULL;
134     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
135
136     list = talloc (notmuch, notmuch_config_list_t);
137     if (! list) {
138         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
139         goto DONE;
140     }
141
142     list->notmuch = notmuch;
143     list->current_key = NULL;
144     list->current_val = NULL;
145
146     try {
147
148         new(&(list->iterator)) Xapian::TermIterator (notmuch->xapian_db->metadata_keys_begin
149                                                          (CONFIG_PREFIX + (prefix ? prefix : "")));
150         talloc_set_destructor (list, _notmuch_config_list_destroy);
151
152     } catch (const Xapian::Error &error) {
153         _notmuch_database_log (notmuch, "A Xapian exception occurred getting metadata iterator: %s.\n",
154                                error.get_msg ().c_str ());
155         notmuch->exception_reported = true;
156         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
157     }
158
159     *out = list;
160
161   DONE:
162     if (status) {
163         if (list) {
164             talloc_free (list);
165             if (status != NOTMUCH_STATUS_XAPIAN_EXCEPTION)
166                 _notmuch_config_list_destroy (list);
167         }
168     }  else {
169         talloc_set_destructor (list, _notmuch_config_list_destroy);
170     }
171
172     return status;
173 }
174
175 notmuch_bool_t
176 notmuch_config_list_valid (notmuch_config_list_t *metadata)
177 {
178     if (metadata->iterator == metadata->notmuch->xapian_db->metadata_keys_end ())
179         return false;
180
181     return true;
182 }
183
184 static inline char * _key_from_iterator (notmuch_config_list_t *list) {
185     return talloc_strdup (list, (*list->iterator).c_str () + CONFIG_PREFIX.length ());
186 }
187
188 const char *
189 notmuch_config_list_key (notmuch_config_list_t *list)
190 {
191     if (list->current_key)
192         talloc_free (list->current_key);
193
194     list->current_key = _key_from_iterator (list);
195
196     return list->current_key;
197 }
198
199 const char *
200 notmuch_config_list_value (notmuch_config_list_t *list)
201 {
202     std::string strval;
203     notmuch_status_t status;
204     char *key = _key_from_iterator (list);
205
206     /* TODO: better error reporting?? */
207     status = _metadata_value (list->notmuch, key, strval);
208     if (status)
209         return NULL;
210
211     if (list->current_val)
212         talloc_free (list->current_val);
213
214     list->current_val = talloc_strdup (list, strval.c_str ());
215     talloc_free (key);
216     return list->current_val;
217 }
218
219 void
220 notmuch_config_list_move_to_next (notmuch_config_list_t *list)
221 {
222     list->iterator++;
223 }
224
225 void
226 notmuch_config_list_destroy (notmuch_config_list_t *list)
227 {
228     talloc_free (list);
229 }
230
231 notmuch_status_t
232 _notmuch_config_load_from_database (notmuch_database_t *notmuch)
233 {
234     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
235     notmuch_config_list_t *list;
236
237     if (notmuch->config == NULL)
238         notmuch->config = _notmuch_string_map_create (notmuch);
239
240     if (unlikely(notmuch->config == NULL))
241         return NOTMUCH_STATUS_OUT_OF_MEMORY;
242
243     status = notmuch_database_get_config_list (notmuch, "", &list);
244     if (status)
245         return status;
246
247     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
248         _notmuch_string_map_append (notmuch->config,
249                                     notmuch_config_list_key (list),
250                                     notmuch_config_list_value (list));
251     }
252
253     return status;
254 }
255
256 notmuch_config_values_t *
257 notmuch_config_get_values (notmuch_database_t *notmuch, notmuch_config_key_t key)
258 {
259     notmuch_config_values_t *values;
260
261     const char *str;
262     const char *key_str = _notmuch_config_key_to_string (key);
263
264     if (! key_str)
265         return NULL;
266
267     str  = _notmuch_string_map_get (notmuch->config, key_str);
268     if (! str)
269         return NULL;
270
271     values = talloc (notmuch, notmuch_config_values_t);
272     if (unlikely(! values))
273         return NULL;
274
275     values->iterator = strsplit_len (str, ';', &(values->tok_len));
276     return values;
277 }
278
279 notmuch_bool_t
280 notmuch_config_values_valid (notmuch_config_values_t *values) {
281     if (! values)
282         return false;
283
284     return (values->iterator != NULL);
285 }
286
287 const char *
288 notmuch_config_values_get (notmuch_config_values_t *values) {
289     return talloc_strndup (values, values->iterator, values->tok_len);
290 }
291
292 void
293 notmuch_config_values_move_to_next (notmuch_config_values_t *values) {
294     values->iterator += values->tok_len;
295     values->iterator = strsplit_len (values->iterator, ';', &(values->tok_len));
296 }
297
298 void
299 notmuch_config_values_destroy (notmuch_config_values_t *values) {
300     talloc_free (values);
301 }
302
303 notmuch_status_t
304 _notmuch_config_load_from_file (notmuch_database_t *notmuch,
305                                 GKeyFile *file)
306 {
307     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
308     gchar **groups, **keys, *val;
309
310     if (notmuch->config == NULL)
311         notmuch->config = _notmuch_string_map_create (notmuch);
312
313     if (unlikely(notmuch->config == NULL)) {
314         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
315         goto DONE;
316     }
317
318     for (groups = g_key_file_get_groups (file, NULL); *groups; groups++) {
319         for (keys = g_key_file_get_keys (file, *groups, NULL, NULL); *keys; keys++) {
320             char *absolute_key = talloc_asprintf(notmuch, "%s.%s", *groups,  *keys);
321             val = g_key_file_get_value (file, *groups, *keys, NULL);
322             if (! val) {
323                 status = NOTMUCH_STATUS_FILE_ERROR;
324                 goto DONE;
325             }
326             _notmuch_string_map_set (notmuch->config, absolute_key, val);
327             talloc_free (absolute_key);
328             if (status)
329                 goto DONE;
330         }
331     }
332
333  DONE:
334     return status;
335 }
336
337 static const char *
338 _notmuch_config_key_to_string (notmuch_config_key_t key) {
339     switch (key) {
340     case NOTMUCH_CONFIG_DATABASE_PATH:
341         return "database.path";
342     case NOTMUCH_CONFIG_EXCLUDE_TAGS:
343         return "search.exclude_tags";
344     case NOTMUCH_CONFIG_NEW_TAGS:
345         return "new.tags";
346     case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS:
347         return "maildir.synchronize_flags";
348     case NOTMUCH_CONFIG_PRIMARY_EMAIL:
349         return "user.primary_email";
350     case NOTMUCH_CONFIG_OTHER_EMAIL:
351         return "user.other_email";
352     case NOTMUCH_CONFIG_USER_NAME:
353         return "user.name";
354     default:
355         return NULL;
356     }
357 }
358
359 static const char *
360 _notmuch_config_default (void *ctx, notmuch_config_key_t key) {
361     char *path;
362
363     switch (key) {
364     case NOTMUCH_CONFIG_DATABASE_PATH:
365         path = getenv ("MAILDIR");
366         if (path)
367             path = talloc_strdup (ctx, path);
368         else
369             path = talloc_asprintf (ctx, "%s/mail",
370                                     getenv ("HOME"));
371         return path;
372     case NOTMUCH_CONFIG_EXCLUDE_TAGS:
373         return "";
374     case NOTMUCH_CONFIG_NEW_TAGS:
375         return "inbox;unread";
376     case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS:
377         return "true";
378     case NOTMUCH_CONFIG_USER_NAME:
379     case NOTMUCH_CONFIG_PRIMARY_EMAIL:
380     case NOTMUCH_CONFIG_OTHER_EMAIL:
381         return NULL;
382     default:
383     case NOTMUCH_CONFIG_LAST:
384         INTERNAL_ERROR ("illegal key enum %d", key);
385    }
386 }
387
388 notmuch_status_t
389 _notmuch_config_load_defaults (notmuch_database_t *notmuch) {
390     notmuch_config_key_t key;
391     for (key = NOTMUCH_CONFIG_FIRST;
392          key < NOTMUCH_CONFIG_LAST;
393          key = notmuch_config_key_t(key + 1)) {
394         const char *val = notmuch_config_get (notmuch, key);
395         const char *key_string = _notmuch_config_key_to_string (key);
396
397         val = _notmuch_string_map_get (notmuch->config, key_string);
398         if (! val) {
399             _notmuch_string_map_set (notmuch->config, key_string, _notmuch_config_default (notmuch, key));
400         }
401     }
402     return NOTMUCH_STATUS_SUCCESS;
403 }
404
405 const char *
406 notmuch_config_get (notmuch_database_t *notmuch, notmuch_config_key_t key) {
407
408     return _notmuch_string_map_get (notmuch->config, _notmuch_config_key_to_string (key));
409 }
410
411 notmuch_status_t
412 notmuch_config_set (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val) {
413
414     return notmuch_database_set_config (notmuch, _notmuch_config_key_to_string (key), val);
415 }