1 /*
2  * purple
3  *
4  * Purple is the legal property of its developers, whose names are too numerous
5  * to list here.  Please refer to the COPYRIGHT file distributed with this
6  * source distribution.
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21  *
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 
28 #include <string.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <glib.h>
34 #include "internal.h"
35 #include "debug.h"
36 #include "glibcompat.h"
37 #include "prefs.h"
38 #include "util.h"
39 
40 #ifdef _WIN32
41 #include "win32dep.h"
42 #endif
43 
44 static PurplePrefsUiOps *prefs_ui_ops = NULL;
45 
46 struct _PurplePrefCallbackData {
47 	PurplePrefCallback func;
48 	gpointer data;
49 	guint id;
50 	void *handle;
51 	void *ui_data;
52 	char *name;
53 };
54 
55 /* TODO: This should use PurpleValues? */
56 struct purple_pref {
57 	PurplePrefType type;
58 	char *name;
59 	union {
60 		gpointer generic;
61 		gboolean boolean;
62 		int integer;
63 		char *string;
64 		GList *stringlist;
65 	} value;
66 	GSList *callbacks;
67 	struct purple_pref *parent;
68 	struct purple_pref *sibling;
69 	struct purple_pref *first_child;
70 };
71 
72 
73 static struct purple_pref prefs = {
74 	PURPLE_PREF_NONE,
75 	NULL,
76 	{ NULL },
77 	NULL,
78 	NULL,
79 	NULL,
80 	NULL
81 };
82 
83 static GHashTable *prefs_hash = NULL;
84 static guint       save_timer = 0;
85 static gboolean    prefs_loaded = FALSE;
86 static GSList     *ui_callbacks = NULL;
87 
88 #define PURPLE_PREFS_UI_OP_CALL(member, ...) \
89 	{ \
90 		PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops(); \
91 		if (uiop && uiop->member) { \
92 			uiop->member(__VA_ARGS__); \
93 			return; \
94 		} \
95 	}
96 
97 #define PURPLE_PREFS_UI_OP_CALL_RETURN(member, ...) \
98 	{ \
99 		PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops(); \
100 		if (uiop && uiop->member) { \
101 			return uiop->member(__VA_ARGS__); \
102 		} \
103 	}
104 
105 
106 /*********************************************************************
107  * Private utility functions                                         *
108  *********************************************************************/
109 
110 static struct
find_pref(const char * name)111 purple_pref *find_pref(const char *name)
112 {
113 	g_return_val_if_fail(name != NULL && name[0] == '/', NULL);
114 
115 	if (name[1] == '\0')
116 		return &prefs;
117 	else
118 	{
119 		/* When we're initializing, the debug system is
120 		 * initialized before the prefs system, but debug
121 		 * calls will end up calling prefs functions, so we
122 		 * need to deal cleanly here. */
123 		if (prefs_hash)
124 			return g_hash_table_lookup(prefs_hash, name);
125 		else
126 			return NULL;
127 	}
128 }
129 
130 
131 /*********************************************************************
132  * Writing to disk                                                   *
133  *********************************************************************/
134 
135 /*
136  * This function recursively creates the xmlnode tree from the prefs
137  * tree structure.  Yay recursion!
138  */
139 static void
pref_to_xmlnode(xmlnode * parent,struct purple_pref * pref)140 pref_to_xmlnode(xmlnode *parent, struct purple_pref *pref)
141 {
142 	xmlnode *node, *childnode;
143 	struct purple_pref *child;
144 	char buf[21];
145 	GList *cur;
146 
147 	/* Create a new node */
148 	node = xmlnode_new_child(parent, "pref");
149 	xmlnode_set_attrib(node, "name", pref->name);
150 
151 	/* Set the type of this node (if type == PURPLE_PREF_NONE then do nothing) */
152 	if (pref->type == PURPLE_PREF_INT) {
153 		xmlnode_set_attrib(node, "type", "int");
154 		g_snprintf(buf, sizeof(buf), "%d", pref->value.integer);
155 		xmlnode_set_attrib(node, "value", buf);
156 	}
157 	else if (pref->type == PURPLE_PREF_STRING) {
158 		xmlnode_set_attrib(node, "type", "string");
159 		xmlnode_set_attrib(node, "value", pref->value.string ? pref->value.string : "");
160 	}
161 	else if (pref->type == PURPLE_PREF_STRING_LIST) {
162 		xmlnode_set_attrib(node, "type", "stringlist");
163 		for (cur = pref->value.stringlist; cur != NULL; cur = cur->next)
164 		{
165 			childnode = xmlnode_new_child(node, "item");
166 			xmlnode_set_attrib(childnode, "value", cur->data ? cur->data : "");
167 		}
168 	}
169 	else if (pref->type == PURPLE_PREF_PATH) {
170 		char *encoded = g_filename_to_utf8(pref->value.string ? pref->value.string : "", -1, NULL, NULL, NULL);
171 		xmlnode_set_attrib(node, "type", "path");
172 		xmlnode_set_attrib(node, "value", encoded);
173 		g_free(encoded);
174 	}
175 	else if (pref->type == PURPLE_PREF_PATH_LIST) {
176 		xmlnode_set_attrib(node, "type", "pathlist");
177 		for (cur = pref->value.stringlist; cur != NULL; cur = cur->next)
178 		{
179 			char *encoded = g_filename_to_utf8(cur->data ? cur->data : "", -1, NULL, NULL, NULL);
180 			childnode = xmlnode_new_child(node, "item");
181 			xmlnode_set_attrib(childnode, "value", encoded);
182 			g_free(encoded);
183 		}
184 	}
185 	else if (pref->type == PURPLE_PREF_BOOLEAN) {
186 		xmlnode_set_attrib(node, "type", "bool");
187 		g_snprintf(buf, sizeof(buf), "%d", pref->value.boolean);
188 		xmlnode_set_attrib(node, "value", buf);
189 	}
190 
191 	/* All My Children */
192 	for (child = pref->first_child; child != NULL; child = child->sibling)
193 		pref_to_xmlnode(node, child);
194 }
195 
196 static xmlnode *
prefs_to_xmlnode(void)197 prefs_to_xmlnode(void)
198 {
199 	xmlnode *node;
200 	struct purple_pref *pref, *child;
201 
202 	pref = &prefs;
203 
204 	/* Create the root preference node */
205 	node = xmlnode_new("pref");
206 	xmlnode_set_attrib(node, "version", "1");
207 	xmlnode_set_attrib(node, "name", "/");
208 
209 	/* All My Children */
210 	for (child = pref->first_child; child != NULL; child = child->sibling)
211 		pref_to_xmlnode(node, child);
212 
213 	return node;
214 }
215 
216 static void
sync_prefs(void)217 sync_prefs(void)
218 {
219 	xmlnode *node;
220 	char *data;
221 
222 	if (!prefs_loaded)
223 	{
224 		/*
225 		 * TODO: Call schedule_prefs_save()?  Ideally we wouldn't need to.
226 		 * (prefs.xml should be loaded when purple_prefs_init is called)
227 		 */
228 		purple_debug_error("prefs", "Attempted to save prefs before "
229 						 "they were read!\n");
230 		return;
231 	}
232 
233 	PURPLE_PREFS_UI_OP_CALL(save);
234 
235 	node = prefs_to_xmlnode();
236 	data = xmlnode_to_formatted_str(node, NULL);
237 	purple_util_write_data_to_file("prefs.xml", data, -1);
238 	g_free(data);
239 	xmlnode_free(node);
240 }
241 
242 static gboolean
save_cb(gpointer data)243 save_cb(gpointer data)
244 {
245 	sync_prefs();
246 	save_timer = 0;
247 	return FALSE;
248 }
249 
250 static void
schedule_prefs_save(void)251 schedule_prefs_save(void)
252 {
253 	PURPLE_PREFS_UI_OP_CALL(schedule_save);
254 
255 	if (save_timer == 0)
256 		save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
257 }
258 
259 
260 /*********************************************************************
261  * Reading from disk                                                 *
262  *********************************************************************/
263 
264 static GList *prefs_stack = NULL;
265 
266 static void
prefs_start_element_handler(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)267 prefs_start_element_handler (GMarkupParseContext *context,
268 		const gchar *element_name,
269 		const gchar **attribute_names,
270 		const gchar **attribute_values,
271 		gpointer user_data,
272 		GError **error)
273 {
274 	PurplePrefType pref_type = PURPLE_PREF_NONE;
275 	int i;
276 	const char *pref_name = NULL, *pref_value = NULL;
277 	GString *pref_name_full;
278 	GList *tmp;
279 
280 	if(!purple_strequal(element_name, "pref") &&
281 	   !purple_strequal(element_name, "item"))
282 		return;
283 
284 	for(i = 0; attribute_names[i]; i++) {
285 		if(purple_strequal(attribute_names[i], "name")) {
286 			pref_name = attribute_values[i];
287 		} else if(purple_strequal(attribute_names[i], "type")) {
288 			if(purple_strequal(attribute_values[i], "bool"))
289 				pref_type = PURPLE_PREF_BOOLEAN;
290 			else if(purple_strequal(attribute_values[i], "int"))
291 				pref_type = PURPLE_PREF_INT;
292 			else if(purple_strequal(attribute_values[i], "string"))
293 				pref_type = PURPLE_PREF_STRING;
294 			else if(purple_strequal(attribute_values[i], "stringlist"))
295 				pref_type = PURPLE_PREF_STRING_LIST;
296 			else if(purple_strequal(attribute_values[i], "path"))
297 				pref_type = PURPLE_PREF_PATH;
298 			else if(purple_strequal(attribute_values[i], "pathlist"))
299 				pref_type = PURPLE_PREF_PATH_LIST;
300 			else
301 				return;
302 		} else if(purple_strequal(attribute_names[i], "value")) {
303 			pref_value = attribute_values[i];
304 		}
305 	}
306 
307 	if ((pref_type == PURPLE_PREF_BOOLEAN || pref_type == PURPLE_PREF_INT) &&
308 			pref_value == NULL) {
309 		/* Missing a value attribute */
310 		return;
311 	}
312 
313 	if(purple_strequal(element_name, "item")) {
314 		struct purple_pref *pref;
315 
316 		pref_name_full = g_string_new("");
317 
318 		for(tmp = prefs_stack; tmp; tmp = tmp->next) {
319 			pref_name_full = g_string_prepend(pref_name_full, tmp->data);
320 			pref_name_full = g_string_prepend_c(pref_name_full, '/');
321 		}
322 
323 		pref = find_pref(pref_name_full->str);
324 
325 		if(pref) {
326 			if(pref->type == PURPLE_PREF_STRING_LIST) {
327 				pref->value.stringlist = g_list_append(pref->value.stringlist,
328 						g_strdup(pref_value));
329 			} else if(pref->type == PURPLE_PREF_PATH_LIST) {
330 				pref->value.stringlist = g_list_append(pref->value.stringlist,
331 						g_filename_from_utf8(pref_value, -1, NULL, NULL, NULL));
332 			}
333 		}
334 		g_string_free(pref_name_full, TRUE);
335 	} else {
336 		char *decoded;
337 
338 		if(!pref_name || purple_strequal(pref_name, "/"))
339 			return;
340 
341 		pref_name_full = g_string_new(pref_name);
342 
343 		for(tmp = prefs_stack; tmp; tmp = tmp->next) {
344 			pref_name_full = g_string_prepend_c(pref_name_full, '/');
345 			pref_name_full = g_string_prepend(pref_name_full, tmp->data);
346 		}
347 
348 		pref_name_full = g_string_prepend_c(pref_name_full, '/');
349 
350 		switch(pref_type) {
351 			case PURPLE_PREF_NONE:
352 				purple_prefs_add_none(pref_name_full->str);
353 				break;
354 			case PURPLE_PREF_BOOLEAN:
355 				purple_prefs_set_bool(pref_name_full->str, atoi(pref_value));
356 				break;
357 			case PURPLE_PREF_INT:
358 				purple_prefs_set_int(pref_name_full->str, atoi(pref_value));
359 				break;
360 			case PURPLE_PREF_STRING:
361 				purple_prefs_set_string(pref_name_full->str, pref_value);
362 				break;
363 			case PURPLE_PREF_STRING_LIST:
364 				purple_prefs_set_string_list(pref_name_full->str, NULL);
365 				break;
366 			case PURPLE_PREF_PATH:
367 				if (pref_value) {
368 					decoded = g_filename_from_utf8(pref_value, -1, NULL, NULL, NULL);
369 					purple_prefs_set_path(pref_name_full->str, decoded);
370 					g_free(decoded);
371 				} else {
372 					purple_prefs_set_path(pref_name_full->str, NULL);
373 				}
374 				break;
375 			case PURPLE_PREF_PATH_LIST:
376 				purple_prefs_set_path_list(pref_name_full->str, NULL);
377 				break;
378 		}
379 		prefs_stack = g_list_prepend(prefs_stack, g_strdup(pref_name));
380 		g_string_free(pref_name_full, TRUE);
381 	}
382 }
383 
384 static void
prefs_end_element_handler(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)385 prefs_end_element_handler(GMarkupParseContext *context,
386 						  const gchar *element_name,
387 						  gpointer user_data, GError **error)
388 {
389 	if(prefs_stack && purple_strequal(element_name, "pref")) {
390 		g_free(prefs_stack->data);
391 		prefs_stack = g_list_delete_link(prefs_stack, prefs_stack);
392 	}
393 }
394 
395 static GMarkupParser prefs_parser = {
396 	prefs_start_element_handler,
397 	prefs_end_element_handler,
398 	NULL,
399 	NULL,
400 	NULL
401 };
402 
403 gboolean
purple_prefs_load()404 purple_prefs_load()
405 {
406 	gchar *filename;
407 	gchar *contents = NULL;
408 	gsize length;
409 	GMarkupParseContext *context;
410 	GError *error = NULL;
411 
412 	PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
413 
414 	if (uiop && uiop->load) {
415 		prefs_loaded = TRUE;
416 		return uiop->load();
417 	}
418 
419 	filename = g_build_filename(purple_user_dir(), "prefs.xml", NULL);
420 
421 	if (!filename) {
422 		prefs_loaded = TRUE;
423 		return FALSE;
424 	}
425 
426 	purple_debug_info("prefs", "Reading %s\n", filename);
427 
428 	if(!g_file_get_contents(filename, &contents, &length, &error)) {
429 #ifdef _WIN32
430 		gchar *common_appdata = wpurple_get_special_folder(CSIDL_COMMON_APPDATA);
431 #endif
432 		g_free(filename);
433 		g_error_free(error);
434 
435 		error = NULL;
436 
437 #ifdef _WIN32
438 		filename = g_build_filename(common_appdata ? common_appdata : "", "purple", "prefs.xml", NULL);
439 		g_free(common_appdata);
440 #else
441 		filename = g_build_filename(SYSCONFDIR, "purple", "prefs.xml", NULL);
442 #endif
443 
444 		purple_debug_info("prefs", "Reading %s\n", filename);
445 
446 		if (!g_file_get_contents(filename, &contents, &length, &error)) {
447 			purple_debug_error("prefs", "Error reading prefs: %s\n",
448 					error->message);
449 			g_error_free(error);
450 			g_free(filename);
451 			prefs_loaded = TRUE;
452 
453 			return FALSE;
454 		}
455 	}
456 
457 	context = g_markup_parse_context_new(&prefs_parser, 0, NULL, NULL);
458 
459 	if(!g_markup_parse_context_parse(context, contents, length, NULL)) {
460 		g_markup_parse_context_free(context);
461 		g_free(contents);
462 		g_free(filename);
463 		prefs_loaded = TRUE;
464 
465 		return FALSE;
466 	}
467 
468 	if(!g_markup_parse_context_end_parse(context, NULL)) {
469 		purple_debug_error("prefs", "Error parsing %s\n", filename);
470 		g_markup_parse_context_free(context);
471 		g_free(contents);
472 		g_free(filename);
473 		prefs_loaded = TRUE;
474 
475 		return FALSE;
476 	}
477 
478 	purple_debug_info("prefs", "Finished reading %s\n", filename);
479 	g_markup_parse_context_free(context);
480 	g_free(contents);
481 	g_free(filename);
482 	prefs_loaded = TRUE;
483 
484 	return TRUE;
485 }
486 
487 
488 
489 static void
prefs_save_cb(const char * name,PurplePrefType type,gconstpointer val,gpointer user_data)490 prefs_save_cb(const char *name, PurplePrefType type, gconstpointer val,
491 			  gpointer user_data)
492 {
493 
494 	if(!prefs_loaded)
495 		return;
496 
497 	purple_debug_misc("prefs", "%s changed, scheduling save.\n", name);
498 
499 	schedule_prefs_save();
500 }
501 
502 static char *
get_path_dirname(const char * name)503 get_path_dirname(const char *name)
504 {
505 	char *c, *str;
506 
507 	str = g_strdup(name);
508 
509 	if ((c = strrchr(str, '/')) != NULL) {
510 		*c = '\0';
511 
512 		if (*str == '\0') {
513 			g_free(str);
514 
515 			str = g_strdup("/");
516 		}
517 	}
518 	else {
519 		g_free(str);
520 
521 		str = g_strdup(".");
522 	}
523 
524 	return str;
525 }
526 
527 static char *
get_path_basename(const char * name)528 get_path_basename(const char *name)
529 {
530 	const char *c;
531 
532 	if ((c = strrchr(name, '/')) != NULL)
533 		return g_strdup(c + 1);
534 
535 	return g_strdup(name);
536 }
537 
538 static char *
pref_full_name(struct purple_pref * pref)539 pref_full_name(struct purple_pref *pref)
540 {
541 	GString *name;
542 	struct purple_pref *parent;
543 
544 	if(!pref)
545 		return NULL;
546 
547 	if(pref == &prefs)
548 		return g_strdup("/");
549 
550 	name = g_string_new(pref->name);
551 
552 	for(parent = pref->parent; parent && parent->name; parent = parent->parent) {
553 		name = g_string_prepend_c(name, '/');
554 		name = g_string_prepend(name, parent->name);
555 	}
556 	name = g_string_prepend_c(name, '/');
557 	return g_string_free(name, FALSE);
558 }
559 
560 static struct purple_pref *
find_pref_parent(const char * name)561 find_pref_parent(const char *name)
562 {
563 	char *parent_name = get_path_dirname(name);
564 	struct purple_pref *ret = &prefs;
565 
566 	if(!purple_strequal(parent_name, "/")) {
567 		ret = find_pref(parent_name);
568 	}
569 
570 	g_free(parent_name);
571 	return ret;
572 }
573 
574 static void
free_pref_value(struct purple_pref * pref)575 free_pref_value(struct purple_pref *pref)
576 {
577 	switch(pref->type) {
578 		case PURPLE_PREF_BOOLEAN:
579 			pref->value.boolean = FALSE;
580 			break;
581 		case PURPLE_PREF_INT:
582 			pref->value.integer = 0;
583 			break;
584 		case PURPLE_PREF_STRING:
585 		case PURPLE_PREF_PATH:
586 			g_free(pref->value.string);
587 			pref->value.string = NULL;
588 			break;
589 		case PURPLE_PREF_STRING_LIST:
590 		case PURPLE_PREF_PATH_LIST:
591 			g_list_free_full(pref->value.stringlist, (GDestroyNotify)g_free);
592 			break;
593 		case PURPLE_PREF_NONE:
594 			break;
595 	}
596 }
597 
598 static struct purple_pref *
add_pref(PurplePrefType type,const char * name)599 add_pref(PurplePrefType type, const char *name)
600 {
601 	struct purple_pref *parent;
602 	struct purple_pref *me;
603 	struct purple_pref *sibling;
604 	char *my_name;
605 
606 	parent = find_pref_parent(name);
607 
608 	if(!parent)
609 		return NULL;
610 
611 	my_name = get_path_basename(name);
612 
613 	for(sibling = parent->first_child; sibling; sibling = sibling->sibling) {
614 		if(purple_strequal(sibling->name, my_name)) {
615 			g_free(my_name);
616 			return NULL;
617 		}
618 	}
619 
620 	me = g_new0(struct purple_pref, 1);
621 	me->type = type;
622 	me->name = my_name;
623 
624 	me->parent = parent;
625 	if(parent->first_child) {
626 		/* blatant abuse of a for loop */
627 		for(sibling = parent->first_child; sibling->sibling;
628 				sibling = sibling->sibling);
629 		sibling->sibling = me;
630 	} else {
631 		parent->first_child = me;
632 	}
633 
634 	g_hash_table_insert(prefs_hash, g_strdup(name), (gpointer)me);
635 
636 	return me;
637 }
638 
639 void
purple_prefs_add_none(const char * name)640 purple_prefs_add_none(const char *name)
641 {
642 	PURPLE_PREFS_UI_OP_CALL(add_none, name);
643 
644 	add_pref(PURPLE_PREF_NONE, name);
645 }
646 
647 void
purple_prefs_add_bool(const char * name,gboolean value)648 purple_prefs_add_bool(const char *name, gboolean value)
649 {
650 	struct purple_pref *pref;
651 
652 	PURPLE_PREFS_UI_OP_CALL(add_bool, name, value);
653 
654 	pref = add_pref(PURPLE_PREF_BOOLEAN, name);
655 
656 	if(!pref)
657 		return;
658 
659 	pref->value.boolean = value;
660 }
661 
662 void
purple_prefs_add_int(const char * name,int value)663 purple_prefs_add_int(const char *name, int value)
664 {
665 	struct purple_pref *pref;
666 
667 	PURPLE_PREFS_UI_OP_CALL(add_int, name, value);
668 
669 	pref = add_pref(PURPLE_PREF_INT, name);
670 
671 	if(!pref)
672 		return;
673 
674 	pref->value.integer = value;
675 }
676 
677 void
purple_prefs_add_string(const char * name,const char * value)678 purple_prefs_add_string(const char *name, const char *value)
679 {
680 	struct purple_pref *pref;
681 
682 	if(value != NULL && !g_utf8_validate(value, -1, NULL)) {
683 		purple_debug_error("prefs", "purple_prefs_add_string: Cannot store invalid UTF8 for string pref %s\n", name);
684 		return;
685 	}
686 
687 	PURPLE_PREFS_UI_OP_CALL(add_string, name, value);
688 
689 	pref = add_pref(PURPLE_PREF_STRING, name);
690 
691 	if(!pref)
692 		return;
693 
694 	pref->value.string = g_strdup(value);
695 }
696 
697 void
purple_prefs_add_string_list(const char * name,GList * value)698 purple_prefs_add_string_list(const char *name, GList *value)
699 {
700 	struct purple_pref *pref;
701 	GList *tmp;
702 
703 	PURPLE_PREFS_UI_OP_CALL(add_string_list, name, value);
704 
705 	pref = add_pref(PURPLE_PREF_STRING_LIST, name);
706 
707 	if(!pref)
708 		return;
709 
710 	for(tmp = value; tmp; tmp = tmp->next) {
711 		if(tmp->data != NULL && !g_utf8_validate(tmp->data, -1, NULL)) {
712 			purple_debug_error("prefs", "purple_prefs_add_string_list: Skipping invalid UTF8 for string list pref %s\n", name);
713 			continue;
714 		}
715 		pref->value.stringlist = g_list_append(pref->value.stringlist,
716 				g_strdup(tmp->data));
717 	}
718 }
719 
720 void
purple_prefs_add_path(const char * name,const char * value)721 purple_prefs_add_path(const char *name, const char *value)
722 {
723 	struct purple_pref *pref;
724 
725 	/* re-use the string UI OP */
726 	PURPLE_PREFS_UI_OP_CALL(add_string, name, value);
727 
728 	pref = add_pref(PURPLE_PREF_PATH, name);
729 
730 	if(!pref)
731 		return;
732 
733 	pref->value.string = g_strdup(value);
734 }
735 
736 void
purple_prefs_add_path_list(const char * name,GList * value)737 purple_prefs_add_path_list(const char *name, GList *value)
738 {
739 	struct purple_pref *pref;
740 	GList *tmp;
741 
742 	/* re-use the string list UI OP */
743 	PURPLE_PREFS_UI_OP_CALL(add_string_list, name, value);
744 
745 	pref = add_pref(PURPLE_PREF_PATH_LIST, name);
746 
747 	if(!pref)
748 		return;
749 
750 	for(tmp = value; tmp; tmp = tmp->next)
751 		pref->value.stringlist = g_list_append(pref->value.stringlist,
752 				g_strdup(tmp->data));
753 }
754 
755 
756 static void
remove_pref(struct purple_pref * pref)757 remove_pref(struct purple_pref *pref)
758 {
759 	struct purple_pref *child = NULL, *next = NULL;
760 	char *name;
761 	GSList *l;
762 
763 	if(!pref)
764 		return;
765 
766 	for(child = pref->first_child; child != NULL; child = next) {
767 		next = child->sibling;
768 		remove_pref(child);
769 	}
770 
771 	if(pref == &prefs)
772 		return;
773 
774 	if(pref->parent->first_child == pref) {
775 		pref->parent->first_child = pref->sibling;
776 	} else {
777 		struct purple_pref *sib = pref->parent->first_child;
778 		while(sib && sib->sibling != pref)
779 			sib = sib->sibling;
780 		if(sib)
781 			sib->sibling = pref->sibling;
782 	}
783 
784 	name = pref_full_name(pref);
785 
786 	if (prefs_loaded)
787 		purple_debug_info("prefs", "removing pref %s\n", name);
788 
789 	g_hash_table_remove(prefs_hash, name);
790 	g_free(name);
791 
792 	free_pref_value(pref);
793 
794 	while((l = pref->callbacks) != NULL) {
795 		pref->callbacks = pref->callbacks->next;
796 		g_free(l->data);
797 		g_slist_free_1(l);
798 	}
799 	g_free(pref->name);
800 	g_free(pref);
801 }
802 
803 void
purple_prefs_remove(const char * name)804 purple_prefs_remove(const char *name)
805 {
806 	struct purple_pref *pref;
807 
808 	PURPLE_PREFS_UI_OP_CALL(remove, name);
809 
810 	pref = find_pref(name);
811 
812 	if(!pref)
813 		return;
814 
815 	remove_pref(pref);
816 }
817 
818 void
purple_prefs_destroy()819 purple_prefs_destroy()
820 {
821 	purple_prefs_remove("/");
822 }
823 
824 static void
do_callbacks(const char * name,struct purple_pref * pref)825 do_callbacks(const char* name, struct purple_pref *pref)
826 {
827 	GSList *cbs;
828 	struct purple_pref *cb_pref;
829 	for(cb_pref = pref; cb_pref; cb_pref = cb_pref->parent) {
830 		for(cbs = cb_pref->callbacks; cbs; cbs = cbs->next) {
831 			PurplePrefCallbackData *cb = cbs->data;
832 			cb->func(name, pref->type, pref->value.generic, cb->data);
833 		}
834 	}
835 }
836 
837 static void
do_ui_callbacks(const char * name)838 do_ui_callbacks(const char *name)
839 {
840 	GSList *cbs;
841 
842 	purple_debug_misc("prefs", "trigger callback %s\n", name);
843 
844 	for (cbs = ui_callbacks; cbs; cbs = cbs->next) {
845 		PurplePrefCallbackData *cb = cbs->data;
846 		const char *cb_name = cb->name;
847 		size_t len = strlen(cb_name);
848 		if (!strncmp(cb_name, name, len) &&
849 		    (name[len] == 0 || name[len] == '/' ||
850 		     (len && name[len - 1] == '/'))) {
851 			/* This test should behave like this:
852 			 * name    = /toto/tata
853 			 * cb_name = /toto/tata --> true
854 			 * cb_name = /toto/tatatiti --> false
855 			 * cb_name = / --> true
856 			 * cb_name = /toto --> true
857 			 * cb_name = /toto/ --> true
858 			 */
859 			purple_prefs_trigger_callback_object(cbs->data);
860 		}
861 	}
862 }
863 
864 void
purple_prefs_trigger_callback(const char * name)865 purple_prefs_trigger_callback(const char *name)
866 {
867 	struct purple_pref *pref;
868 	PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
869 
870 	if (uiop && uiop->connect_callback) {
871 		do_ui_callbacks(name);
872 		return;
873 	}
874 
875 	pref = find_pref(name);
876 
877 	if(!pref) {
878 		purple_debug_error("prefs",
879 				"purple_prefs_trigger_callback: Unknown pref %s\n", name);
880 		return;
881 	}
882 
883 	do_callbacks(name, pref);
884 }
885 
886 /* this function is deprecated, so it doesn't get the new UI ops */
887 void
purple_prefs_set_generic(const char * name,gpointer value)888 purple_prefs_set_generic(const char *name, gpointer value)
889 {
890 	struct purple_pref *pref = find_pref(name);
891 
892 	if(!pref) {
893 		purple_debug_error("prefs",
894 				"purple_prefs_set_generic: Unknown pref %s\n", name);
895 		return;
896 	}
897 
898 	pref->value.generic = value;
899 	do_callbacks(name, pref);
900 }
901 
902 void
purple_prefs_set_bool(const char * name,gboolean value)903 purple_prefs_set_bool(const char *name, gboolean value)
904 {
905 	struct purple_pref *pref;
906 
907 	PURPLE_PREFS_UI_OP_CALL(set_bool, name, value);
908 
909 	pref = find_pref(name);
910 
911 	if(pref) {
912 		if(pref->type != PURPLE_PREF_BOOLEAN) {
913 			purple_debug_error("prefs",
914 					"purple_prefs_set_bool: %s not a boolean pref\n", name);
915 			return;
916 		}
917 
918 		if(pref->value.boolean != value) {
919 			pref->value.boolean = value;
920 			do_callbacks(name, pref);
921 		}
922 	} else {
923 		purple_prefs_add_bool(name, value);
924 	}
925 }
926 
927 void
purple_prefs_set_int(const char * name,int value)928 purple_prefs_set_int(const char *name, int value)
929 {
930 	struct purple_pref *pref;
931 
932 	PURPLE_PREFS_UI_OP_CALL(set_int, name, value);
933 
934 	pref = find_pref(name);
935 
936 	if(pref) {
937 		if(pref->type != PURPLE_PREF_INT) {
938 			purple_debug_error("prefs",
939 					"purple_prefs_set_int: %s not an integer pref\n", name);
940 			return;
941 		}
942 
943 		if(pref->value.integer != value) {
944 			pref->value.integer = value;
945 			do_callbacks(name, pref);
946 		}
947 	} else {
948 		purple_prefs_add_int(name, value);
949 	}
950 }
951 
952 void
purple_prefs_set_string(const char * name,const char * value)953 purple_prefs_set_string(const char *name, const char *value)
954 {
955 	struct purple_pref *pref;
956 
957 	if(value != NULL && !g_utf8_validate(value, -1, NULL)) {
958 		purple_debug_error("prefs", "purple_prefs_set_string: Cannot store invalid UTF8 for string pref %s\n", name);
959 		return;
960 	}
961 
962 	PURPLE_PREFS_UI_OP_CALL(set_string, name, value);
963 
964 	pref = find_pref(name);
965 
966 	if(pref) {
967 		if(pref->type != PURPLE_PREF_STRING && pref->type != PURPLE_PREF_PATH) {
968 			purple_debug_error("prefs",
969 					"purple_prefs_set_string: %s not a string pref\n", name);
970 			return;
971 		}
972 
973 		if (!purple_strequal(pref->value.string, value)) {
974 			g_free(pref->value.string);
975 			pref->value.string = g_strdup(value);
976 			do_callbacks(name, pref);
977 		}
978 	} else {
979 		purple_prefs_add_string(name, value);
980 	}
981 }
982 
983 void
purple_prefs_set_string_list(const char * name,GList * value)984 purple_prefs_set_string_list(const char *name, GList *value)
985 {
986 	struct purple_pref *pref;
987 
988 	PURPLE_PREFS_UI_OP_CALL(set_string_list, name, value);
989 
990 	pref = find_pref(name);
991 
992 	if(pref) {
993 		GList *tmp;
994 
995 		if(pref->type != PURPLE_PREF_STRING_LIST) {
996 			purple_debug_error("prefs",
997 					"purple_prefs_set_string_list: %s not a string list pref\n",
998 					name);
999 			return;
1000 		}
1001 
1002 		g_list_free_full(pref->value.stringlist, (GDestroyNotify)g_free);
1003 		pref->value.stringlist = NULL;
1004 
1005 		for(tmp = value; tmp; tmp = tmp->next) {
1006 			if(tmp->data != NULL && !g_utf8_validate(tmp->data, -1, NULL)) {
1007 				purple_debug_error("prefs", "purple_prefs_set_string_list: Skipping invalid UTF8 for string list pref %s\n", name);
1008 				continue;
1009 			}
1010 			pref->value.stringlist = g_list_prepend(pref->value.stringlist,
1011 					g_strdup(tmp->data));
1012 		}
1013 		pref->value.stringlist = g_list_reverse(pref->value.stringlist);
1014 
1015 		do_callbacks(name, pref);
1016 
1017 	} else {
1018 		purple_prefs_add_string_list(name, value);
1019 	}
1020 }
1021 
1022 void
purple_prefs_set_path(const char * name,const char * value)1023 purple_prefs_set_path(const char *name, const char *value)
1024 {
1025 	struct purple_pref *pref;
1026 
1027 	PURPLE_PREFS_UI_OP_CALL(set_string, name, value);
1028 
1029 	pref = find_pref(name);
1030 
1031 	if(pref) {
1032 		if(pref->type != PURPLE_PREF_PATH) {
1033 			purple_debug_error("prefs",
1034 					"purple_prefs_set_path: %s not a path pref\n", name);
1035 			return;
1036 		}
1037 
1038 		if (!purple_strequal(pref->value.string, value)) {
1039 			g_free(pref->value.string);
1040 			pref->value.string = g_strdup(value);
1041 			do_callbacks(name, pref);
1042 		}
1043 	} else {
1044 		purple_prefs_add_path(name, value);
1045 	}
1046 }
1047 
1048 void
purple_prefs_set_path_list(const char * name,GList * value)1049 purple_prefs_set_path_list(const char *name, GList *value)
1050 {
1051 	struct purple_pref *pref;
1052 
1053 	PURPLE_PREFS_UI_OP_CALL(set_string_list, name, value);
1054 
1055 	pref = find_pref(name);
1056 
1057 	if(pref) {
1058 		GList *tmp;
1059 
1060 		if(pref->type != PURPLE_PREF_PATH_LIST) {
1061 			purple_debug_error("prefs",
1062 					"purple_prefs_set_path_list: %s not a path list pref\n",
1063 					name);
1064 			return;
1065 		}
1066 
1067 		g_list_free_full(pref->value.stringlist, (GDestroyNotify)g_free);
1068 		pref->value.stringlist = NULL;
1069 
1070 		for(tmp = value; tmp; tmp = tmp->next)
1071 			pref->value.stringlist = g_list_prepend(pref->value.stringlist,
1072 					g_strdup(tmp->data));
1073 		pref->value.stringlist = g_list_reverse(pref->value.stringlist);
1074 
1075 		do_callbacks(name, pref);
1076 
1077 	} else {
1078 		purple_prefs_add_path_list(name, value);
1079 	}
1080 }
1081 
1082 
1083 gboolean
purple_prefs_exists(const char * name)1084 purple_prefs_exists(const char *name)
1085 {
1086 	struct purple_pref *pref;
1087 
1088 	PURPLE_PREFS_UI_OP_CALL_RETURN(exists, name);
1089 
1090 	pref = find_pref(name);
1091 
1092 	if (pref != NULL)
1093 		return TRUE;
1094 
1095 	return FALSE;
1096 }
1097 
1098 PurplePrefType
purple_prefs_get_type(const char * name)1099 purple_prefs_get_type(const char *name)
1100 {
1101 	struct purple_pref *pref;
1102 
1103 	PURPLE_PREFS_UI_OP_CALL_RETURN(get_type, name);
1104 
1105 	pref = find_pref(name);
1106 
1107 	if (pref == NULL)
1108 		return PURPLE_PREF_NONE;
1109 
1110 	return (pref->type);
1111 }
1112 
1113 gboolean
purple_prefs_get_bool(const char * name)1114 purple_prefs_get_bool(const char *name)
1115 {
1116 	struct purple_pref *pref;
1117 
1118 	PURPLE_PREFS_UI_OP_CALL_RETURN(get_bool, name);
1119 
1120 	pref = find_pref(name);
1121 
1122 	if(!pref) {
1123 		purple_debug_error("prefs",
1124 				"purple_prefs_get_bool: Unknown pref %s\n", name);
1125 		return FALSE;
1126 	} else if(pref->type != PURPLE_PREF_BOOLEAN) {
1127 		purple_debug_error("prefs",
1128 				"purple_prefs_get_bool: %s not a boolean pref\n", name);
1129 		return FALSE;
1130 	}
1131 
1132 	return pref->value.boolean;
1133 }
1134 
1135 int
purple_prefs_get_int(const char * name)1136 purple_prefs_get_int(const char *name)
1137 {
1138 	struct purple_pref *pref;
1139 
1140 	PURPLE_PREFS_UI_OP_CALL_RETURN(get_int, name);
1141 
1142 	pref = find_pref(name);
1143 
1144 	if(!pref) {
1145 		purple_debug_error("prefs",
1146 				"purple_prefs_get_int: Unknown pref %s\n", name);
1147 		return 0;
1148 	} else if(pref->type != PURPLE_PREF_INT) {
1149 		purple_debug_error("prefs",
1150 				"purple_prefs_get_int: %s not an integer pref\n", name);
1151 		return 0;
1152 	}
1153 
1154 	return pref->value.integer;
1155 }
1156 
1157 const char *
purple_prefs_get_string(const char * name)1158 purple_prefs_get_string(const char *name)
1159 {
1160 	struct purple_pref *pref;
1161 
1162 	PURPLE_PREFS_UI_OP_CALL_RETURN(get_string, name);
1163 
1164 	pref = find_pref(name);
1165 
1166 	if(!pref) {
1167 		purple_debug_error("prefs",
1168 				"purple_prefs_get_string: Unknown pref %s\n", name);
1169 		return NULL;
1170 	} else if(pref->type != PURPLE_PREF_STRING) {
1171 		purple_debug_error("prefs",
1172 				"purple_prefs_get_string: %s not a string pref\n", name);
1173 		return NULL;
1174 	}
1175 
1176 	return pref->value.string;
1177 }
1178 
1179 GList *
purple_prefs_get_string_list(const char * name)1180 purple_prefs_get_string_list(const char *name)
1181 {
1182 	struct purple_pref *pref;
1183 	GList *ret = NULL, *tmp;
1184 
1185 	PURPLE_PREFS_UI_OP_CALL_RETURN(get_string_list, name);
1186 
1187 	pref = find_pref(name);
1188 
1189 	if(!pref) {
1190 		purple_debug_error("prefs",
1191 				"purple_prefs_get_string_list: Unknown pref %s\n", name);
1192 		return NULL;
1193 	} else if(pref->type != PURPLE_PREF_STRING_LIST) {
1194 		purple_debug_error("prefs",
1195 				"purple_prefs_get_string_list: %s not a string list pref\n", name);
1196 		return NULL;
1197 	}
1198 
1199 	for(tmp = pref->value.stringlist; tmp; tmp = tmp->next)
1200 		ret = g_list_prepend(ret, g_strdup(tmp->data));
1201 	ret = g_list_reverse(ret);
1202 
1203 	return ret;
1204 }
1205 
1206 const char *
purple_prefs_get_path(const char * name)1207 purple_prefs_get_path(const char *name)
1208 {
1209 	struct purple_pref *pref;
1210 
1211 	PURPLE_PREFS_UI_OP_CALL_RETURN(get_string, name);
1212 
1213 	pref = find_pref(name);
1214 
1215 	if(!pref) {
1216 		purple_debug_error("prefs",
1217 				"purple_prefs_get_path: Unknown pref %s\n", name);
1218 		return NULL;
1219 	} else if(pref->type != PURPLE_PREF_PATH) {
1220 		purple_debug_error("prefs",
1221 				"purple_prefs_get_path: %s not a path pref\n", name);
1222 		return NULL;
1223 	}
1224 
1225 	return pref->value.string;
1226 }
1227 
1228 GList *
purple_prefs_get_path_list(const char * name)1229 purple_prefs_get_path_list(const char *name)
1230 {
1231 	struct purple_pref *pref;
1232 	GList *ret = NULL, *tmp;
1233 
1234 	PURPLE_PREFS_UI_OP_CALL_RETURN(get_string_list, name);
1235 
1236 	pref = find_pref(name);
1237 
1238 	if(!pref) {
1239 		purple_debug_error("prefs",
1240 				"purple_prefs_get_path_list: Unknown pref %s\n", name);
1241 		return NULL;
1242 	} else if(pref->type != PURPLE_PREF_PATH_LIST) {
1243 		purple_debug_error("prefs",
1244 				"purple_prefs_get_path_list: %s not a path list pref\n", name);
1245 		return NULL;
1246 	}
1247 
1248 	for(tmp = pref->value.stringlist; tmp; tmp = tmp->next)
1249 		ret = g_list_prepend(ret, g_strdup(tmp->data));
1250 	ret = g_list_reverse(ret);
1251 
1252 	return ret;
1253 }
1254 
1255 static void
purple_prefs_rename_node(struct purple_pref * oldpref,struct purple_pref * newpref)1256 purple_prefs_rename_node(struct purple_pref *oldpref, struct purple_pref *newpref)
1257 {
1258 	struct purple_pref *child, *next;
1259 	char *oldname, *newname;
1260 
1261 	/* if we're a parent, rename the kids first */
1262 	for(child = oldpref->first_child; child != NULL; child = next)
1263 	{
1264 		struct purple_pref *newchild;
1265 		next = child->sibling;
1266 		for(newchild = newpref->first_child; newchild != NULL; newchild = newchild->sibling)
1267 		{
1268 			if(purple_strequal(child->name, newchild->name))
1269 			{
1270 				purple_prefs_rename_node(child, newchild);
1271 				break;
1272 			}
1273 		}
1274 		if(newchild == NULL) {
1275 			/* no rename happened, we weren't able to find the new pref */
1276 			char *tmpname = pref_full_name(child);
1277 			purple_debug_error("prefs", "Unable to find rename pref for %s\n", tmpname);
1278 			g_free(tmpname);
1279 		}
1280 	}
1281 
1282 	oldname = pref_full_name(oldpref);
1283 	newname = pref_full_name(newpref);
1284 
1285 	if (oldpref->type != newpref->type)
1286 	{
1287 		purple_debug_error("prefs", "Unable to rename %s to %s: differing types\n", oldname, newname);
1288 		g_free(oldname);
1289 		g_free(newname);
1290 		return;
1291 	}
1292 
1293 	purple_debug_info("prefs", "Renaming %s to %s\n", oldname, newname);
1294 	g_free(oldname);
1295 
1296 	switch(oldpref->type) {
1297 		case PURPLE_PREF_NONE:
1298 			break;
1299 		case PURPLE_PREF_BOOLEAN:
1300 			purple_prefs_set_bool(newname, oldpref->value.boolean);
1301 			break;
1302 		case PURPLE_PREF_INT:
1303 			purple_prefs_set_int(newname, oldpref->value.integer);
1304 			break;
1305 		case PURPLE_PREF_STRING:
1306 			purple_prefs_set_string(newname, oldpref->value.string);
1307 			break;
1308 		case PURPLE_PREF_STRING_LIST:
1309 			purple_prefs_set_string_list(newname, oldpref->value.stringlist);
1310 			break;
1311 		case PURPLE_PREF_PATH:
1312 			purple_prefs_set_path(newname, oldpref->value.string);
1313 			break;
1314 		case PURPLE_PREF_PATH_LIST:
1315 			purple_prefs_set_path_list(newname, oldpref->value.stringlist);
1316 			break;
1317 	}
1318 	g_free(newname);
1319 
1320 	remove_pref(oldpref);
1321 }
1322 
1323 void
purple_prefs_rename(const char * oldname,const char * newname)1324 purple_prefs_rename(const char *oldname, const char *newname)
1325 {
1326 	struct purple_pref *oldpref, *newpref;
1327 
1328 /* win32dep.h causes rename to be defined as wpurple_rename, so we need to undefine it here */
1329 #if defined(_WIN32) && defined(rename)
1330 #undef rename
1331 #endif
1332 
1333 	PURPLE_PREFS_UI_OP_CALL(rename, oldname, newname);
1334 
1335 	oldpref = find_pref(oldname);
1336 
1337 	/* it's already been renamed, call off the dogs */
1338 	if(!oldpref)
1339 		return;
1340 
1341 	newpref = find_pref(newname);
1342 
1343 	if (newpref == NULL)
1344 	{
1345 		purple_debug_error("prefs", "Unable to rename %s to %s: new pref not created\n", oldname, newname);
1346 		return;
1347 	}
1348 
1349 	purple_prefs_rename_node(oldpref, newpref);
1350 }
1351 
1352 void
purple_prefs_rename_boolean_toggle(const char * oldname,const char * newname)1353 purple_prefs_rename_boolean_toggle(const char *oldname, const char *newname)
1354 {
1355 		struct purple_pref *oldpref, *newpref;
1356 
1357 		PURPLE_PREFS_UI_OP_CALL(rename_boolean_toggle, oldname, newname);
1358 
1359 		oldpref = find_pref(oldname);
1360 
1361 		/* it's already been renamed, call off the cats */
1362 		if(!oldpref)
1363 			return;
1364 
1365 		if (oldpref->type != PURPLE_PREF_BOOLEAN)
1366 		{
1367 			purple_debug_error("prefs", "Unable to rename %s to %s: old pref not a boolean\n", oldname, newname);
1368 			return;
1369 		}
1370 
1371 		if (oldpref->first_child != NULL) /* can't rename parents */
1372 		{
1373 			purple_debug_error("prefs", "Unable to rename %s to %s: can't rename parents\n", oldname, newname);
1374 			return;
1375 		}
1376 
1377 
1378 		newpref = find_pref(newname);
1379 
1380 		if (newpref == NULL)
1381 		{
1382 			purple_debug_error("prefs", "Unable to rename %s to %s: new pref not created\n", oldname, newname);
1383 			return;
1384 		}
1385 
1386 		if (oldpref->type != newpref->type)
1387 		{
1388 			purple_debug_error("prefs", "Unable to rename %s to %s: differing types\n", oldname, newname);
1389 			return;
1390 		}
1391 
1392 		purple_debug_info("prefs", "Renaming and toggling %s to %s\n", oldname, newname);
1393 		purple_prefs_set_bool(newname, !(oldpref->value.boolean));
1394 
1395 		remove_pref(oldpref);
1396 }
1397 
1398 guint
purple_prefs_connect_callback(void * handle,const char * name,PurplePrefCallback func,gpointer data)1399 purple_prefs_connect_callback(void *handle, const char *name, PurplePrefCallback func, gpointer data)
1400 {
1401 	struct purple_pref *pref = NULL;
1402 	PurplePrefCallbackData *cb;
1403 	static guint cb_id = 0;
1404 	PurplePrefsUiOps *uiop = NULL;
1405 
1406 	g_return_val_if_fail(name != NULL, 0);
1407 	g_return_val_if_fail(func != NULL, 0);
1408 
1409 	uiop = purple_prefs_get_ui_ops();
1410 
1411 	if (!(uiop && uiop->connect_callback)) {
1412 		pref = find_pref(name);
1413 		if (pref == NULL) {
1414 			purple_debug_error("prefs", "purple_prefs_connect_callback: Unknown pref %s\n", name);
1415 			return 0;
1416 		}
1417 	}
1418 
1419 	cb = g_new0(PurplePrefCallbackData, 1);
1420 
1421 	cb->func = func;
1422 	cb->data = data;
1423 	cb->id = ++cb_id;
1424 	cb->handle = handle;
1425 	cb->name = g_strdup(name);
1426 
1427 	if (uiop && uiop->connect_callback) {
1428 		cb->ui_data = uiop->connect_callback(name, cb);
1429 
1430 		if (cb->ui_data == NULL) {
1431 			purple_debug_error("prefs", "purple_prefs_connect_callback: connect failed for %s\n", name);
1432 			g_free(cb->name);
1433 			g_free(cb);
1434 			return 0;
1435 		}
1436 
1437 		ui_callbacks = g_slist_append(ui_callbacks, cb);
1438 	} else {
1439 		pref->callbacks = g_slist_append(pref->callbacks, cb);
1440 	}
1441 
1442 	return cb->id;
1443 }
1444 
1445 static void
purple_prefs_trigger_ui_callback_object(PurplePrefCallbackData * cb)1446 purple_prefs_trigger_ui_callback_object(PurplePrefCallbackData *cb)
1447 {
1448 	PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
1449 	gconstpointer value = NULL;
1450 	PurplePrefType type = PURPLE_PREF_NONE;
1451 
1452 	type = uiop->get_type(cb->name);
1453 
1454 	switch (type) {
1455 		case PURPLE_PREF_INT:
1456 			if (uiop->get_int) {
1457 				value = GINT_TO_POINTER(uiop->get_int(cb->name));
1458 			}
1459 			break;
1460 		case PURPLE_PREF_BOOLEAN:
1461 			if (uiop->get_bool) {
1462 				value = GINT_TO_POINTER(uiop->get_bool(cb->name));
1463 			}
1464 			break;
1465 		case PURPLE_PREF_STRING:
1466 		case PURPLE_PREF_PATH:
1467 			if (uiop->get_string) {
1468 				value = uiop->get_string(cb->name);
1469 			}
1470 			break;
1471 		case PURPLE_PREF_STRING_LIST:
1472 		case PURPLE_PREF_PATH_LIST:
1473 			if (uiop->get_string_list) {
1474 				value = uiop->get_string_list(cb->name);
1475 			}
1476 			break;
1477 		case PURPLE_PREF_NONE:
1478 			break;
1479 		default:
1480 			purple_debug_error("prefs", "Unexpected type = %i\n", type);
1481 	}
1482 
1483 	cb->func(cb->name, type, value, cb->data);
1484 }
1485 
1486 void
purple_prefs_trigger_callback_object(PurplePrefCallbackData * cb)1487 purple_prefs_trigger_callback_object(PurplePrefCallbackData *cb)
1488 {
1489 	PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
1490 
1491 	if (uiop && uiop->connect_callback && uiop->get_type) {
1492 		purple_prefs_trigger_ui_callback_object(cb);
1493 	} else {
1494 		purple_prefs_trigger_callback(cb->name);
1495 	}
1496 }
1497 
1498 static gboolean
disco_callback_helper(struct purple_pref * pref,guint callback_id)1499 disco_callback_helper(struct purple_pref *pref, guint callback_id)
1500 {
1501 	GSList *cbs;
1502 	struct purple_pref *child;
1503 
1504 	if(!pref)
1505 		return FALSE;
1506 
1507 	for(cbs = pref->callbacks; cbs; cbs = cbs->next) {
1508 		PurplePrefCallbackData *cb = cbs->data;
1509 		if(cb->id == callback_id) {
1510 			pref->callbacks = g_slist_delete_link(pref->callbacks, cbs);
1511 			g_free(cb->name);
1512 			g_free(cb);
1513 			return TRUE;
1514 		}
1515 	}
1516 
1517 	for(child = pref->first_child; child; child = child->sibling) {
1518 		if(disco_callback_helper(child, callback_id))
1519 			return TRUE;
1520 	}
1521 
1522 	return FALSE;
1523 }
1524 
1525 static void
disco_ui_callback_helper(guint callback_id)1526 disco_ui_callback_helper(guint callback_id)
1527 {
1528 	GSList *cbs;
1529 	PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
1530 
1531 	for (cbs = ui_callbacks; cbs; cbs = cbs->next) {
1532 		PurplePrefCallbackData *cb = cbs->data;
1533 		if (cb->id == callback_id) {
1534 			uiop->disconnect_callback(cb->name, cb->ui_data);
1535 
1536 			ui_callbacks = g_slist_delete_link(ui_callbacks, cbs);
1537 			g_free(cb->name);
1538 			g_free(cb);
1539 			return;
1540 		}
1541 	}
1542 }
1543 
1544 void
purple_prefs_disconnect_callback(guint callback_id)1545 purple_prefs_disconnect_callback(guint callback_id)
1546 {
1547 	PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
1548 
1549 	if (uiop && uiop->disconnect_callback) {
1550 		disco_ui_callback_helper(callback_id);
1551 	} else {
1552 		disco_callback_helper(&prefs, callback_id);
1553 	}
1554 }
1555 
1556 static void
disco_callback_helper_handle(struct purple_pref * pref,void * handle)1557 disco_callback_helper_handle(struct purple_pref *pref, void *handle)
1558 {
1559 	GSList *cbs;
1560 	struct purple_pref *child;
1561 
1562 	if(!pref)
1563 		return;
1564 
1565 	cbs = pref->callbacks;
1566 	while (cbs != NULL) {
1567 		PurplePrefCallbackData *cb = cbs->data;
1568 		if(cb->handle == handle) {
1569 			pref->callbacks = g_slist_delete_link(pref->callbacks, cbs);
1570 			g_free(cb->name);
1571 			g_free(cb);
1572 			cbs = pref->callbacks;
1573 		} else
1574 			cbs = cbs->next;
1575 	}
1576 
1577 	for(child = pref->first_child; child; child = child->sibling)
1578 		disco_callback_helper_handle(child, handle);
1579 }
1580 
1581 static void
disco_ui_callback_helper_handle(void * handle)1582 disco_ui_callback_helper_handle(void *handle)
1583 {
1584 	GSList *cbs;
1585 	PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
1586 
1587 	for (cbs = ui_callbacks; cbs; cbs = cbs->next) {
1588 		PurplePrefCallbackData *cb = cbs->data;
1589 		if (cb->handle != handle) {
1590 			cbs = cbs->next;
1591 			continue;
1592 		}
1593 
1594 		uiop->disconnect_callback(cb->name, cb->ui_data);
1595 
1596 		ui_callbacks = g_slist_delete_link(ui_callbacks, cbs);
1597 		g_free(cb->name);
1598 		g_free(cb);
1599 		cbs = ui_callbacks;
1600 	}
1601 }
1602 
1603 void
purple_prefs_disconnect_by_handle(void * handle)1604 purple_prefs_disconnect_by_handle(void *handle)
1605 {
1606 	PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
1607 
1608 	g_return_if_fail(handle != NULL);
1609 
1610 	if (uiop && uiop->disconnect_callback) {
1611 		disco_ui_callback_helper_handle(handle);
1612 	} else {
1613 		disco_callback_helper_handle(&prefs, handle);
1614 	}
1615 }
1616 
1617 GList *
purple_prefs_get_children_names(const char * name)1618 purple_prefs_get_children_names(const char *name)
1619 {
1620 	GList * list = NULL;
1621 	struct purple_pref *pref, *child;
1622 	char sep[2] = "\0\0";;
1623 
1624 	PURPLE_PREFS_UI_OP_CALL_RETURN(get_children_names, name);
1625 
1626 	pref = find_pref(name);
1627 
1628 	if (pref == NULL)
1629 		return NULL;
1630 
1631 	if (name[strlen(name) - 1] != '/')
1632 		sep[0] = '/';
1633 	for (child = pref->first_child; child; child = child->sibling) {
1634 		list = g_list_append(list, g_strdup_printf("%s%s%s", name, sep, child->name));
1635 	}
1636 	return list;
1637 }
1638 
1639 void
purple_prefs_update_old()1640 purple_prefs_update_old()
1641 {
1642 	purple_prefs_rename("/core", "/purple");
1643 
1644 	/* Remove some no-longer-used prefs */
1645 	purple_prefs_remove("/purple/away/auto_response/enabled");
1646 	purple_prefs_remove("/purple/away/auto_response/idle_only");
1647 	purple_prefs_remove("/purple/away/auto_response/in_active_conv");
1648 	purple_prefs_remove("/purple/away/auto_response/sec_before_resend");
1649 	purple_prefs_remove("/purple/away/auto_response");
1650 	purple_prefs_remove("/purple/away/default_message");
1651 	purple_prefs_remove("/purple/buddies/use_server_alias");
1652 	purple_prefs_remove("/purple/conversations/away_back_on_send");
1653 	purple_prefs_remove("/purple/conversations/send_urls_as_links");
1654 	purple_prefs_remove("/purple/conversations/im/show_login");
1655 	purple_prefs_remove("/purple/conversations/chat/show_join");
1656 	purple_prefs_remove("/purple/conversations/chat/show_leave");
1657 	purple_prefs_remove("/purple/conversations/combine_chat_im");
1658 	purple_prefs_remove("/purple/conversations/use_alias_for_title");
1659 	purple_prefs_remove("/purple/logging/log_signon_signoff");
1660 	purple_prefs_remove("/purple/logging/log_idle_state");
1661 	purple_prefs_remove("/purple/logging/log_away_state");
1662 	purple_prefs_remove("/purple/logging/log_own_states");
1663 	purple_prefs_remove("/purple/status/scores/hidden");
1664 	purple_prefs_remove("/plugins/core/autorecon/hide_connected_error");
1665 	purple_prefs_remove("/plugins/core/autorecon/hide_connecting_error");
1666 	purple_prefs_remove("/plugins/core/autorecon/hide_reconnecting_dialog");
1667 	purple_prefs_remove("/plugins/core/autorecon/restore_state");
1668 	purple_prefs_remove("/plugins/core/autorecon");
1669 
1670 	/* Convert old sounds while_away pref to new 3-way pref. */
1671 	if (purple_prefs_exists("/purple/sound/while_away") &&
1672 	    purple_prefs_get_bool("/purple/sound/while_away"))
1673 	{
1674 		purple_prefs_set_int("/purple/sound/while_status", 3);
1675 	}
1676 	purple_prefs_remove("/purple/sound/while_away");
1677 }
1678 
1679 void *
purple_prefs_get_handle(void)1680 purple_prefs_get_handle(void)
1681 {
1682 	static int handle;
1683 
1684 	return &handle;
1685 }
1686 
1687 void
purple_prefs_init(void)1688 purple_prefs_init(void)
1689 {
1690 	void *handle = purple_prefs_get_handle();
1691 
1692 	prefs_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1693 
1694 	purple_prefs_connect_callback(handle, "/", prefs_save_cb, NULL);
1695 
1696 	purple_prefs_add_none("/purple");
1697 	purple_prefs_add_none("/plugins");
1698 	purple_prefs_add_none("/plugins/core");
1699 	purple_prefs_add_none("/plugins/lopl");
1700 	purple_prefs_add_none("/plugins/prpl");
1701 
1702 	/* Away */
1703 	purple_prefs_add_none("/purple/away");
1704 	purple_prefs_add_string("/purple/away/idle_reporting", "system");
1705 	purple_prefs_add_bool("/purple/away/away_when_idle", TRUE);
1706 	purple_prefs_add_int("/purple/away/mins_before_away", 5);
1707 
1708 	/* Away -> Auto-Reply */
1709 	if (!purple_prefs_exists("/purple/away/auto_response/enabled") ||
1710 	    !purple_prefs_exists("/purple/away/auto_response/idle_only"))
1711 	{
1712 		purple_prefs_add_string("/purple/away/auto_reply", "awayidle");
1713 	}
1714 	else
1715 	{
1716 		if (!purple_prefs_get_bool("/purple/away/auto_response/enabled"))
1717 		{
1718 			purple_prefs_add_string("/purple/away/auto_reply", "never");
1719 		}
1720 		else
1721 		{
1722 			if (purple_prefs_get_bool("/purple/away/auto_response/idle_only"))
1723 			{
1724 				purple_prefs_add_string("/purple/away/auto_reply", "awayidle");
1725 			}
1726 			else
1727 			{
1728 				purple_prefs_add_string("/purple/away/auto_reply", "away");
1729 			}
1730 		}
1731 	}
1732 
1733 	/* Buddies */
1734 	purple_prefs_add_none("/purple/buddies");
1735 
1736 	/* Contact Priority Settings */
1737 	purple_prefs_add_none("/purple/contact");
1738 	purple_prefs_add_bool("/purple/contact/last_match", FALSE);
1739 	purple_prefs_remove("/purple/contact/offline_score");
1740 	purple_prefs_remove("/purple/contact/away_score");
1741 	purple_prefs_remove("/purple/contact/idle_score");
1742 
1743 	purple_prefs_load();
1744 	purple_prefs_update_old();
1745 }
1746 
1747 void
purple_prefs_uninit()1748 purple_prefs_uninit()
1749 {
1750 	if (save_timer != 0)
1751 	{
1752 		purple_timeout_remove(save_timer);
1753 		save_cb(NULL);
1754 	}
1755 
1756 	purple_prefs_disconnect_by_handle(purple_prefs_get_handle());
1757 
1758 	prefs_loaded = FALSE;
1759 	purple_prefs_destroy();
1760 	g_hash_table_destroy(prefs_hash);
1761 	prefs_hash = NULL;
1762 
1763 }
1764 
1765 void
purple_prefs_set_ui_ops(PurplePrefsUiOps * ops)1766 purple_prefs_set_ui_ops(PurplePrefsUiOps *ops)
1767 {
1768 	prefs_ui_ops = ops;
1769 }
1770 
1771 PurplePrefsUiOps *
purple_prefs_get_ui_ops(void)1772 purple_prefs_get_ui_ops(void)
1773 {
1774 	return prefs_ui_ops;
1775 }
1776