pidgin/pidgin

Added tag v2.14.2 for changeset 2eb25613d054
release-2.x.y
2021-04-01, Gary Kramlich
1dd6e5170860
Added tag v2.14.2 for changeset 2eb25613d054
/*
* purple
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <glib.h>
#include "internal.h"
#include "debug.h"
#include "glibcompat.h"
#include "prefs.h"
#include "util.h"
#ifdef _WIN32
#include "win32dep.h"
#endif
static PurplePrefsUiOps *prefs_ui_ops = NULL;
struct _PurplePrefCallbackData {
PurplePrefCallback func;
gpointer data;
guint id;
void *handle;
void *ui_data;
char *name;
};
/* TODO: This should use PurpleValues? */
struct purple_pref {
PurplePrefType type;
char *name;
union {
gpointer generic;
gboolean boolean;
int integer;
char *string;
GList *stringlist;
} value;
GSList *callbacks;
struct purple_pref *parent;
struct purple_pref *sibling;
struct purple_pref *first_child;
};
static struct purple_pref prefs = {
PURPLE_PREF_NONE,
NULL,
{ NULL },
NULL,
NULL,
NULL,
NULL
};
static GHashTable *prefs_hash = NULL;
static guint save_timer = 0;
static gboolean prefs_loaded = FALSE;
static GSList *ui_callbacks = NULL;
#define PURPLE_PREFS_UI_OP_CALL(member, ...) \
{ \
PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops(); \
if (uiop && uiop->member) { \
uiop->member(__VA_ARGS__); \
return; \
} \
}
#define PURPLE_PREFS_UI_OP_CALL_RETURN(member, ...) \
{ \
PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops(); \
if (uiop && uiop->member) { \
return uiop->member(__VA_ARGS__); \
} \
}
/*********************************************************************
* Private utility functions *
*********************************************************************/
static struct
purple_pref *find_pref(const char *name)
{
g_return_val_if_fail(name != NULL && name[0] == '/', NULL);
if (name[1] == '\0')
return &prefs;
else
{
/* When we're initializing, the debug system is
* initialized before the prefs system, but debug
* calls will end up calling prefs functions, so we
* need to deal cleanly here. */
if (prefs_hash)
return g_hash_table_lookup(prefs_hash, name);
else
return NULL;
}
}
/*********************************************************************
* Writing to disk *
*********************************************************************/
/*
* This function recursively creates the xmlnode tree from the prefs
* tree structure. Yay recursion!
*/
static void
pref_to_xmlnode(xmlnode *parent, struct purple_pref *pref)
{
xmlnode *node, *childnode;
struct purple_pref *child;
char buf[21];
GList *cur;
/* Create a new node */
node = xmlnode_new_child(parent, "pref");
xmlnode_set_attrib(node, "name", pref->name);
/* Set the type of this node (if type == PURPLE_PREF_NONE then do nothing) */
if (pref->type == PURPLE_PREF_INT) {
xmlnode_set_attrib(node, "type", "int");
g_snprintf(buf, sizeof(buf), "%d", pref->value.integer);
xmlnode_set_attrib(node, "value", buf);
}
else if (pref->type == PURPLE_PREF_STRING) {
xmlnode_set_attrib(node, "type", "string");
xmlnode_set_attrib(node, "value", pref->value.string ? pref->value.string : "");
}
else if (pref->type == PURPLE_PREF_STRING_LIST) {
xmlnode_set_attrib(node, "type", "stringlist");
for (cur = pref->value.stringlist; cur != NULL; cur = cur->next)
{
childnode = xmlnode_new_child(node, "item");
xmlnode_set_attrib(childnode, "value", cur->data ? cur->data : "");
}
}
else if (pref->type == PURPLE_PREF_PATH) {
char *encoded = g_filename_to_utf8(pref->value.string ? pref->value.string : "", -1, NULL, NULL, NULL);
xmlnode_set_attrib(node, "type", "path");
xmlnode_set_attrib(node, "value", encoded);
g_free(encoded);
}
else if (pref->type == PURPLE_PREF_PATH_LIST) {
xmlnode_set_attrib(node, "type", "pathlist");
for (cur = pref->value.stringlist; cur != NULL; cur = cur->next)
{
char *encoded = g_filename_to_utf8(cur->data ? cur->data : "", -1, NULL, NULL, NULL);
childnode = xmlnode_new_child(node, "item");
xmlnode_set_attrib(childnode, "value", encoded);
g_free(encoded);
}
}
else if (pref->type == PURPLE_PREF_BOOLEAN) {
xmlnode_set_attrib(node, "type", "bool");
g_snprintf(buf, sizeof(buf), "%d", pref->value.boolean);
xmlnode_set_attrib(node, "value", buf);
}
/* All My Children */
for (child = pref->first_child; child != NULL; child = child->sibling)
pref_to_xmlnode(node, child);
}
static xmlnode *
prefs_to_xmlnode(void)
{
xmlnode *node;
struct purple_pref *pref, *child;
pref = &prefs;
/* Create the root preference node */
node = xmlnode_new("pref");
xmlnode_set_attrib(node, "version", "1");
xmlnode_set_attrib(node, "name", "/");
/* All My Children */
for (child = pref->first_child; child != NULL; child = child->sibling)
pref_to_xmlnode(node, child);
return node;
}
static void
sync_prefs(void)
{
xmlnode *node;
char *data;
if (!prefs_loaded)
{
/*
* TODO: Call schedule_prefs_save()? Ideally we wouldn't need to.
* (prefs.xml should be loaded when purple_prefs_init is called)
*/
purple_debug_error("prefs", "Attempted to save prefs before "
"they were read!\n");
return;
}
PURPLE_PREFS_UI_OP_CALL(save);
node = prefs_to_xmlnode();
data = xmlnode_to_formatted_str(node, NULL);
purple_util_write_data_to_file("prefs.xml", data, -1);
g_free(data);
xmlnode_free(node);
}
static gboolean
save_cb(gpointer data)
{
sync_prefs();
save_timer = 0;
return FALSE;
}
static void
schedule_prefs_save(void)
{
PURPLE_PREFS_UI_OP_CALL(schedule_save);
if (save_timer == 0)
save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
}
/*********************************************************************
* Reading from disk *
*********************************************************************/
static GList *prefs_stack = NULL;
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)
{
PurplePrefType pref_type = PURPLE_PREF_NONE;
int i;
const char *pref_name = NULL, *pref_value = NULL;
GString *pref_name_full;
GList *tmp;
if(!purple_strequal(element_name, "pref") &&
!purple_strequal(element_name, "item"))
return;
for(i = 0; attribute_names[i]; i++) {
if(purple_strequal(attribute_names[i], "name")) {
pref_name = attribute_values[i];
} else if(purple_strequal(attribute_names[i], "type")) {
if(purple_strequal(attribute_values[i], "bool"))
pref_type = PURPLE_PREF_BOOLEAN;
else if(purple_strequal(attribute_values[i], "int"))
pref_type = PURPLE_PREF_INT;
else if(purple_strequal(attribute_values[i], "string"))
pref_type = PURPLE_PREF_STRING;
else if(purple_strequal(attribute_values[i], "stringlist"))
pref_type = PURPLE_PREF_STRING_LIST;
else if(purple_strequal(attribute_values[i], "path"))
pref_type = PURPLE_PREF_PATH;
else if(purple_strequal(attribute_values[i], "pathlist"))
pref_type = PURPLE_PREF_PATH_LIST;
else
return;
} else if(purple_strequal(attribute_names[i], "value")) {
pref_value = attribute_values[i];
}
}
if ((pref_type == PURPLE_PREF_BOOLEAN || pref_type == PURPLE_PREF_INT) &&
pref_value == NULL) {
/* Missing a value attribute */
return;
}
if(purple_strequal(element_name, "item")) {
struct purple_pref *pref;
pref_name_full = g_string_new("");
for(tmp = prefs_stack; tmp; tmp = tmp->next) {
pref_name_full = g_string_prepend(pref_name_full, tmp->data);
pref_name_full = g_string_prepend_c(pref_name_full, '/');
}
pref = find_pref(pref_name_full->str);
if(pref) {
if(pref->type == PURPLE_PREF_STRING_LIST) {
pref->value.stringlist = g_list_append(pref->value.stringlist,
g_strdup(pref_value));
} else if(pref->type == PURPLE_PREF_PATH_LIST) {
pref->value.stringlist = g_list_append(pref->value.stringlist,
g_filename_from_utf8(pref_value, -1, NULL, NULL, NULL));
}
}
g_string_free(pref_name_full, TRUE);
} else {
char *decoded;
if(!pref_name || purple_strequal(pref_name, "/"))
return;
pref_name_full = g_string_new(pref_name);
for(tmp = prefs_stack; tmp; tmp = tmp->next) {
pref_name_full = g_string_prepend_c(pref_name_full, '/');
pref_name_full = g_string_prepend(pref_name_full, tmp->data);
}
pref_name_full = g_string_prepend_c(pref_name_full, '/');
switch(pref_type) {
case PURPLE_PREF_NONE:
purple_prefs_add_none(pref_name_full->str);
break;
case PURPLE_PREF_BOOLEAN:
purple_prefs_set_bool(pref_name_full->str, atoi(pref_value));
break;
case PURPLE_PREF_INT:
purple_prefs_set_int(pref_name_full->str, atoi(pref_value));
break;
case PURPLE_PREF_STRING:
purple_prefs_set_string(pref_name_full->str, pref_value);
break;
case PURPLE_PREF_STRING_LIST:
purple_prefs_set_string_list(pref_name_full->str, NULL);
break;
case PURPLE_PREF_PATH:
if (pref_value) {
decoded = g_filename_from_utf8(pref_value, -1, NULL, NULL, NULL);
purple_prefs_set_path(pref_name_full->str, decoded);
g_free(decoded);
} else {
purple_prefs_set_path(pref_name_full->str, NULL);
}
break;
case PURPLE_PREF_PATH_LIST:
purple_prefs_set_path_list(pref_name_full->str, NULL);
break;
}
prefs_stack = g_list_prepend(prefs_stack, g_strdup(pref_name));
g_string_free(pref_name_full, TRUE);
}
}
static void
prefs_end_element_handler(GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data, GError **error)
{
if(prefs_stack && purple_strequal(element_name, "pref")) {
g_free(prefs_stack->data);
prefs_stack = g_list_delete_link(prefs_stack, prefs_stack);
}
}
static GMarkupParser prefs_parser = {
prefs_start_element_handler,
prefs_end_element_handler,
NULL,
NULL,
NULL
};
gboolean
purple_prefs_load()
{
gchar *filename;
gchar *contents = NULL;
gsize length;
GMarkupParseContext *context;
GError *error = NULL;
PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
if (uiop && uiop->load) {
prefs_loaded = TRUE;
return uiop->load();
}
filename = g_build_filename(purple_user_dir(), "prefs.xml", NULL);
if (!filename) {
prefs_loaded = TRUE;
return FALSE;
}
purple_debug_info("prefs", "Reading %s\n", filename);
if(!g_file_get_contents(filename, &contents, &length, &error)) {
#ifdef _WIN32
gchar *common_appdata = wpurple_get_special_folder(CSIDL_COMMON_APPDATA);
#endif
g_free(filename);
g_error_free(error);
error = NULL;
#ifdef _WIN32
filename = g_build_filename(common_appdata ? common_appdata : "", "purple", "prefs.xml", NULL);
g_free(common_appdata);
#else
filename = g_build_filename(SYSCONFDIR, "purple", "prefs.xml", NULL);
#endif
purple_debug_info("prefs", "Reading %s\n", filename);
if (!g_file_get_contents(filename, &contents, &length, &error)) {
purple_debug_error("prefs", "Error reading prefs: %s\n",
error->message);
g_error_free(error);
g_free(filename);
prefs_loaded = TRUE;
return FALSE;
}
}
context = g_markup_parse_context_new(&prefs_parser, 0, NULL, NULL);
if(!g_markup_parse_context_parse(context, contents, length, NULL)) {
g_markup_parse_context_free(context);
g_free(contents);
g_free(filename);
prefs_loaded = TRUE;
return FALSE;
}
if(!g_markup_parse_context_end_parse(context, NULL)) {
purple_debug_error("prefs", "Error parsing %s\n", filename);
g_markup_parse_context_free(context);
g_free(contents);
g_free(filename);
prefs_loaded = TRUE;
return FALSE;
}
purple_debug_info("prefs", "Finished reading %s\n", filename);
g_markup_parse_context_free(context);
g_free(contents);
g_free(filename);
prefs_loaded = TRUE;
return TRUE;
}
static void
prefs_save_cb(const char *name, PurplePrefType type, gconstpointer val,
gpointer user_data)
{
if(!prefs_loaded)
return;
purple_debug_misc("prefs", "%s changed, scheduling save.\n", name);
schedule_prefs_save();
}
static char *
get_path_dirname(const char *name)
{
char *c, *str;
str = g_strdup(name);
if ((c = strrchr(str, '/')) != NULL) {
*c = '\0';
if (*str == '\0') {
g_free(str);
str = g_strdup("/");
}
}
else {
g_free(str);
str = g_strdup(".");
}
return str;
}
static char *
get_path_basename(const char *name)
{
const char *c;
if ((c = strrchr(name, '/')) != NULL)
return g_strdup(c + 1);
return g_strdup(name);
}
static char *
pref_full_name(struct purple_pref *pref)
{
GString *name;
struct purple_pref *parent;
if(!pref)
return NULL;
if(pref == &prefs)
return g_strdup("/");
name = g_string_new(pref->name);
for(parent = pref->parent; parent && parent->name; parent = parent->parent) {
name = g_string_prepend_c(name, '/');
name = g_string_prepend(name, parent->name);
}
name = g_string_prepend_c(name, '/');
return g_string_free(name, FALSE);
}
static struct purple_pref *
find_pref_parent(const char *name)
{
char *parent_name = get_path_dirname(name);
struct purple_pref *ret = &prefs;
if(!purple_strequal(parent_name, "/")) {
ret = find_pref(parent_name);
}
g_free(parent_name);
return ret;
}
static void
free_pref_value(struct purple_pref *pref)
{
switch(pref->type) {
case PURPLE_PREF_BOOLEAN:
pref->value.boolean = FALSE;
break;
case PURPLE_PREF_INT:
pref->value.integer = 0;
break;
case PURPLE_PREF_STRING:
case PURPLE_PREF_PATH:
g_free(pref->value.string);
pref->value.string = NULL;
break;
case PURPLE_PREF_STRING_LIST:
case PURPLE_PREF_PATH_LIST:
g_list_free_full(pref->value.stringlist, (GDestroyNotify)g_free);
break;
case PURPLE_PREF_NONE:
break;
}
}
static struct purple_pref *
add_pref(PurplePrefType type, const char *name)
{
struct purple_pref *parent;
struct purple_pref *me;
struct purple_pref *sibling;
char *my_name;
parent = find_pref_parent(name);
if(!parent)
return NULL;
my_name = get_path_basename(name);
for(sibling = parent->first_child; sibling; sibling = sibling->sibling) {
if(purple_strequal(sibling->name, my_name)) {
g_free(my_name);
return NULL;
}
}
me = g_new0(struct purple_pref, 1);
me->type = type;
me->name = my_name;
me->parent = parent;
if(parent->first_child) {
/* blatant abuse of a for loop */
for(sibling = parent->first_child; sibling->sibling;
sibling = sibling->sibling);
sibling->sibling = me;
} else {
parent->first_child = me;
}
g_hash_table_insert(prefs_hash, g_strdup(name), (gpointer)me);
return me;
}
void
purple_prefs_add_none(const char *name)
{
PURPLE_PREFS_UI_OP_CALL(add_none, name);
add_pref(PURPLE_PREF_NONE, name);
}
void
purple_prefs_add_bool(const char *name, gboolean value)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL(add_bool, name, value);
pref = add_pref(PURPLE_PREF_BOOLEAN, name);
if(!pref)
return;
pref->value.boolean = value;
}
void
purple_prefs_add_int(const char *name, int value)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL(add_int, name, value);
pref = add_pref(PURPLE_PREF_INT, name);
if(!pref)
return;
pref->value.integer = value;
}
void
purple_prefs_add_string(const char *name, const char *value)
{
struct purple_pref *pref;
if(value != NULL && !g_utf8_validate(value, -1, NULL)) {
purple_debug_error("prefs", "purple_prefs_add_string: Cannot store invalid UTF8 for string pref %s\n", name);
return;
}
PURPLE_PREFS_UI_OP_CALL(add_string, name, value);
pref = add_pref(PURPLE_PREF_STRING, name);
if(!pref)
return;
pref->value.string = g_strdup(value);
}
void
purple_prefs_add_string_list(const char *name, GList *value)
{
struct purple_pref *pref;
GList *tmp;
PURPLE_PREFS_UI_OP_CALL(add_string_list, name, value);
pref = add_pref(PURPLE_PREF_STRING_LIST, name);
if(!pref)
return;
for(tmp = value; tmp; tmp = tmp->next) {
if(tmp->data != NULL && !g_utf8_validate(tmp->data, -1, NULL)) {
purple_debug_error("prefs", "purple_prefs_add_string_list: Skipping invalid UTF8 for string list pref %s\n", name);
continue;
}
pref->value.stringlist = g_list_append(pref->value.stringlist,
g_strdup(tmp->data));
}
}
void
purple_prefs_add_path(const char *name, const char *value)
{
struct purple_pref *pref;
/* re-use the string UI OP */
PURPLE_PREFS_UI_OP_CALL(add_string, name, value);
pref = add_pref(PURPLE_PREF_PATH, name);
if(!pref)
return;
pref->value.string = g_strdup(value);
}
void
purple_prefs_add_path_list(const char *name, GList *value)
{
struct purple_pref *pref;
GList *tmp;
/* re-use the string list UI OP */
PURPLE_PREFS_UI_OP_CALL(add_string_list, name, value);
pref = add_pref(PURPLE_PREF_PATH_LIST, name);
if(!pref)
return;
for(tmp = value; tmp; tmp = tmp->next)
pref->value.stringlist = g_list_append(pref->value.stringlist,
g_strdup(tmp->data));
}
static void
remove_pref(struct purple_pref *pref)
{
char *name;
GSList *l;
if(!pref)
return;
while(pref->first_child)
remove_pref(pref->first_child);
if(pref == &prefs)
return;
if(pref->parent->first_child == pref) {
pref->parent->first_child = pref->sibling;
} else {
struct purple_pref *sib = pref->parent->first_child;
while(sib && sib->sibling != pref)
sib = sib->sibling;
if(sib)
sib->sibling = pref->sibling;
}
name = pref_full_name(pref);
if (prefs_loaded)
purple_debug_info("prefs", "removing pref %s\n", name);
g_hash_table_remove(prefs_hash, name);
g_free(name);
free_pref_value(pref);
while((l = pref->callbacks) != NULL) {
pref->callbacks = pref->callbacks->next;
g_free(l->data);
g_slist_free_1(l);
}
g_free(pref->name);
g_free(pref);
}
void
purple_prefs_remove(const char *name)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL(remove, name);
pref = find_pref(name);
if(!pref)
return;
remove_pref(pref);
}
void
purple_prefs_destroy()
{
purple_prefs_remove("/");
}
static void
do_callbacks(const char* name, struct purple_pref *pref)
{
GSList *cbs;
struct purple_pref *cb_pref;
for(cb_pref = pref; cb_pref; cb_pref = cb_pref->parent) {
for(cbs = cb_pref->callbacks; cbs; cbs = cbs->next) {
PurplePrefCallbackData *cb = cbs->data;
cb->func(name, pref->type, pref->value.generic, cb->data);
}
}
}
static void
do_ui_callbacks(const char *name)
{
GSList *cbs;
purple_debug_misc("prefs", "trigger callback %s\n", name);
for (cbs = ui_callbacks; cbs; cbs = cbs->next) {
PurplePrefCallbackData *cb = cbs->data;
const char *cb_name = cb->name;
size_t len = strlen(cb_name);
if (!strncmp(cb_name, name, len) &&
(name[len] == 0 || name[len] == '/' ||
(len && name[len - 1] == '/'))) {
/* This test should behave like this:
* name = /toto/tata
* cb_name = /toto/tata --> true
* cb_name = /toto/tatatiti --> false
* cb_name = / --> true
* cb_name = /toto --> true
* cb_name = /toto/ --> true
*/
purple_prefs_trigger_callback_object(cbs->data);
}
}
}
void
purple_prefs_trigger_callback(const char *name)
{
struct purple_pref *pref;
PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
if (uiop && uiop->connect_callback) {
do_ui_callbacks(name);
return;
}
pref = find_pref(name);
if(!pref) {
purple_debug_error("prefs",
"purple_prefs_trigger_callback: Unknown pref %s\n", name);
return;
}
do_callbacks(name, pref);
}
/* this function is deprecated, so it doesn't get the new UI ops */
void
purple_prefs_set_generic(const char *name, gpointer value)
{
struct purple_pref *pref = find_pref(name);
if(!pref) {
purple_debug_error("prefs",
"purple_prefs_set_generic: Unknown pref %s\n", name);
return;
}
pref->value.generic = value;
do_callbacks(name, pref);
}
void
purple_prefs_set_bool(const char *name, gboolean value)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL(set_bool, name, value);
pref = find_pref(name);
if(pref) {
if(pref->type != PURPLE_PREF_BOOLEAN) {
purple_debug_error("prefs",
"purple_prefs_set_bool: %s not a boolean pref\n", name);
return;
}
if(pref->value.boolean != value) {
pref->value.boolean = value;
do_callbacks(name, pref);
}
} else {
purple_prefs_add_bool(name, value);
}
}
void
purple_prefs_set_int(const char *name, int value)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL(set_int, name, value);
pref = find_pref(name);
if(pref) {
if(pref->type != PURPLE_PREF_INT) {
purple_debug_error("prefs",
"purple_prefs_set_int: %s not an integer pref\n", name);
return;
}
if(pref->value.integer != value) {
pref->value.integer = value;
do_callbacks(name, pref);
}
} else {
purple_prefs_add_int(name, value);
}
}
void
purple_prefs_set_string(const char *name, const char *value)
{
struct purple_pref *pref;
if(value != NULL && !g_utf8_validate(value, -1, NULL)) {
purple_debug_error("prefs", "purple_prefs_set_string: Cannot store invalid UTF8 for string pref %s\n", name);
return;
}
PURPLE_PREFS_UI_OP_CALL(set_string, name, value);
pref = find_pref(name);
if(pref) {
if(pref->type != PURPLE_PREF_STRING && pref->type != PURPLE_PREF_PATH) {
purple_debug_error("prefs",
"purple_prefs_set_string: %s not a string pref\n", name);
return;
}
if (!purple_strequal(pref->value.string, value)) {
g_free(pref->value.string);
pref->value.string = g_strdup(value);
do_callbacks(name, pref);
}
} else {
purple_prefs_add_string(name, value);
}
}
void
purple_prefs_set_string_list(const char *name, GList *value)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL(set_string_list, name, value);
pref = find_pref(name);
if(pref) {
GList *tmp;
if(pref->type != PURPLE_PREF_STRING_LIST) {
purple_debug_error("prefs",
"purple_prefs_set_string_list: %s not a string list pref\n",
name);
return;
}
g_list_free_full(pref->value.stringlist, (GDestroyNotify)g_free);
pref->value.stringlist = NULL;
for(tmp = value; tmp; tmp = tmp->next) {
if(tmp->data != NULL && !g_utf8_validate(tmp->data, -1, NULL)) {
purple_debug_error("prefs", "purple_prefs_set_string_list: Skipping invalid UTF8 for string list pref %s\n", name);
continue;
}
pref->value.stringlist = g_list_prepend(pref->value.stringlist,
g_strdup(tmp->data));
}
pref->value.stringlist = g_list_reverse(pref->value.stringlist);
do_callbacks(name, pref);
} else {
purple_prefs_add_string_list(name, value);
}
}
void
purple_prefs_set_path(const char *name, const char *value)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL(set_string, name, value);
pref = find_pref(name);
if(pref) {
if(pref->type != PURPLE_PREF_PATH) {
purple_debug_error("prefs",
"purple_prefs_set_path: %s not a path pref\n", name);
return;
}
if (!purple_strequal(pref->value.string, value)) {
g_free(pref->value.string);
pref->value.string = g_strdup(value);
do_callbacks(name, pref);
}
} else {
purple_prefs_add_path(name, value);
}
}
void
purple_prefs_set_path_list(const char *name, GList *value)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL(set_string_list, name, value);
pref = find_pref(name);
if(pref) {
GList *tmp;
if(pref->type != PURPLE_PREF_PATH_LIST) {
purple_debug_error("prefs",
"purple_prefs_set_path_list: %s not a path list pref\n",
name);
return;
}
g_list_free_full(pref->value.stringlist, (GDestroyNotify)g_free);
pref->value.stringlist = NULL;
for(tmp = value; tmp; tmp = tmp->next)
pref->value.stringlist = g_list_prepend(pref->value.stringlist,
g_strdup(tmp->data));
pref->value.stringlist = g_list_reverse(pref->value.stringlist);
do_callbacks(name, pref);
} else {
purple_prefs_add_path_list(name, value);
}
}
gboolean
purple_prefs_exists(const char *name)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL_RETURN(exists, name);
pref = find_pref(name);
if (pref != NULL)
return TRUE;
return FALSE;
}
PurplePrefType
purple_prefs_get_type(const char *name)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL_RETURN(get_type, name);
pref = find_pref(name);
if (pref == NULL)
return PURPLE_PREF_NONE;
return (pref->type);
}
gboolean
purple_prefs_get_bool(const char *name)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL_RETURN(get_bool, name);
pref = find_pref(name);
if(!pref) {
purple_debug_error("prefs",
"purple_prefs_get_bool: Unknown pref %s\n", name);
return FALSE;
} else if(pref->type != PURPLE_PREF_BOOLEAN) {
purple_debug_error("prefs",
"purple_prefs_get_bool: %s not a boolean pref\n", name);
return FALSE;
}
return pref->value.boolean;
}
int
purple_prefs_get_int(const char *name)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL_RETURN(get_int, name);
pref = find_pref(name);
if(!pref) {
purple_debug_error("prefs",
"purple_prefs_get_int: Unknown pref %s\n", name);
return 0;
} else if(pref->type != PURPLE_PREF_INT) {
purple_debug_error("prefs",
"purple_prefs_get_int: %s not an integer pref\n", name);
return 0;
}
return pref->value.integer;
}
const char *
purple_prefs_get_string(const char *name)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL_RETURN(get_string, name);
pref = find_pref(name);
if(!pref) {
purple_debug_error("prefs",
"purple_prefs_get_string: Unknown pref %s\n", name);
return NULL;
} else if(pref->type != PURPLE_PREF_STRING) {
purple_debug_error("prefs",
"purple_prefs_get_string: %s not a string pref\n", name);
return NULL;
}
return pref->value.string;
}
GList *
purple_prefs_get_string_list(const char *name)
{
struct purple_pref *pref;
GList *ret = NULL, *tmp;
PURPLE_PREFS_UI_OP_CALL_RETURN(get_string_list, name);
pref = find_pref(name);
if(!pref) {
purple_debug_error("prefs",
"purple_prefs_get_string_list: Unknown pref %s\n", name);
return NULL;
} else if(pref->type != PURPLE_PREF_STRING_LIST) {
purple_debug_error("prefs",
"purple_prefs_get_string_list: %s not a string list pref\n", name);
return NULL;
}
for(tmp = pref->value.stringlist; tmp; tmp = tmp->next)
ret = g_list_prepend(ret, g_strdup(tmp->data));
ret = g_list_reverse(ret);
return ret;
}
const char *
purple_prefs_get_path(const char *name)
{
struct purple_pref *pref;
PURPLE_PREFS_UI_OP_CALL_RETURN(get_string, name);
pref = find_pref(name);
if(!pref) {
purple_debug_error("prefs",
"purple_prefs_get_path: Unknown pref %s\n", name);
return NULL;
} else if(pref->type != PURPLE_PREF_PATH) {
purple_debug_error("prefs",
"purple_prefs_get_path: %s not a path pref\n", name);
return NULL;
}
return pref->value.string;
}
GList *
purple_prefs_get_path_list(const char *name)
{
struct purple_pref *pref;
GList *ret = NULL, *tmp;
PURPLE_PREFS_UI_OP_CALL_RETURN(get_string_list, name);
pref = find_pref(name);
if(!pref) {
purple_debug_error("prefs",
"purple_prefs_get_path_list: Unknown pref %s\n", name);
return NULL;
} else if(pref->type != PURPLE_PREF_PATH_LIST) {
purple_debug_error("prefs",
"purple_prefs_get_path_list: %s not a path list pref\n", name);
return NULL;
}
for(tmp = pref->value.stringlist; tmp; tmp = tmp->next)
ret = g_list_prepend(ret, g_strdup(tmp->data));
ret = g_list_reverse(ret);
return ret;
}
static void
purple_prefs_rename_node(struct purple_pref *oldpref, struct purple_pref *newpref)
{
struct purple_pref *child, *next;
char *oldname, *newname;
/* if we're a parent, rename the kids first */
for(child = oldpref->first_child; child != NULL; child = next)
{
struct purple_pref *newchild;
next = child->sibling;
for(newchild = newpref->first_child; newchild != NULL; newchild = newchild->sibling)
{
if(purple_strequal(child->name, newchild->name))
{
purple_prefs_rename_node(child, newchild);
break;
}
}
if(newchild == NULL) {
/* no rename happened, we weren't able to find the new pref */
char *tmpname = pref_full_name(child);
purple_debug_error("prefs", "Unable to find rename pref for %s\n", tmpname);
g_free(tmpname);
}
}
oldname = pref_full_name(oldpref);
newname = pref_full_name(newpref);
if (oldpref->type != newpref->type)
{
purple_debug_error("prefs", "Unable to rename %s to %s: differing types\n", oldname, newname);
g_free(oldname);
g_free(newname);
return;
}
purple_debug_info("prefs", "Renaming %s to %s\n", oldname, newname);
g_free(oldname);
switch(oldpref->type) {
case PURPLE_PREF_NONE:
break;
case PURPLE_PREF_BOOLEAN:
purple_prefs_set_bool(newname, oldpref->value.boolean);
break;
case PURPLE_PREF_INT:
purple_prefs_set_int(newname, oldpref->value.integer);
break;
case PURPLE_PREF_STRING:
purple_prefs_set_string(newname, oldpref->value.string);
break;
case PURPLE_PREF_STRING_LIST:
purple_prefs_set_string_list(newname, oldpref->value.stringlist);
break;
case PURPLE_PREF_PATH:
purple_prefs_set_path(newname, oldpref->value.string);
break;
case PURPLE_PREF_PATH_LIST:
purple_prefs_set_path_list(newname, oldpref->value.stringlist);
break;
}
g_free(newname);
remove_pref(oldpref);
}
void
purple_prefs_rename(const char *oldname, const char *newname)
{
struct purple_pref *oldpref, *newpref;
/* win32dep.h causes rename to be defined as wpurple_rename, so we need to undefine it here */
#if defined(_WIN32) && defined(rename)
#undef rename
#endif
PURPLE_PREFS_UI_OP_CALL(rename, oldname, newname);
oldpref = find_pref(oldname);
/* it's already been renamed, call off the dogs */
if(!oldpref)
return;
newpref = find_pref(newname);
if (newpref == NULL)
{
purple_debug_error("prefs", "Unable to rename %s to %s: new pref not created\n", oldname, newname);
return;
}
purple_prefs_rename_node(oldpref, newpref);
}
void
purple_prefs_rename_boolean_toggle(const char *oldname, const char *newname)
{
struct purple_pref *oldpref, *newpref;
PURPLE_PREFS_UI_OP_CALL(rename_boolean_toggle, oldname, newname);
oldpref = find_pref(oldname);
/* it's already been renamed, call off the cats */
if(!oldpref)
return;
if (oldpref->type != PURPLE_PREF_BOOLEAN)
{
purple_debug_error("prefs", "Unable to rename %s to %s: old pref not a boolean\n", oldname, newname);
return;
}
if (oldpref->first_child != NULL) /* can't rename parents */
{
purple_debug_error("prefs", "Unable to rename %s to %s: can't rename parents\n", oldname, newname);
return;
}
newpref = find_pref(newname);
if (newpref == NULL)
{
purple_debug_error("prefs", "Unable to rename %s to %s: new pref not created\n", oldname, newname);
return;
}
if (oldpref->type != newpref->type)
{
purple_debug_error("prefs", "Unable to rename %s to %s: differing types\n", oldname, newname);
return;
}
purple_debug_info("prefs", "Renaming and toggling %s to %s\n", oldname, newname);
purple_prefs_set_bool(newname, !(oldpref->value.boolean));
remove_pref(oldpref);
}
guint
purple_prefs_connect_callback(void *handle, const char *name, PurplePrefCallback func, gpointer data)
{
struct purple_pref *pref = NULL;
PurplePrefCallbackData *cb;
static guint cb_id = 0;
PurplePrefsUiOps *uiop = NULL;
g_return_val_if_fail(name != NULL, 0);
g_return_val_if_fail(func != NULL, 0);
uiop = purple_prefs_get_ui_ops();
if (!(uiop && uiop->connect_callback)) {
pref = find_pref(name);
if (pref == NULL) {
purple_debug_error("prefs", "purple_prefs_connect_callback: Unknown pref %s\n", name);
return 0;
}
}
cb = g_new0(PurplePrefCallbackData, 1);
cb->func = func;
cb->data = data;
cb->id = ++cb_id;
cb->handle = handle;
cb->name = g_strdup(name);
if (uiop && uiop->connect_callback) {
cb->ui_data = uiop->connect_callback(name, cb);
if (cb->ui_data == NULL) {
purple_debug_error("prefs", "purple_prefs_connect_callback: connect failed for %s\n", name);
g_free(cb->name);
g_free(cb);
return 0;
}
ui_callbacks = g_slist_append(ui_callbacks, cb);
} else {
pref->callbacks = g_slist_append(pref->callbacks, cb);
}
return cb->id;
}
static void
purple_prefs_trigger_ui_callback_object(PurplePrefCallbackData *cb)
{
PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
gconstpointer value = NULL;
PurplePrefType type = PURPLE_PREF_NONE;
type = uiop->get_type(cb->name);
switch (type) {
case PURPLE_PREF_INT:
if (uiop->get_int) {
value = GINT_TO_POINTER(uiop->get_int(cb->name));
}
break;
case PURPLE_PREF_BOOLEAN:
if (uiop->get_bool) {
value = GINT_TO_POINTER(uiop->get_bool(cb->name));
}
break;
case PURPLE_PREF_STRING:
case PURPLE_PREF_PATH:
if (uiop->get_string) {
value = uiop->get_string(cb->name);
}
break;
case PURPLE_PREF_STRING_LIST:
case PURPLE_PREF_PATH_LIST:
if (uiop->get_string_list) {
value = uiop->get_string_list(cb->name);
}
break;
case PURPLE_PREF_NONE:
break;
default:
purple_debug_error("prefs", "Unexpected type = %i\n", type);
}
cb->func(cb->name, type, value, cb->data);
}
void
purple_prefs_trigger_callback_object(PurplePrefCallbackData *cb)
{
PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
if (uiop && uiop->connect_callback && uiop->get_type) {
purple_prefs_trigger_ui_callback_object(cb);
} else {
purple_prefs_trigger_callback(cb->name);
}
}
static gboolean
disco_callback_helper(struct purple_pref *pref, guint callback_id)
{
GSList *cbs;
struct purple_pref *child;
if(!pref)
return FALSE;
for(cbs = pref->callbacks; cbs; cbs = cbs->next) {
PurplePrefCallbackData *cb = cbs->data;
if(cb->id == callback_id) {
pref->callbacks = g_slist_delete_link(pref->callbacks, cbs);
g_free(cb->name);
g_free(cb);
return TRUE;
}
}
for(child = pref->first_child; child; child = child->sibling) {
if(disco_callback_helper(child, callback_id))
return TRUE;
}
return FALSE;
}
static void
disco_ui_callback_helper(guint callback_id)
{
GSList *cbs;
PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
for (cbs = ui_callbacks; cbs; cbs = cbs->next) {
PurplePrefCallbackData *cb = cbs->data;
if (cb->id == callback_id) {
uiop->disconnect_callback(cb->name, cb->ui_data);
ui_callbacks = g_slist_delete_link(ui_callbacks, cbs);
g_free(cb->name);
g_free(cb);
return;
}
}
}
void
purple_prefs_disconnect_callback(guint callback_id)
{
PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
if (uiop && uiop->disconnect_callback) {
disco_ui_callback_helper(callback_id);
} else {
disco_callback_helper(&prefs, callback_id);
}
}
static void
disco_callback_helper_handle(struct purple_pref *pref, void *handle)
{
GSList *cbs;
struct purple_pref *child;
if(!pref)
return;
cbs = pref->callbacks;
while (cbs != NULL) {
PurplePrefCallbackData *cb = cbs->data;
if(cb->handle == handle) {
pref->callbacks = g_slist_delete_link(pref->callbacks, cbs);
g_free(cb->name);
g_free(cb);
cbs = pref->callbacks;
} else
cbs = cbs->next;
}
for(child = pref->first_child; child; child = child->sibling)
disco_callback_helper_handle(child, handle);
}
static void
disco_ui_callback_helper_handle(void *handle)
{
GSList *cbs;
PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
cbs = ui_callbacks;
for (cbs = ui_callbacks; cbs; cbs = cbs->next) {
PurplePrefCallbackData *cb = cbs->data;
if (cb->handle != handle) {
cbs = cbs->next;
continue;
}
uiop->disconnect_callback(cb->name, cb->ui_data);
ui_callbacks = g_slist_delete_link(ui_callbacks, cbs);
g_free(cb->name);
g_free(cb);
cbs = ui_callbacks;
}
}
void
purple_prefs_disconnect_by_handle(void *handle)
{
PurplePrefsUiOps *uiop = purple_prefs_get_ui_ops();
g_return_if_fail(handle != NULL);
if (uiop && uiop->disconnect_callback) {
disco_ui_callback_helper_handle(handle);
} else {
disco_callback_helper_handle(&prefs, handle);
}
}
GList *
purple_prefs_get_children_names(const char *name)
{
GList * list = NULL;
struct purple_pref *pref, *child;
char sep[2] = "\0\0";;
PURPLE_PREFS_UI_OP_CALL_RETURN(get_children_names, name);
pref = find_pref(name);
if (pref == NULL)
return NULL;
if (name[strlen(name) - 1] != '/')
sep[0] = '/';
for (child = pref->first_child; child; child = child->sibling) {
list = g_list_append(list, g_strdup_printf("%s%s%s", name, sep, child->name));
}
return list;
}
void
purple_prefs_update_old()
{
purple_prefs_rename("/core", "/purple");
/* Remove some no-longer-used prefs */
purple_prefs_remove("/purple/away/auto_response/enabled");
purple_prefs_remove("/purple/away/auto_response/idle_only");
purple_prefs_remove("/purple/away/auto_response/in_active_conv");
purple_prefs_remove("/purple/away/auto_response/sec_before_resend");
purple_prefs_remove("/purple/away/auto_response");
purple_prefs_remove("/purple/away/default_message");
purple_prefs_remove("/purple/buddies/use_server_alias");
purple_prefs_remove("/purple/conversations/away_back_on_send");
purple_prefs_remove("/purple/conversations/send_urls_as_links");
purple_prefs_remove("/purple/conversations/im/show_login");
purple_prefs_remove("/purple/conversations/chat/show_join");
purple_prefs_remove("/purple/conversations/chat/show_leave");
purple_prefs_remove("/purple/conversations/combine_chat_im");
purple_prefs_remove("/purple/conversations/use_alias_for_title");
purple_prefs_remove("/purple/logging/log_signon_signoff");
purple_prefs_remove("/purple/logging/log_idle_state");
purple_prefs_remove("/purple/logging/log_away_state");
purple_prefs_remove("/purple/logging/log_own_states");
purple_prefs_remove("/purple/status/scores/hidden");
purple_prefs_remove("/plugins/core/autorecon/hide_connected_error");
purple_prefs_remove("/plugins/core/autorecon/hide_connecting_error");
purple_prefs_remove("/plugins/core/autorecon/hide_reconnecting_dialog");
purple_prefs_remove("/plugins/core/autorecon/restore_state");
purple_prefs_remove("/plugins/core/autorecon");
/* Convert old sounds while_away pref to new 3-way pref. */
if (purple_prefs_exists("/purple/sound/while_away") &&
purple_prefs_get_bool("/purple/sound/while_away"))
{
purple_prefs_set_int("/purple/sound/while_status", 3);
}
purple_prefs_remove("/purple/sound/while_away");
}
void *
purple_prefs_get_handle(void)
{
static int handle;
return &handle;
}
void
purple_prefs_init(void)
{
void *handle = purple_prefs_get_handle();
prefs_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
purple_prefs_connect_callback(handle, "/", prefs_save_cb, NULL);
purple_prefs_add_none("/purple");
purple_prefs_add_none("/plugins");
purple_prefs_add_none("/plugins/core");
purple_prefs_add_none("/plugins/lopl");
purple_prefs_add_none("/plugins/prpl");
/* Away */
purple_prefs_add_none("/purple/away");
purple_prefs_add_string("/purple/away/idle_reporting", "system");
purple_prefs_add_bool("/purple/away/away_when_idle", TRUE);
purple_prefs_add_int("/purple/away/mins_before_away", 5);
/* Away -> Auto-Reply */
if (!purple_prefs_exists("/purple/away/auto_response/enabled") ||
!purple_prefs_exists("/purple/away/auto_response/idle_only"))
{
purple_prefs_add_string("/purple/away/auto_reply", "awayidle");
}
else
{
if (!purple_prefs_get_bool("/purple/away/auto_response/enabled"))
{
purple_prefs_add_string("/purple/away/auto_reply", "never");
}
else
{
if (purple_prefs_get_bool("/purple/away/auto_response/idle_only"))
{
purple_prefs_add_string("/purple/away/auto_reply", "awayidle");
}
else
{
purple_prefs_add_string("/purple/away/auto_reply", "away");
}
}
}
/* Buddies */
purple_prefs_add_none("/purple/buddies");
/* Contact Priority Settings */
purple_prefs_add_none("/purple/contact");
purple_prefs_add_bool("/purple/contact/last_match", FALSE);
purple_prefs_remove("/purple/contact/offline_score");
purple_prefs_remove("/purple/contact/away_score");
purple_prefs_remove("/purple/contact/idle_score");
purple_prefs_load();
purple_prefs_update_old();
}
void
purple_prefs_uninit()
{
if (save_timer != 0)
{
purple_timeout_remove(save_timer);
save_cb(NULL);
}
purple_prefs_disconnect_by_handle(purple_prefs_get_handle());
prefs_loaded = FALSE;
purple_prefs_destroy();
g_hash_table_destroy(prefs_hash);
prefs_hash = NULL;
}
void
purple_prefs_set_ui_ops(PurplePrefsUiOps *ops)
{
prefs_ui_ops = ops;
}
PurplePrefsUiOps *
purple_prefs_get_ui_ops(void)
{
return prefs_ui_ops;
}