1 /*
2  * prefs.c: configuration routines
3  * Copyright (C) 2002-2004 Saulius Menkevicius
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 2 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, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  * $Id: prefs.c,v 1.35 2004/12/21 15:11:37 bobas Exp $
20  */
21 
22 #include <stdarg.h>
23 #include <errno.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include <glib.h>
29 #include <gtk/gtk.h>
30 
31 #include "main.h"
32 #include "prefs.h"
33 #include "net.h"
34 #include "user.h"
35 #include "util.h"
36 #include "gui_tray.h"
37 
38 #define LOG_PREFS	N_("Preferences")
39 #define PREFS_FILE_NAME	".vqcc.conf.xml"
40 
41 enum prefs_parser_tag {
42 	PREFS_TAG_NO_TAG,
43 	PREFS_TAG_VQCC_GTK,
44 	PREFS_TAG_VQCC_GTK_SETTINGS,
45 	PREFS_TAG_VQCC_GTK_SETTINGS_PREF,
46 	PREFS_TAG_VQCC_GTK_SETTINGS_PREF_ENTRY
47 };
48 
49 struct prefs_parser_state {
50 	enum prefs_parser_tag tag;
51 	gboolean pref_valid;
52 	GString * pref_name;
53 	enum prefs_type pref_type;
54 };
55 
56 struct prefs_value {
57 	gchar * description;
58 	enum prefs_type type;
59 	union prefs_value_union {
60 		gboolean boolean;
61 		gchar * string;
62 		guint integer;
63 		GList * list;
64 	} value;
65 
66 	prefs_validator_func * validator_func;
67 	gpointer validator_user_data;
68 
69 	GHookList change_hook;
70 };
71 
72 /* static variables */
73 static gchar * prefs_file_path;
74 static GHashTable * prefs_hash;
75 static gboolean prefs_values_saved;
76 
77 /* forward references */
78 static gboolean prefs_load();
79 static gboolean prefs_store();
80 
81 /** static routines
82   *****************/
83 
84 /* prefs_free_value:
85  *	frees data in the internal value struct
86  */
87 static void
prefs_free_value(enum prefs_type value_type,union prefs_value_union * value)88 prefs_free_value(
89 	enum prefs_type value_type, union prefs_value_union * value)
90 {
91 	switch(value_type) {
92 	case PREFS_TYPE_LIST:
93 		util_list_free_with_data(value->list, (GDestroyNotify)g_free);
94 		break;
95 	case PREFS_TYPE_STR:
96 		g_free(value->string);
97 		break;
98 	default:
99 		break;
100 	}
101 }
102 
103 /* prefs_destroy_value_cb:
104  *	destroys preference value descriptor in prefs hash table
105  */
106 static void
prefs_destroy_value_cb(struct prefs_value * value)107 prefs_destroy_value_cb(struct prefs_value * value)
108 {
109 	g_assert(value && value->description);
110 
111 	g_free(value->description);
112 	prefs_free_value(value->type, &value->value);
113 	g_hook_list_clear(&value->change_hook);
114 	g_free(value);
115 }
116 
117 /* prefs_init:
118  *	initializes preference module
119  */
120 static void
prefs_init()121 prefs_init()
122 {
123 	prefs_values_saved = FALSE;
124 
125 	/* setup prefs hash */
126 	prefs_hash = g_hash_table_new_full(
127 		(GHashFunc)g_str_hash, (GEqualFunc)g_str_equal,
128 		(GDestroyNotify)g_free, (GDestroyNotify)prefs_destroy_value_cb);
129 }
130 
131 /* prefs_free:
132  *	free any variables we might have allocated in
133  *	config module since the start of application
134  */
135 static void
prefs_free()136 prefs_free()
137 {
138 	g_hash_table_destroy(prefs_hash);
139 	prefs_hash = NULL;
140 
141 	if(prefs_file_path) {
142 		g_free(prefs_file_path);
143 		prefs_file_path = NULL;
144 	}
145 }
146 
147 static void
prefs_event_cb(enum app_event_enum e,gpointer p,gint i)148 prefs_event_cb(
149 	enum app_event_enum e,
150 	gpointer p, gint i)
151 {
152 	switch(e) {
153 	case EVENT_MAIN_INIT:
154 		prefs_init();
155 		break;
156 
157 	case EVENT_MAIN_REGISTER_PREFS:
158 		prefs_register(PREFS_PREFS_AUTO_SAVE, PREFS_TYPE_BOOL,
159 			_("Save settings on exit"), NULL, NULL);
160 		break;
161 
162 	case EVENT_MAIN_PRESET_PREFS:
163 		prefs_set(PREFS_PREFS_AUTO_SAVE, TRUE);
164 		break;
165 
166 	case EVENT_MAIN_LOAD_PREFS:
167 		prefs_load();
168 		break;
169 
170 	case EVENT_MAIN_CLOSE:
171 		/* save configuration */
172 		if(prefs_bool(PREFS_PREFS_AUTO_SAVE))
173 			prefs_store();
174 		prefs_free();
175 		break;
176 
177 	case EVENT_IFACE_RELOAD_CONFIG:
178 		/* force reload of config */
179 		prefs_load();
180 		break;
181 
182 	case EVENT_IFACE_STORE_CONFIG:
183 		prefs_store();
184 		break;
185 	default:
186 		break;
187 	}
188 }
189 
190 /** exported routines
191   *******************/
192 
prefs_register_module(gint argc,gchar ** argv,gchar ** env)193 void prefs_register_module(gint argc, gchar ** argv, gchar ** env)
194 {
195 	const gchar * home;
196 	gchar ** p;
197 
198 	register_event_cb(prefs_event_cb, EVENT_MAIN|EVENT_IFACE);
199 
200 	/* get home path */
201 	prefs_file_path = NULL;
202 
203 	/* check if configuration file was specified */
204 	for(p=argv+1; *p; p++)
205 		if(!g_strcasecmp(*p, "-c") && *(p+1)!=NULL) {
206 			prefs_file_path = g_strdup(*(p+1));
207 			break;
208 		}
209 
210 	/* get & store UNIX home path */
211 	home = g_get_home_dir();
212 	if(!prefs_file_path)
213 		prefs_file_path = g_strdup_printf("%s/"PREFS_FILE_NAME, home ? home: "");
214 }
215 
prefs_register(const gchar * name,enum prefs_type type,const gchar * description,prefs_validator_func * validator_func,gpointer validator_user_data)216 void prefs_register(
217 	const gchar * name,
218 	enum prefs_type type,
219 	const gchar * description,
220 	prefs_validator_func * validator_func,
221 	gpointer validator_user_data)
222 {
223 	struct prefs_value * value;
224 
225 	g_assert(name && description);
226 	g_assert(prefs_hash != NULL);
227 
228 	/* setup preference value struct */
229 	value = g_new(struct prefs_value, 1);
230 
231 	value->description = g_strdup(description);
232 	value->type = type;
233 	switch(type) {
234 	case PREFS_TYPE_BOOL:
235 		value->value.boolean = FALSE;
236 		break;
237 	case PREFS_TYPE_STR:
238 		value->value.string = g_strdup("");
239 		break;
240 	case PREFS_TYPE_UINT:
241 		value->value.integer = 0;
242 		break;
243 	case PREFS_TYPE_LIST:
244 		value->value.list = NULL;
245 		break;
246 	}
247 
248 	value->validator_func = validator_func;
249 	value->validator_user_data = validator_user_data;
250 
251 	g_hook_list_init(&value->change_hook, sizeof(GHook));
252 
253 	/* add it into the prefs hash */
254 	g_hash_table_insert(prefs_hash, g_strdup(name), value);
255 }
256 
257 /* prefs_add_notifier:
258  *	registers pref change notifier
259  */
prefs_add_notifier(const gchar * pref_name,GHookFunc func)260 void prefs_add_notifier(const gchar * pref_name, GHookFunc func)
261 {
262 	GHook * hook;
263 	struct prefs_value * value;
264 	const gchar * value_key;
265 	gboolean found_pref;
266 
267 	found_pref = g_hash_table_lookup_extended(
268 			prefs_hash, pref_name, (gpointer*)&value_key, (gpointer*)&value);
269 	g_assert(found_pref);
270 
271 	/* setup new hook for this preference */
272 	hook = g_hook_alloc(&value->change_hook);
273 	hook->func = func;
274 	hook->data = (gpointer)value_key;
275 
276 	g_hook_prepend(&value->change_hook, hook);
277 }
278 
279 /* prefs_write_xml_pref:
280  *	stores specified value to the disk
281  */
282 static void
prefs_write_xml_pref(const gchar * prefs_name,struct prefs_value * value,FILE * prefs_f)283 prefs_write_xml_pref(const gchar * prefs_name, struct prefs_value * value, FILE * prefs_f)
284 {
285 	gchar * esc;
286 	GList * entry;
287 
288 	g_assert(prefs_name && value && prefs_f);
289 
290 #define WRITE_ESCAPED(format, ...) \
291 	do { \
292 		esc = g_markup_printf_escaped(format, __VA_ARGS__); \
293 		fputs(esc, prefs_f); \
294 		g_free(esc); \
295 	} while(0);
296 
297 	WRITE_ESCAPED("\t\t<pref name=\"%s\"", prefs_name);
298 
299 	switch(value->type) {
300 	case PREFS_TYPE_UINT:
301 		WRITE_ESCAPED(" type=\"uint\">%u</pref>\n", value->value.integer);
302 		break;
303 
304 	case PREFS_TYPE_BOOL:
305 		WRITE_ESCAPED(" type=\"bool\">%s</pref>\n",
306 				value->value.boolean ? "TRUE": "FALSE");
307 		break;
308 
309 	case PREFS_TYPE_STR:
310 		WRITE_ESCAPED(" type=\"string\">%s</pref>\n", value->value.string);
311 		break;
312 
313 	case PREFS_TYPE_LIST:
314 		fputs(" type=\"list\">", prefs_f);
315 		if(value->value.list) {
316 			fputs("\n", prefs_f);
317 
318 			for(entry = value->value.list; entry; entry = entry->next)
319 				WRITE_ESCAPED("\t\t\t<entry>%s</entry>\n",
320 						(const gchar*)entry->data);
321 
322 			fputs("\t\t", prefs_f);
323 		}
324 		fputs("</pref>\n", prefs_f);
325 		break;
326 	}
327 }
328 
329 static void
prefs_write_xml_sort_prefs_cb(const gchar * prefs_name,struct prefs_value * value,GList ** list)330 prefs_write_xml_sort_prefs_cb(const gchar * prefs_name, struct prefs_value * value, GList ** list)
331 {
332 	*list = g_list_insert_sorted(*list, (gpointer)prefs_name, (GCompareFunc)g_utf8_collate);
333 }
334 
335 static void
prefs_write_xml_to(FILE * prefs_f)336 prefs_write_xml_to(FILE * prefs_f)
337 {
338 	GList * sorted, * entry;
339 
340 	fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
341 		"<vqcc_gtk>\n"
342 		"\t<settings>\n",
343 		prefs_f);
344 
345 	/* sort the preferences */
346 	sorted = NULL;
347 	g_hash_table_foreach(prefs_hash, (GHFunc)prefs_write_xml_sort_prefs_cb, (gpointer)&sorted);
348 
349 	for(entry = sorted; entry; entry = entry->next)
350 		prefs_write_xml_pref(
351 			(const gchar*) entry->data,
352 			g_hash_table_lookup(prefs_hash, entry->data),
353 			prefs_f);
354 
355 	g_list_free(sorted);
356 
357 	fputs("\t</settings>\n"
358 		"</vqcc_gtk>",
359 		prefs_f);
360 }
361 
362 
363 /* prefs_store:
364  *	save configuration settings to configuration file
365  */
366 static gboolean
prefs_store()367 prefs_store()
368 {
369 	FILE * cf;
370 
371 	/* open config file */
372 	cf = fopen(prefs_file_path, "wb");
373 	if(!cf) {
374 		log_ferror(_(LOG_PREFS), g_strdup_printf(
375 			_("Cannot save configuration settings to file \"%s\": %s"),
376 			prefs_file_path, strerror(errno)));
377 	} else {
378 		/* store settings to file */
379 		prefs_write_xml_to(cf);
380 		fclose(cf);
381 
382 		log_fevent(_(LOG_PREFS), g_strdup_printf(
383 			_("Configuration settings were stored in \"%s\""),
384 			prefs_file_path));
385 	}
386 
387 	/* config values now are in sync with those on the disk */
388 	prefs_values_saved = TRUE;
389 	raise_event(EVENT_PREFS_SAVED, NULL, 0);
390 
391 	return TRUE;
392 }
393 
394 static gboolean
prefs_get_type_by_name(const gchar * type_name,enum prefs_type * type)395 prefs_get_type_by_name(const gchar * type_name, enum prefs_type * type)
396 {
397 	gboolean found = TRUE;
398 
399 	if(!g_utf8_collate(type_name, "uint"))
400 		*type = PREFS_TYPE_UINT;
401 	else if(!g_utf8_collate(type_name, "bool"))
402 		*type = PREFS_TYPE_BOOL;
403 	else if(!g_utf8_collate(type_name, "string"))
404 		*type = PREFS_TYPE_STR;
405 	else if(!g_utf8_collate(type_name, "list"))
406 		*type = PREFS_TYPE_LIST;
407 	else
408 		found = FALSE;
409 
410 	return found;
411 }
412 
413 static void
prefs_load_xml_start_element(GMarkupParseContext * context,const gchar * element,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)414 prefs_load_xml_start_element(
415 	GMarkupParseContext * context,
416 	const gchar * element,
417 	const gchar ** attribute_names, const gchar ** attribute_values,
418 	gpointer user_data,
419 	GError ** error)
420 {
421 	struct prefs_parser_state * state = (struct prefs_parser_state*)user_data;
422 
423 	if(!g_utf8_collate(element, "vqcc_gtk") && state->tag==PREFS_TAG_NO_TAG) {
424 		state->tag = PREFS_TAG_VQCC_GTK;
425 	}
426 	else if(!g_utf8_collate(element, "settings") && state->tag==PREFS_TAG_VQCC_GTK) {
427 		state->tag = PREFS_TAG_VQCC_GTK_SETTINGS;
428 	}
429 	else if(!g_utf8_collate(element, "pref") && state->tag==PREFS_TAG_VQCC_GTK_SETTINGS) {
430 		struct prefs_value * val;
431 		const gchar ** attr, ** value;
432 		const gchar * prefs_type_attr;
433 
434 		state->tag = PREFS_TAG_VQCC_GTK_SETTINGS_PREF;
435 
436 		/* fetch pref attributes */
437 		g_string_assign(state->pref_name, "");
438 		for(attr = attribute_names, value = attribute_values; *attr; attr++, value++) {
439 			if(!g_utf8_collate(*attr, "name"))
440 				g_string_assign(state->pref_name, *value);
441 
442 			if(!g_utf8_collate(*attr, "type"))
443 				prefs_type_attr = *value;
444 		}
445 
446 		/* check if we have a valid preference name and type */
447 		state->pref_valid = FALSE;
448 		val = (struct prefs_value*)g_hash_table_lookup(prefs_hash, state->pref_name->str);
449 
450 		if(val && prefs_get_type_by_name(prefs_type_attr, &state->pref_type)) {
451 			if(state->pref_type==val->type)
452 				state->pref_valid = TRUE;
453 		}
454 	}
455 	else if(!g_utf8_collate(element, "entry") && state->tag==PREFS_TAG_VQCC_GTK_SETTINGS_PREF) {
456 		state->tag = PREFS_TAG_VQCC_GTK_SETTINGS_PREF_ENTRY;
457 	}
458 }
459 
460 static void
prefs_load_xml_end_element(GMarkupParseContext * context,const gchar * element,gpointer user_data,GError ** error)461 prefs_load_xml_end_element(
462 	GMarkupParseContext * context,
463 	const gchar * element,
464 	gpointer user_data,
465 	GError ** error)
466 {
467 	struct prefs_parser_state * state = (struct prefs_parser_state*)user_data;
468 
469 	if(!g_utf8_collate(element, "vqcc_gtk")
470 			&& state->tag==PREFS_TAG_VQCC_GTK) {
471 		state->tag = PREFS_TAG_NO_TAG;
472 	}
473 	else if(!g_utf8_collate(element, "settings")
474 			&& state->tag==PREFS_TAG_VQCC_GTK_SETTINGS) {
475 		state->tag = PREFS_TAG_VQCC_GTK;
476 	}
477 	else if(!g_utf8_collate(element, "pref")
478 			&& state->tag==PREFS_TAG_VQCC_GTK_SETTINGS_PREF) {
479 		state->tag = PREFS_TAG_VQCC_GTK_SETTINGS;
480 	}
481 	else if(!g_utf8_collate(element, "entry")
482 			&& state->tag==PREFS_TAG_VQCC_GTK_SETTINGS_PREF_ENTRY) {
483 		state->tag = PREFS_TAG_VQCC_GTK_SETTINGS_PREF;
484 	}
485 }
486 
487 static void
prefs_load_xml_text(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)488 prefs_load_xml_text(
489 	GMarkupParseContext * context,
490 	const gchar * text,
491 	gsize text_len,
492 	gpointer user_data,
493 	GError ** error)
494 {
495 	struct prefs_parser_state * state = (struct prefs_parser_state*)user_data;
496 
497 	if(!state->pref_valid)
498 		return;
499 
500 	if(state->tag==PREFS_TAG_VQCC_GTK_SETTINGS_PREF) {
501 		gchar * stripped;
502 		guint uint_read;
503 
504 		switch(state->pref_type) {
505 		case PREFS_TYPE_UINT:
506 			if(sscanf(text, "%u", &uint_read)==1) {
507 				prefs_set(state->pref_name->str, uint_read);
508 			} else {
509 				g_set_error(error, G_MARKUP_ERROR,
510 					G_MARKUP_ERROR_INVALID_CONTENT,
511 					_("Could not read value for preference \"%s\""),
512 						state->pref_name->str);
513 			}
514 			break;
515 		case PREFS_TYPE_BOOL:
516 			stripped = g_strstrip(g_strdup(text));
517 			if(!g_ascii_strncasecmp(text, "FALSE", sizeof("FALSE")-1)) {
518 				prefs_set(state->pref_name->str, FALSE);
519 			}
520 			else if(!g_ascii_strncasecmp(text, "TRUE", sizeof("TRUE")-1)) {
521 				prefs_set(state->pref_name->str, TRUE);
522 			}
523 			else {
524 				g_set_error(error, G_MARKUP_ERROR,
525 					G_MARKUP_ERROR_INVALID_CONTENT,
526 					_("Could not read value for preference \"%s\""),
527 						state->pref_name->str);
528 			}
529 			g_free(stripped);
530 			break;
531 		case PREFS_TYPE_STR:
532 			prefs_set(state->pref_name->str, text);
533 			break;
534 		default:
535 			break;
536 		}
537 	} else if(state->tag==PREFS_TAG_VQCC_GTK_SETTINGS_PREF_ENTRY
538 			&& state->pref_type==PREFS_TYPE_LIST) {
539 		/* read list data */
540 		prefs_list_add(state->pref_name->str, text);
541 	}
542 }
543 
544 static void
prefs_free_parser_state(struct prefs_parser_state * state)545 prefs_free_parser_state(struct prefs_parser_state * state)
546 {
547 	g_string_free(state->pref_name, TRUE);
548 	g_free(state);
549 }
550 
551 /* prefs_load_xml_from:
552  *	creates GMarkupParserContext for parsing the prefs
553  */
554 static void
prefs_load_xml_from(const gchar * prefs_filename,FILE * prefs_f)555 prefs_load_xml_from(const gchar * prefs_filename, FILE * prefs_f)
556 {
557 	GMarkupParser parser;
558 	GMarkupParseContext * context;
559 	gchar * buf;
560 	gsize buf_bytes;
561 	GError * error = NULL;
562 	struct prefs_parser_state * state;
563 
564 	parser.start_element = prefs_load_xml_start_element;
565 	parser.end_element = prefs_load_xml_end_element;
566 	parser.text = prefs_load_xml_text;
567 	parser.passthrough = NULL;
568 	parser.error = NULL;
569 
570 	state = g_new(struct prefs_parser_state, 1);
571 	state->tag = PREFS_TAG_NO_TAG;
572 	state->pref_name = g_string_new(NULL);
573 
574 	context = g_markup_parse_context_new(
575 		&parser, 0, (gpointer)state,
576 		(GDestroyNotify)prefs_free_parser_state);
577 
578 	buf = g_malloc(4096);
579 
580 	while(!feof(prefs_f)) {
581 		buf_bytes = fread(buf, 1, 4096, prefs_f);
582 		if(buf_bytes < 4096 && ferror(prefs_f)) {
583 			log_ferror(_(LOG_PREFS),
584 				g_strdup_printf(_("Error while reading "
585 						"configuration file \"%s\": %s"),
586 					prefs_filename, strerror(errno)));
587 
588 			goto bail_out;
589 		}
590 
591 		if(buf_bytes)
592 			if(g_markup_parse_context_parse(context, buf, buf_bytes, &error)==FALSE)
593 				goto parse_error;
594 	}
595 
596 	if(g_markup_parse_context_end_parse(context, &error)==TRUE) {
597 		/* parse ok */
598 		goto bail_out;
599 	}
600 
601 parse_error:
602 	log_ferror(_(LOG_PREFS),
603 		g_strdup_printf(_("Error parsing configuration from \"%s\": %s"),
604 			prefs_filename, error->message));
605 
606 	g_error_free(error);
607 
608 bail_out:
609 	g_free(buf);
610 	g_markup_parse_context_free(context);
611 
612 }
613 
614 /* prefs_load:
615  *	load configuration settings from file
616  */
617 static gboolean
prefs_load()618 prefs_load()
619 {
620 	FILE * config_file;
621 
622 	/* parse main file */
623 	config_file = fopen(prefs_file_path, "r");
624 	if(!config_file) {
625 		log_ferror(_(LOG_PREFS), g_strdup_printf(
626 			_("Cannot open configuration file \"%s\" for reading: %s"),
627 			prefs_file_path, strerror(errno)));
628 		return FALSE;
629 	}
630 
631 	prefs_load_xml_from(prefs_file_path, config_file);
632 	fclose(config_file);
633 
634 	/* config values are in sync with those on disk */
635 	prefs_values_saved = TRUE;
636 
637 	return TRUE;
638 }
639 
640 gboolean
prefs_in_sync()641 prefs_in_sync()
642 {
643 	return prefs_values_saved;
644 }
645 
646 const gchar *
prefs_description(const gchar * prefs_name)647 prefs_description(const gchar * prefs_name)
648 {
649 	struct prefs_value * value;
650 	value = g_hash_table_lookup(prefs_hash, prefs_name);
651 
652 	g_return_val_if_fail(value!=NULL, "<Unknown preference>");
653 
654 	return value->description;
655 }
656 
prefs_set(const gchar * prefs_name,...)657 void prefs_set(const gchar * prefs_name, ...)
658 {
659 	va_list ap;
660 	struct prefs_value * value;
661 	guint new_guint;
662 	gboolean new_gboolean;
663 	const gchar * new_string;
664 
665 	va_start(ap, prefs_name);
666 
667 	value = g_hash_table_lookup(prefs_hash, prefs_name);
668 	if(value) {
669 		gboolean validated;
670 		union prefs_value_union value_backup;
671 
672 		/* backup the current value, if the validator decides the new one is invalid */
673 		value_backup = value->value;
674 
675 		/* check if the old value hasn't changed
676 		 * and if so, set the new value */
677 		switch(value->type) {
678 		case PREFS_TYPE_UINT:
679 			new_guint = va_arg(ap, guint);
680 			if(value->value.integer!=new_guint)
681 				value->value.integer = new_guint;
682 			else
683 				goto no_change;
684 			break;
685 		case PREFS_TYPE_BOOL:
686 			new_gboolean = va_arg(ap, gboolean);
687 			if(value->value.boolean!=new_gboolean)
688 				value->value.boolean = new_gboolean;
689 			else
690 				goto no_change;
691 			break;
692 		case PREFS_TYPE_STR:
693 			new_string = va_arg(ap, gchar*);
694 			if(strcmp(value->value.string, new_string))
695 				value->value.string = g_strdup(new_string);
696 			else
697 				goto no_change;
698 			break;
699 		case PREFS_TYPE_LIST:
700 			value->value.list = util_list_copy_with_data(
701 				va_arg(ap, GList *),
702 				(util_list_data_copy_func_t*)g_strdup);
703 			break;
704 		}
705 
706 		/* invoke validator to check new value */
707 		validated = value->validator_func != NULL
708 			? value->validator_func(prefs_name, value->validator_user_data)
709 			: TRUE;
710 
711 		if(validated) {
712 			/* free backup data */
713 			prefs_free_value(value->type, &value_backup);
714 
715 			/* notify that we've changed to a new value */
716 			g_hook_list_invoke(&value->change_hook, FALSE);
717 			raise_event(EVENT_PREFS_CHANGED, (gpointer)prefs_name, 0);
718 
719 			/* preferences are not in sync with those on the disk */
720 			prefs_values_saved = FALSE;
721 		} else {
722 			/* restore backup value
723 			 */
724 			prefs_free_value(value->type, &value->value);
725 			value->value = value_backup;
726 		}
727 	} else {
728 		log_ferror(_(LOG_PREFS), g_strdup_printf(
729 			_("Unknown preference value \"%s\""), prefs_name));
730 	}
731 
732 no_change:
733 	va_end(ap);
734 }
735 
prefs_int(const gchar * prefs_name)736 guint prefs_int(const gchar * prefs_name)
737 {
738 	struct prefs_value * value = g_hash_table_lookup(prefs_hash, prefs_name);
739 	g_assert(value!=NULL && value->type==PREFS_TYPE_UINT);
740 
741 	return value->value.integer;
742 }
743 
prefs_bool(const gchar * prefs_name)744 gboolean prefs_bool(const gchar * prefs_name)
745 {
746 	struct prefs_value * value = g_hash_table_lookup(prefs_hash, prefs_name);
747 	g_assert(value!=NULL && value->type==PREFS_TYPE_BOOL);
748 
749 	return value->value.boolean;
750 }
751 
prefs_str(const gchar * prefs_name)752 const gchar * prefs_str(const gchar * prefs_name)
753 {
754 	struct prefs_value * value = g_hash_table_lookup(prefs_hash, prefs_name);
755 	g_assert(value!=NULL && value->type==PREFS_TYPE_STR);
756 
757 	return value->value.string;
758 }
759 
prefs_list(const gchar * prefs_name)760 GList * prefs_list(const gchar * prefs_name)
761 {
762 	struct prefs_value * value = g_hash_table_lookup(prefs_hash, prefs_name);
763 	g_assert(value!=NULL && value->type==PREFS_TYPE_LIST);
764 
765 	return value->value.list;
766 }
767 
prefs_list_add(const gchar * prefs_name,const gchar * string)768 void prefs_list_add(const gchar * prefs_name, const gchar * string)
769 {
770 	struct prefs_value * value = g_hash_table_lookup(prefs_hash, prefs_name);
771 	g_assert(value!=NULL && value->type==PREFS_TYPE_LIST);
772 
773 	value->value.list = g_list_prepend(value->value.list, g_strdup(string));
774 
775 	/* notify that we've changed to a new value */
776 	g_hook_list_invoke(&value->change_hook, FALSE);
777 	raise_event(EVENT_PREFS_CHANGED, (gpointer)prefs_name, 0);
778 }
779 
prefs_list_add_unique(const gchar * prefs_name,const gchar * string)780 void prefs_list_add_unique(const gchar * prefs_name, const gchar * string)
781 {
782 	if(!prefs_list_contains(prefs_name, string))
783 		prefs_list_add(prefs_name, string);
784 }
785 
prefs_list_remove(const gchar * prefs_name,const gchar * string)786 gboolean prefs_list_remove(const gchar * prefs_name, const gchar * string)
787 {
788 	GList * entry;
789 
790 	struct prefs_value * value = g_hash_table_lookup(prefs_hash, prefs_name);
791 	g_assert(value!=NULL && value->type==PREFS_TYPE_LIST);
792 
793 	for(entry = value->value.list; entry; entry = entry->next)
794 		if(!g_utf8_collate((const gchar*)entry->data, string)) {
795 			g_free(entry->data);
796 			value->value.list = g_list_delete_link(value->value.list, entry);
797 
798 			/* notify that we've changed to a new value */
799 			g_hook_list_invoke(&value->change_hook, FALSE);
800 			raise_event(EVENT_PREFS_CHANGED, (gpointer)prefs_name, 0);
801 
802 			return TRUE;
803 		}
804 
805 	return FALSE;
806 }
807 
prefs_list_clear(const gchar * prefs_name)808 void prefs_list_clear(const gchar * prefs_name)
809 {
810 	struct prefs_value * value = g_hash_table_lookup(prefs_hash, prefs_name);
811 	g_assert(value!=NULL && value->type==PREFS_TYPE_LIST);
812 
813 	util_list_free_with_data(value->value.list, (GDestroyNotify)g_free);
814 	value->value.list = NULL;
815 
816 	/* notify that we've changed to a new value */
817 	g_hook_list_invoke(&value->change_hook, FALSE);
818 	raise_event(EVENT_PREFS_CHANGED, (gpointer)prefs_name, 0);
819 }
820 
prefs_list_contains(const gchar * prefs_name,const gchar * string)821 gboolean prefs_list_contains(const gchar * prefs_name, const gchar * string)
822 {
823 	GList * entry;
824 	struct prefs_value * value = g_hash_table_lookup(prefs_hash, prefs_name);
825 	g_assert(value!=NULL && value->type==PREFS_TYPE_LIST);
826 
827 	for(entry = value->value.list; entry!=NULL; entry = entry->next)
828 		if(!g_utf8_collate((const gchar *)entry->data, string))
829 			return TRUE;
830 	return FALSE;
831 }
832