1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3 *
4 * Copyright (C) 1997-2013 Stuart Parmenter and others,
5 * See the file AUTHORS for a list.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 * 02111-1307, USA.
21 */
22
23 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
24 # include "config.h"
25 #endif /* HAVE_CONFIG_H */
26 #include "libbalsa-conf.h"
27
28 #include <string.h>
29
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33 #include <unistd.h>
34 #include <stdio.h>
35 #include <glib/gstdio.h>
36
37 #include "libbalsa.h"
38 #include "misc.h"
39 #include <glib/gi18n.h>
40
41 typedef struct {
42 GKeyFile *key_file;
43 gchar *path;
44 guint changes;
45 time_t mtime;
46 } LibBalsaConf;
47
48 static LibBalsaConf lbc_conf;
49 static LibBalsaConf lbc_conf_priv;
50 static GSList *lbc_groups;
51
52 #define BALSA_KEY_FILE "config"
53 #define DEBUG FALSE
54 #define LBC_KEY_FILE(priv) \
55 ((priv) ? lbc_conf_priv.key_file : lbc_conf.key_file)
56 #define LBC_CHANGED(priv) \
57 ((priv) ? ++lbc_conf_priv.changes : ++lbc_conf.changes)
58
59 static gchar *
lbc_readfile(const gchar * filename)60 lbc_readfile(const gchar * filename)
61 {
62 gchar *buf;
63 gchar **split;
64
65 if (!g_file_get_contents(filename, &buf, NULL, NULL)) {
66 #if DEBUG
67 g_message("Failed to read \"%s\"\n", filename);
68 #endif /* DEBUG */
69 return NULL;
70 }
71
72 split = g_strsplit(buf, "\\\\ ", 0);
73 g_free(buf);
74 buf = g_strjoinv("\\ ", split);
75 g_strfreev(split);
76
77 return buf;
78 }
79
80 static void
lbc_init(LibBalsaConf * conf,const gchar * filename,const gchar * old_dir)81 lbc_init(LibBalsaConf * conf, const gchar * filename,
82 const gchar * old_dir)
83 {
84 struct stat buf;
85 GError *error = NULL;
86 gint rc;
87
88 if (!conf->path)
89 conf->path =
90 g_build_filename(g_get_home_dir(), ".balsa", filename, NULL);
91 rc = stat(conf->path, &buf);
92 if (conf->key_file) {
93 if (rc >= 0 && buf.st_mtime <= conf->mtime)
94 /* found the config file, and it hasn't been touched since
95 * we loaded it */
96 return;
97 } else {
98 conf->key_file = g_key_file_new();
99 if (rc < 0)
100 /* no config file--must be first time startup */
101 return;
102 }
103 conf->mtime = buf.st_mtime;
104
105 libbalsa_assure_balsa_dir();
106 if (!g_key_file_load_from_file
107 (conf->key_file, conf->path, G_KEY_FILE_NONE, &error)) {
108 gchar *old_path;
109 gchar *buf;
110 static gboolean warn = TRUE;
111
112 old_path =
113 g_build_filename(g_get_home_dir(), old_dir, "balsa", NULL);
114 #if DEBUG
115 g_message("Could not load config from \"%s\":\n %s\n"
116 " trying \"%s\"", conf->path, error->message, old_path);
117 #endif /* DEBUG */
118 g_clear_error(&error);
119
120 buf = lbc_readfile(old_path);
121 if (buf) {
122 /* GnomeConfig used ' ' as the list separator... */
123 g_key_file_set_list_separator(conf->key_file, ' ');
124 g_key_file_load_from_data(conf->key_file, buf, -1,
125 G_KEY_FILE_KEEP_COMMENTS, &error);
126 g_free(buf);
127 /* ...but GKeyFile doesn't handle it properly, so we'll
128 * revert to the default ';'. */
129 g_key_file_set_list_separator(conf->key_file, ';');
130 }
131 if (!buf || error) {
132 #if DEBUG
133 g_message("Could not load key file from file \"%s\": %s",
134 old_path,
135 error ? error->message : g_strerror(errno));
136 #endif /* DEBUG */
137 g_clear_error(&error);
138 warn = FALSE;
139 }
140 g_free(old_path);
141 if (warn)
142 libbalsa_information(LIBBALSA_INFORMATION_WARNING,
143 _("Your Balsa configuration "
144 "is now stored in "
145 "\"~/.balsa/config\"."));
146 }
147 }
148
149 #ifdef BALSA_USE_THREADS
150 #if GLIB_CHECK_VERSION(2, 32, 0)
151 static GRecMutex lbc_mutex;
152 #else /* GLIB_CHECK_VERSION(2, 32, 0) */
153 static GStaticRecMutex lbc_mutex = G_STATIC_REC_MUTEX_INIT;
154 #endif /* GLIB_CHECK_VERSION(2, 32, 0) */
155 #endif /* BALSA_USE_THREADS */
156
157 static void
lbc_lock(void)158 lbc_lock(void)
159 {
160 static gboolean initialized = FALSE;
161
162 #ifdef BALSA_USE_THREADS
163 #if GLIB_CHECK_VERSION(2, 32, 0)
164 g_rec_mutex_lock(&lbc_mutex);
165 #else /* GLIB_CHECK_VERSION(2, 32, 0) */
166 g_static_rec_mutex_lock(&lbc_mutex);
167 #endif /* GLIB_CHECK_VERSION(2, 32, 0) */
168 #endif /* BALSA_USE_THREADS */
169 if (!initialized) {
170 lbc_init(&lbc_conf, "config", ".gnome2");
171 lbc_init(&lbc_conf_priv, "config-private", ".gnome2_private");
172 initialized = TRUE;
173 }
174 }
175
176 static void
lbc_unlock(void)177 lbc_unlock(void)
178 {
179 #ifdef BALSA_USE_THREADS
180 #if GLIB_CHECK_VERSION(2, 32, 0)
181 g_rec_mutex_unlock(&lbc_mutex);
182 #else /* GLIB_CHECK_VERSION(2, 32, 0) */
183 g_static_rec_mutex_unlock(&lbc_mutex);
184 #endif /* GLIB_CHECK_VERSION(2, 32, 0) */
185 #endif /* BALSA_USE_THREADS */
186 }
187
188 /*
189 * Call @func for each group that begins with @prefix.
190 * @func is called with arguments:
191 * const gchar * @key the group;
192 * const gchar * @value the trailing part of the group name,
193 * following the @prefix;
194 * gpointer @data the @data passed in.
195 * Iteration terminates when @func returns TRUE.
196 */
197 void
libbalsa_conf_foreach_group(const gchar * prefix,LibBalsaConfForeachFunc func,gpointer data)198 libbalsa_conf_foreach_group(const gchar * prefix,
199 LibBalsaConfForeachFunc func, gpointer data)
200 {
201 gchar **groups, **group;
202 gsize pref_len = strlen(prefix);
203
204 lbc_lock();
205
206 groups = g_key_file_get_groups(lbc_conf.key_file, NULL);
207 for (group = groups; *group; group++) {
208 if (g_str_has_prefix(*group, prefix)
209 && func(*group, *group + pref_len, data))
210 break;
211 }
212 g_strfreev(groups);
213
214 lbc_unlock();
215 }
216
217 void
libbalsa_conf_foreach_keys(const gchar * group,LibBalsaConfForeachFunc func,gpointer data)218 libbalsa_conf_foreach_keys(const gchar * group,
219 LibBalsaConfForeachFunc func, gpointer data)
220 {
221 gchar **keys, **key;
222
223 lbc_lock();
224
225 if ((keys = g_key_file_get_keys(lbc_conf.key_file, group, NULL, NULL))) {
226 for (key = keys; *key; key++) {
227 gchar * val = g_key_file_get_value(lbc_conf.key_file, group, *key, NULL);
228 if (func(*key, val, data)) {
229 g_free(val);
230 break;
231 }
232 g_free(val);
233 }
234 g_strfreev(keys);
235 }
236
237 lbc_unlock();
238 }
239
240 void
libbalsa_conf_push_group(const gchar * group)241 libbalsa_conf_push_group(const gchar * group)
242 {
243
244 lbc_lock(); /* Will be held until prefix is popped. */
245 lbc_groups = g_slist_prepend(lbc_groups, g_strdup(group));
246 }
247
248 void
libbalsa_conf_pop_group(void)249 libbalsa_conf_pop_group(void)
250 {
251 g_free(lbc_groups->data);
252 lbc_groups = g_slist_delete_link(lbc_groups, lbc_groups);
253 lbc_unlock(); /* Held since prefix was pushed. */
254 }
255
256 void
libbalsa_conf_remove_group_(const char * group,gboolean priv)257 libbalsa_conf_remove_group_(const char *group, gboolean priv)
258 {
259 lbc_lock();
260 g_key_file_remove_group(LBC_KEY_FILE(priv), group, NULL);
261 LBC_CHANGED(priv);
262 lbc_unlock();
263 }
264
265 gboolean
libbalsa_conf_has_group(const char * group)266 libbalsa_conf_has_group(const char *group)
267 {
268 return (g_key_file_has_group(lbc_conf.key_file, group) ||
269 g_key_file_has_group(lbc_conf_priv.key_file, group));
270 }
271
272 gboolean
libbalsa_conf_has_key(const gchar * key)273 libbalsa_conf_has_key(const gchar * key)
274 {
275 /* g_key_file_has_key returns FALSE on error, but that is OK */
276 return (g_key_file_has_key(lbc_conf.key_file, lbc_groups->data,
277 key, NULL) ||
278 g_key_file_has_key(lbc_conf_priv.key_file, lbc_groups->data,
279 key, NULL));
280 }
281
282 static void
lbc_remove_key(LibBalsaConf * conf,const char * key)283 lbc_remove_key(LibBalsaConf * conf, const char *key)
284 {
285 GError *error = NULL;
286
287 g_key_file_remove_key(conf->key_file, lbc_groups->data, key, &error);
288 if (error)
289 g_error_free(error);
290 else
291 ++conf->changes;
292 }
293
294 void
libbalsa_conf_clean_key(const char * key)295 libbalsa_conf_clean_key(const char *key)
296 {
297 lbc_lock();
298 lbc_remove_key(&lbc_conf, key);
299 lbc_remove_key(&lbc_conf_priv, key);
300 lbc_unlock();
301 }
302
303 void
libbalsa_conf_set_bool_(const char * path,gboolean value,gboolean priv)304 libbalsa_conf_set_bool_(const char *path, gboolean value, gboolean priv)
305 {
306 g_key_file_set_boolean(LBC_KEY_FILE(priv), lbc_groups->data, path,
307 value);
308 LBC_CHANGED(priv);
309 }
310
311 static gchar *
lbc_get_key(const char * path,const char ** defval)312 lbc_get_key(const char *path, const char **defval)
313 {
314 const gchar *equals;
315 gchar *key;
316
317 equals = strchr(path, '=');
318 key = equals ? g_strndup(path, equals - path) : g_strdup(path);
319 if (defval)
320 *defval = equals ? ++equals : NULL;
321
322 return key;
323 }
324
325 gboolean
libbalsa_conf_get_bool_with_default_(const char * path,gboolean * def,gboolean priv)326 libbalsa_conf_get_bool_with_default_(const char *path, gboolean * def,
327 gboolean priv)
328 {
329 gchar *key;
330 const gchar *defval;
331 gboolean retval;
332 GError *error = NULL;
333
334 key = lbc_get_key(path, &defval);
335 retval =
336 g_key_file_get_boolean(LBC_KEY_FILE(priv), lbc_groups->data, key,
337 &error);
338 g_free(key);
339 if (error) {
340 g_error_free(error);
341 if (defval)
342 retval = strcmp(defval, "true") == 0;
343 }
344
345 if (def)
346 *def = error != NULL;
347
348 return retval;
349 }
350
351 gint
libbalsa_conf_get_int_with_default_(const char * path,gboolean * def,gboolean priv)352 libbalsa_conf_get_int_with_default_(const char *path,
353 gboolean * def, gboolean priv)
354 {
355 gchar *key;
356 const gchar *defval;
357 gint retval;
358 GError *error = NULL;
359
360 key = lbc_get_key(path, &defval);
361 retval =
362 g_key_file_get_integer(LBC_KEY_FILE(priv), lbc_groups->data, key,
363 &error);
364 g_free(key);
365 if (error) {
366 g_error_free(error);
367 if (defval)
368 retval = g_ascii_strtoull(defval, NULL, 10);
369 }
370
371 if (def)
372 *def = error != NULL;
373
374 return retval;
375 }
376
377 gdouble
libbalsa_conf_get_double_with_default_(const char * path,gboolean * def,gboolean priv)378 libbalsa_conf_get_double_with_default_(const char *path,
379 gboolean * def, gboolean priv)
380 {
381 gchar *key;
382 const gchar *defval;
383 gdouble retval;
384 GError *error = NULL;
385
386 key = lbc_get_key(path, &defval);
387 retval =
388 g_key_file_get_double(LBC_KEY_FILE(priv), lbc_groups->data, key,
389 &error);
390 g_free(key);
391 if (error) {
392 g_error_free(error);
393 if (defval)
394 retval = g_ascii_strtod(defval, NULL);
395 }
396
397 if (def)
398 *def = error != NULL;
399
400 return retval;
401 }
402
403 void
libbalsa_conf_set_int_(const char * path,int value,gboolean priv)404 libbalsa_conf_set_int_(const char *path, int value, gboolean priv)
405 {
406 g_key_file_set_integer(LBC_KEY_FILE(priv), lbc_groups->data, path,
407 value);
408 LBC_CHANGED(priv);
409 }
410
411 void
libbalsa_conf_set_double_(const char * path,double value,gboolean priv)412 libbalsa_conf_set_double_(const char *path, double value, gboolean priv)
413 {
414 g_key_file_set_double(LBC_KEY_FILE(priv), lbc_groups->data, path,
415 value);
416 LBC_CHANGED(priv);
417 }
418
419 void
libbalsa_conf_set_string_(const char * path,const char * value,gboolean priv)420 libbalsa_conf_set_string_(const char *path, const char *value,
421 gboolean priv)
422 {
423 g_key_file_set_string(LBC_KEY_FILE(priv), lbc_groups->data, path,
424 value ? value : "");
425 LBC_CHANGED(priv);
426 }
427
428 gchar *
libbalsa_conf_get_string_with_default_(const char * path,gboolean * def,gboolean priv)429 libbalsa_conf_get_string_with_default_(const char *path, gboolean * def,
430 gboolean priv)
431 {
432 gchar *key;
433 const gchar *defval;
434 gchar *retval;
435 GError *error = NULL;
436
437 key = lbc_get_key(path, &defval);
438 retval =
439 g_key_file_get_string(LBC_KEY_FILE(priv), lbc_groups->data, key,
440 &error);
441 g_free(key);
442 if (error) {
443 g_error_free(error);
444 retval = g_strdup(defval);
445 }
446
447 if (def)
448 *def = error != NULL;
449
450 return retval;
451 }
452
453 void
libbalsa_conf_set_vector(const char * path,int argc,const char * const argv[])454 libbalsa_conf_set_vector(const char *path, int argc,
455 const char *const argv[])
456 {
457 g_key_file_set_string_list(lbc_conf.key_file, lbc_groups->data, path,
458 argv, argc);
459 ++lbc_conf.changes;
460 }
461
462 void
libbalsa_conf_get_vector_with_default(const char * path,gint * argcp,char *** argvp,gboolean * def)463 libbalsa_conf_get_vector_with_default(const char *path, gint * argcp,
464 char ***argvp, gboolean * def)
465 {
466 GError *error = NULL;
467 gsize len;
468
469 *argvp =
470 g_key_file_get_string_list(lbc_conf.key_file, lbc_groups->data,
471 path, &len, &error);
472 *argcp = len;
473 if (error)
474 g_error_free(error);
475
476 if (def)
477 *def = error != NULL;
478 }
479
480 static void
lbc_drop_all(LibBalsaConf * conf)481 lbc_drop_all(LibBalsaConf * conf)
482 {
483 g_key_file_free(conf->key_file);
484 conf->key_file = NULL;
485 g_free(conf->path);
486 conf->path = NULL;
487 conf->changes = 0;
488 }
489
490 void
libbalsa_conf_drop_all(void)491 libbalsa_conf_drop_all(void)
492 {
493 lbc_lock();
494 lbc_drop_all(&lbc_conf);
495 lbc_drop_all(&lbc_conf_priv);
496 lbc_unlock();
497 }
498
499 static void
lbc_sync(LibBalsaConf * conf)500 lbc_sync(LibBalsaConf * conf)
501 {
502 gchar *buf;
503 gsize len;
504 GError *error = NULL;
505
506 if (!conf->changes)
507 return;
508
509 buf = g_key_file_to_data(conf->key_file, &len, &error);
510 if (error) {
511 #if DEBUG
512 g_message("Failed to sync config file \"%s\": %s\n"
513 " changes not saved", conf->path, error->message);
514 #endif /* DEBUG */
515 g_error_free(error);
516 g_free(buf);
517 return;
518 }
519
520 conf->mtime = time(NULL);
521 if (!g_file_set_contents(conf->path, buf, len, &error)) {
522 if (error) {
523 #if DEBUG
524 g_message("Failed to rewrite config file \"%s\": %s\n"
525 " changes not saved", conf->path, error->message);
526 #endif /* DEBUG */
527 g_error_free(error);
528 #if DEBUG
529 } else {
530 g_message("Failed to rewrite config file \"%s\";"
531 " changes not saved", conf->path);
532 #endif /* DEBUG */
533 }
534 }
535
536 g_free(buf);
537 }
538
539 static guint lbc_sync_idle_id;
540 #ifdef BALSA_USE_THREADS
541 G_LOCK_DEFINE_STATIC(lbc_sync_idle_id);
542 #endif /* BALSA_USE_THREADS */
543
544 void
libbalsa_conf_sync(void)545 libbalsa_conf_sync(void)
546 {
547 #ifdef BALSA_USE_THREADS
548 G_LOCK(lbc_sync_idle_id);
549 #endif /* BALSA_USE_THREADS */
550 #if DEBUG
551 g_print("%s id %d, will be cleared\n", __func__, lbc_sync_idle_id);
552 #endif /* DEBUG */
553 if (lbc_sync_idle_id) {
554 g_source_remove(lbc_sync_idle_id);
555 lbc_sync_idle_id = 0;
556 }
557 #ifdef BALSA_USE_THREADS
558 G_UNLOCK(lbc_sync_idle_id);
559 #endif /* BALSA_USE_THREADS */
560 lbc_lock();
561 lbc_sync(&lbc_conf);
562 lbc_sync(&lbc_conf_priv);
563 lbc_unlock();
564 }
565
566 static gboolean
libbalsa_conf_sync_idle_cb(gpointer data)567 libbalsa_conf_sync_idle_cb(gpointer data)
568 {
569 libbalsa_conf_sync();
570
571 return FALSE;
572 }
573
574 void
libbalsa_conf_queue_sync(void)575 libbalsa_conf_queue_sync(void)
576 {
577 #ifdef BALSA_USE_THREADS
578 G_LOCK(lbc_sync_idle_id);
579 #endif /* BALSA_USE_THREADS */
580 #if DEBUG
581 g_print("%s id %d, will be set if zero\n", __func__, lbc_sync_idle_id);
582 #endif /* DEBUG */
583 if (!lbc_sync_idle_id)
584 lbc_sync_idle_id =
585 g_idle_add((GSourceFunc) libbalsa_conf_sync_idle_cb, NULL);
586 #if DEBUG
587 g_print("%s id now %d\n", __func__, lbc_sync_idle_id);
588 #endif /* DEBUG */
589 #ifdef BALSA_USE_THREADS
590 G_UNLOCK(lbc_sync_idle_id);
591 #endif /* BALSA_USE_THREADS */
592 }
593