/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* GConf
* Copyright (C) 1999, 2000, 2000 Red Hat Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <stdio.h>
#include <string.h>
#include "gconf-client.h"
#include <gconf/gconf-internals.h>
#include "gconfmarshal.h"
#include "gconfmarshal.c"
static gboolean do_trace = FALSE;
static void
trace (const char *format, ...)
{
va_list args;
gchar *str;
FILE *out;
if (!do_trace)
return;
g_return_if_fail (format != NULL);
va_start (args, format);
str = g_strdup_vprintf (format, args);
va_end (args);
out = stderr;
fputs ("gconf trace: ", out);
fputs (str, out);
g_free (str);
}
/*
* Error handler override
*/
static GConfClientErrorHandlerFunc global_error_handler = NULL;
void
gconf_client_set_global_default_error_handler(GConfClientErrorHandlerFunc func)
{
global_error_handler = func;
}
/*
* Dir object (for list of directories we're watching)
*/
typedef struct _Dir Dir;
struct _Dir {
gchar* name;
guint notify_id;
/* number of times this dir has been added */
guint add_count;
};
static Dir* dir_new(const gchar* name, guint notify_id);
static void dir_destroy(Dir* d);
/*
* Listener object
*/
typedef struct _Listener Listener;
struct _Listener {
GConfClientNotifyFunc func;
gpointer data;
GFreeFunc destroy_notify;
};
static Listener* listener_new(GConfClientNotifyFunc func,
GFreeFunc destroy_notify,
gpointer data);
static void listener_destroy(Listener* l);
/*
* GConfClient proper
*/
#define PUSH_USE_ENGINE(client) do { if ((client)->engine) gconf_engine_push_owner_usage ((client)->engine, client); } while (0)
#define POP_USE_ENGINE(client) do { if ((client)->engine) gconf_engine_pop_owner_usage ((client)->engine, client); } while (0)
enum {
VALUE_CHANGED,
UNRETURNED_ERROR,
ERROR,
LAST_SIGNAL
};
static void register_client (GConfClient *client);
static GConfClient *lookup_client (GConfEngine *engine);
static void unregister_client (GConfClient *client);
static void gconf_client_class_init (GConfClientClass *klass);
static void gconf_client_init (GConfClient *client);
static void gconf_client_real_unreturned_error (GConfClient* client, GError* error);
static void gconf_client_real_error (GConfClient* client, GError* error);
static void gconf_client_finalize (GObject* object);
static gboolean gconf_client_cache (GConfClient *client,
gboolean take_ownership,
GConfEntry *entry,
gboolean preserve_schema_name);
static gboolean gconf_client_lookup (GConfClient *client,
const char *key,
GConfEntry **entryp);
static void gconf_client_real_remove_dir (GConfClient* client,
Dir* d,
GError** err);
static void gconf_client_queue_notify (GConfClient *client,
const char *key);
static void gconf_client_flush_notifies (GConfClient *client);
static void gconf_client_unqueue_notifies (GConfClient *client);
static void notify_one_entry (GConfClient *client, GConfEntry *entry);
static GConfEntry* get (GConfClient *client,
const gchar *key,
gboolean use_default,
GError **error);
static gpointer parent_class = NULL;
static guint client_signals[LAST_SIGNAL] = { 0 };
GType
gconf_client_get_type (void)
{
static GType client_type = 0;
if (!client_type)
{
static const GTypeInfo client_info =
{
sizeof (GConfClientClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) gconf_client_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (GConfClient),
0, /* n_preallocs */
(GInstanceInitFunc) gconf_client_init
};
client_type = g_type_register_static (G_TYPE_OBJECT,
"GConfClient",
&client_info,
0);
}
return client_type;
}
static void
gconf_client_class_init (GConfClientClass *class)
{
GObjectClass *object_class;
object_class = (GObjectClass*) class;
parent_class = g_type_class_peek_parent (class);
client_signals[VALUE_CHANGED] =
g_signal_new ("value_changed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GConfClientClass, value_changed),
NULL, NULL,
gconf_marshal_VOID__STRING_POINTER,
G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_POINTER);
client_signals[UNRETURNED_ERROR] =
g_signal_new ("unreturned_error",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GConfClientClass, unreturned_error),
NULL, NULL,
gconf_marshal_VOID__POINTER,
G_TYPE_NONE, 1, G_TYPE_POINTER);
client_signals[ERROR] =
g_signal_new ("error",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GConfClientClass, error),
NULL, NULL,
gconf_marshal_VOID__POINTER,
G_TYPE_NONE, 1, G_TYPE_POINTER);
class->value_changed = NULL;
class->unreturned_error = gconf_client_real_unreturned_error;
class->error = gconf_client_real_error;
object_class->finalize = gconf_client_finalize;
if (g_getenv ("GCONF_DEBUG_TRACE_CLIENT") != NULL)
do_trace = TRUE;
}
static void
gconf_client_init (GConfClient *client)
{
client->engine = NULL;
client->error_mode = GCONF_CLIENT_HANDLE_UNRETURNED;
client->dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
client->cache_hash = g_hash_table_new (g_str_hash, g_str_equal);
/* We create the listeners only if they're actually used */
client->listeners = NULL;
client->notify_list = NULL;
client->notify_handler = 0;
}
static gboolean
destroy_dir_foreach_remove(gpointer key, gpointer value, gpointer user_data)
{
GConfClient *client = user_data;
Dir* d = value;
/* remove notify for this dir */
if (d->notify_id != 0)
{
trace ("Removing notify ID %u from engine\n", d->notify_id);
PUSH_USE_ENGINE (client);
gconf_engine_notify_remove (client->engine, d->notify_id);
POP_USE_ENGINE (client);
}
d->notify_id = 0;
dir_destroy(value);
return TRUE;
}
static void
set_engine (GConfClient *client,
GConfEngine *engine)
{
if (engine == client->engine)
return;
if (engine)
{
gconf_engine_ref (engine);
gconf_engine_set_owner (engine, client);
}
if (client->engine)
{
gconf_engine_set_owner (client->engine, NULL);
gconf_engine_unref (client->engine);
}
client->engine = engine;
}
static void
gconf_client_finalize (GObject* object)
{
GConfClient* client = GCONF_CLIENT(object);
gconf_client_unqueue_notifies (client);
g_hash_table_foreach_remove (client->dir_hash,
destroy_dir_foreach_remove, client);
gconf_client_clear_cache (client);
if (client->listeners != NULL)
{
gconf_listeners_free (client->listeners);
client->listeners = NULL;
}
g_hash_table_destroy (client->dir_hash);
client->dir_hash = NULL;
g_hash_table_destroy (client->cache_hash);
client->cache_hash = NULL;
unregister_client (client);
set_engine (client, NULL);
if (G_OBJECT_CLASS (parent_class)->finalize)
(* G_OBJECT_CLASS (parent_class)->finalize) (object);
}
/*
* Default error handlers
*/
static void
gconf_client_real_unreturned_error (GConfClient* client, GError* error)
{
trace ("Unreturned error '%s'\n", error->message);
if (client->error_mode == GCONF_CLIENT_HANDLE_UNRETURNED)
{
if (global_error_handler != NULL)
{
(*global_error_handler) (client, error);
}
else
{
/* We silently ignore this, since it probably isn't
* really an error per se
*/
if (error->code == GCONF_ERROR_OVERRIDDEN ||
error->code == GCONF_ERROR_NO_WRITABLE_DATABASE)
return;
g_printerr (_("GConf Error: %s\n"),
error->message);
}
}
}
static void
gconf_client_real_error (GConfClient* client, GError* error)
{
trace ("Error '%s'\n", error->message);
if (client->error_mode == GCONF_CLIENT_HANDLE_ALL)
{
if (global_error_handler != NULL)
{
(*global_error_handler) (client, error);
}
else
{
g_printerr (_("GConf Error: %s\n"),
error->message);
}
}
}
/* Emit the proper signals for the error, and fill in err */
static gboolean
handle_error(GConfClient* client, GError* error, GError** err)
{
if (error != NULL)
{
gconf_client_error(client, error);
if (err == NULL)
{
gconf_client_unreturned_error(client, error);
g_error_free(error);
}
else
*err = error;
return TRUE;
}
else
return FALSE;
}
static void
notify_from_server_callback (GConfEngine* conf, guint cnxn_id,
GConfEntry *entry,
gpointer user_data)
{
GConfClient* client = user_data;
gboolean changed;
g_return_if_fail (client != NULL);
g_return_if_fail (GCONF_IS_CLIENT(client));
g_return_if_fail (client->engine == conf);
trace ("Received notify of change to '%s' from server\n",
entry->key);
/* First do the caching, so that state is sane for the
* listeners or functions connected to value_changed.
* We know this key is under a directory in our dir list.
*/
changed = gconf_client_cache (client, FALSE, entry, TRUE);
if (!changed)
return; /* don't do the notify */
gconf_client_queue_notify (client, entry->key);
}
/*
* Public API
*/
GConfClient*
gconf_client_get_default (void)
{
GConfClient *client;
GConfEngine *engine;
g_return_val_if_fail(gconf_is_initialized(), NULL);
engine = gconf_engine_get_default ();
client = lookup_client (engine);
if (client)
{
g_assert (client->engine == engine);
g_object_ref (G_OBJECT (client));
gconf_engine_unref (engine);
return client;
}
else
{
client = g_object_new (gconf_client_get_type (), NULL);
g_object_ref (G_OBJECT (client));
set_engine (client, engine);
register_client (client);
}
return client;
}
GConfClient*
gconf_client_get_for_engine (GConfEngine* engine)
{
GConfClient *client;
g_return_val_if_fail(gconf_is_initialized(), NULL);
client = lookup_client (engine);
if (client)
{
g_assert (client->engine == engine);
g_object_ref (G_OBJECT (client));
return client;
}
else
{
client = g_object_new (gconf_client_get_type (), NULL);
set_engine (client, engine);
register_client (client);
}
return client;
}
typedef struct {
GConfClient *client;
Dir *lower_dir;
const char *dirname;
} OverlapData;
static void
foreach_setup_overlap(gpointer key, gpointer value, gpointer user_data)
{
GConfClient *client;
Dir *dir = value;
OverlapData * od = user_data;
client = od->client;
/* if we have found the first (well there is only one anyway) directory
* that includes us that has a notify handler
*/
#ifdef GCONF_ENABLE_DEBUG
if (dir->notify_id != 0 &&
gconf_key_is_below(dir->name, od->dirname))
{
g_assert(od->lower_dir == NULL);
od->lower_dir = dir;
}
#else
if (od->lower_dir == NULL &&
dir->notify_id != 0 &&
gconf_key_is_below(dir->name, od->dirname))
od->lower_dir = dir;
#endif
/* if we have found a directory that we include and it has
* a notify_id, remove the notify handler now
* FIXME: this is a race, from now on we can miss notifies, it is
* not an incredible amount of time so this is not a showstopper */
else if (dir->notify_id != 0 &&
gconf_key_is_below (od->dirname, dir->name))
{
PUSH_USE_ENGINE (client);
gconf_engine_notify_remove (client->engine, dir->notify_id);
POP_USE_ENGINE (client);
dir->notify_id = 0;
}
}
static Dir *
setup_overlaps(GConfClient* client, const gchar* dirname)
{
OverlapData od;
od.client = client;
od.lower_dir = NULL;
od.dirname = dirname;
g_hash_table_foreach(client->dir_hash, foreach_setup_overlap, &od);
return od.lower_dir;
}
void
gconf_client_add_dir (GConfClient* client,
const gchar* dirname,
GConfClientPreloadType preload,
GError** err)
{
Dir* d;
guint notify_id = 0;
GError* error = NULL;
g_return_if_fail (gconf_valid_key (dirname, NULL));
trace ("Adding directory '%s'\n", dirname);
d = g_hash_table_lookup (client->dir_hash, dirname);
if (d == NULL)
{
Dir *overlap_dir;
overlap_dir = setup_overlaps (client, dirname);
/* only if there is no directory that includes us
* already add a notify
*/
if (overlap_dir == NULL)
{
trace ("Adding notify to engine at '%s'\n",
dirname);
PUSH_USE_ENGINE (client);
notify_id = gconf_engine_notify_add (client->engine,
dirname,
notify_from_server_callback,
client,
&error);
POP_USE_ENGINE (client);
/* We got a notify ID or we got an error, not both */
g_return_if_fail ( (notify_id != 0 && error == NULL) ||
(notify_id == 0 && error != NULL) );
if (handle_error (client, error, err))
return;
g_assert (error == NULL);
}
else
{
notify_id = 0;
}
d = dir_new (dirname, notify_id);
g_hash_table_insert (client->dir_hash, d->name, d);
gconf_client_preload (client, dirname, preload, &error);
handle_error (client, error, err);
}
g_assert (d != NULL);
d->add_count += 1;
}
typedef struct {
GConfClient *client;
GError *error;
} AddNotifiesData;
static void
foreach_add_notifies(gpointer key, gpointer value, gpointer user_data)
{
AddNotifiesData *ad = user_data;
GConfClient *client;
Dir *dir = value;
client = ad->client;
if (ad->error != NULL)
return;
if (dir->notify_id == 0)
{
Dir *overlap_dir;
overlap_dir = setup_overlaps(client, dir->name);
/* only if there is no directory that includes us
* already add a notify */
if (overlap_dir == NULL)
{
PUSH_USE_ENGINE (client);
dir->notify_id = gconf_engine_notify_add(client->engine,
dir->name,
notify_from_server_callback,
client,
&ad->error);
POP_USE_ENGINE (client);
/* We got a notify ID or we got an error, not both */
g_return_if_fail( (dir->notify_id != 0 && ad->error == NULL) ||
(dir->notify_id == 0 && ad->error != NULL) );
/* if error is returned, then we'll just ignore
* things until the end */
}
}
}
static gboolean
clear_dir_cache_foreach (char* key, GConfEntry* entry, char *dir)
{
if (gconf_key_is_below (dir, key))
{
gconf_entry_free (entry);
return TRUE;
}
else
return FALSE;
}
static void
gconf_client_real_remove_dir (GConfClient* client,
Dir* d,
GError** err)
{
AddNotifiesData ad;
g_return_if_fail(d != NULL);
g_return_if_fail(d->add_count == 0);
g_hash_table_remove(client->dir_hash, d->name);
/* remove notify for this dir */
if (d->notify_id != 0)
{
trace ("Removing notify from engine at '%s'\n", d->name);
PUSH_USE_ENGINE (client);
gconf_engine_notify_remove (client->engine, d->notify_id);
POP_USE_ENGINE (client);
d->notify_id = 0;
}
g_hash_table_foreach_remove (client->cache_hash,
(GHRFunc)clear_dir_cache_foreach,
d->name);
dir_destroy(d);
ad.client = client;
ad.error = NULL;
g_hash_table_foreach(client->dir_hash, foreach_add_notifies, &ad);
handle_error(client, ad.error, err);
}
void
gconf_client_remove_dir (GConfClient* client,
const gchar* dirname,
GError** err)
{
Dir* found = NULL;
trace ("Removing directory '%s'\n", dirname);
found = g_hash_table_lookup (client->dir_hash,
dirname);
if (found != NULL)
{
g_return_if_fail(found->add_count > 0);
found->add_count -= 1;
if (found->add_count == 0)
gconf_client_real_remove_dir(client, found, err);
}
#ifndef G_DISABLE_CHECKS
else
g_warning("Directory `%s' was not being monitored by GConfClient %p",
dirname, client);
#endif
}
guint
gconf_client_notify_add (GConfClient* client,
const gchar* namespace_section,
GConfClientNotifyFunc func,
gpointer user_data,
GFreeFunc destroy_notify,
GError** err)
{
guint cnxn_id = 0;
g_return_val_if_fail(client != NULL, 0);
g_return_val_if_fail(GCONF_IS_CLIENT(client), 0);
if (client->listeners == NULL)
client->listeners = gconf_listeners_new();
cnxn_id = gconf_listeners_add (client->listeners,
namespace_section,
listener_new (func, destroy_notify, user_data),
(GFreeFunc)listener_destroy);
return cnxn_id;
}
void
gconf_client_notify_remove (GConfClient* client,
guint cnxn)
{
g_return_if_fail(client != NULL);
g_return_if_fail(GCONF_IS_CLIENT(client));
g_return_if_fail(client->listeners != NULL);
gconf_listeners_remove(client->listeners, cnxn);
if (gconf_listeners_count(client->listeners) == 0)
{
gconf_listeners_free(client->listeners);
client->listeners = NULL;
}
}
void
gconf_client_notify (GConfClient* client, const char* key)
{
GConfEntry *entry;
g_return_if_fail (client != NULL);
g_return_if_fail (GCONF_IS_CLIENT(client));
g_return_if_fail (key != NULL);
entry = gconf_client_get_entry (client, key, NULL, TRUE, NULL);
if (entry != NULL)
{
notify_one_entry (client, entry);
gconf_entry_unref (entry);
}
}
void
gconf_client_set_error_handling(GConfClient* client,
GConfClientErrorHandlingMode mode)
{
g_return_if_fail(client != NULL);
g_return_if_fail(GCONF_IS_CLIENT(client));
client->error_mode = mode;
}
static gboolean
clear_cache_foreach (char* key, GConfEntry* entry, GConfClient* client)
{
gconf_entry_free (entry);
return TRUE;
}
void
gconf_client_clear_cache(GConfClient* client)
{
g_return_if_fail(client != NULL);
g_return_if_fail(GCONF_IS_CLIENT(client));
trace ("Clearing cache\n");
g_hash_table_foreach_remove (client->cache_hash, (GHRFunc)clear_cache_foreach,
client);
g_assert (g_hash_table_size(client->cache_hash) == 0);
}
static void
cache_pairs_in_dir(GConfClient* client, const gchar* path);
static void
recurse_subdir_list(GConfClient* client, GSList* subdirs)
{
GSList* tmp;
tmp = subdirs;
while (tmp != NULL)
{
gchar* s = tmp->data;
cache_pairs_in_dir(client, s);
PUSH_USE_ENGINE (client);
recurse_subdir_list(client,
gconf_engine_all_dirs (client->engine, s, NULL));
POP_USE_ENGINE (client);
g_free(s);
tmp = g_slist_next(tmp);
}
g_slist_free(subdirs);
}
static gboolean
key_being_monitored (GConfClient *client,
const char *key)
{
gboolean retval = FALSE;
char* parent = g_strdup (key);
char* end;
end = parent + strlen (parent);
while (end)
{
if (end == parent)
*(end + 1) = '\0'; /* special-case "/" root dir */
else
*end = '\0'; /* chop '/' off of dir */
if (g_hash_table_lookup (client->dir_hash, parent) != NULL)
{
retval = TRUE;
break;
}
if (end != parent)
end = strrchr (parent, '/');
else
end = NULL;
}
g_free (parent);
return retval;
}
static void
cache_entry_list_destructively (GConfClient *client,
GSList *entries)
{
GSList *tmp;
tmp = entries;
while (tmp != NULL)
{
GConfEntry* entry = tmp->data;
gconf_client_cache (client, TRUE, entry, FALSE);
tmp = g_slist_next (tmp);
}
g_slist_free (entries);
}
static void
cache_pairs_in_dir(GConfClient* client, const gchar* dir)
{
GSList* pairs;
GError* error = NULL;
trace ("Caching values in '%s'\n", dir);
PUSH_USE_ENGINE (client);
pairs = gconf_engine_all_entries(client->engine, dir, &error);
POP_USE_ENGINE (client);
if (error != NULL)
{
g_printerr (_("GConf warning: failure listing pairs in `%s': %s"),
dir, error->message);
g_error_free(error);
error = NULL;
}
cache_entry_list_destructively (client, pairs);
}
void
gconf_client_preload (GConfClient* client,
const gchar* dirname,
GConfClientPreloadType type,
GError** err)
{
g_return_if_fail(client != NULL);
g_return_if_fail(GCONF_IS_CLIENT(client));
g_return_if_fail(dirname != NULL);
#ifdef GCONF_ENABLE_DEBUG
if (!key_being_monitored (client, dirname))
{
g_warning("Can only preload directories you've added with gconf_client_add_dir() (tried to preload %s)",
dirname);
return;
}
#endif
switch (type)
{
case GCONF_CLIENT_PRELOAD_NONE:
/* nothing */
break;
case GCONF_CLIENT_PRELOAD_ONELEVEL:
{
trace ("Onelevel preload of '%s'\n", dirname);
cache_pairs_in_dir (client, dirname);
}
break;
case GCONF_CLIENT_PRELOAD_RECURSIVE:
{
GSList* subdirs;
trace ("Recursive preload of '%s'\n", dirname);
PUSH_USE_ENGINE (client);
subdirs = gconf_engine_all_dirs(client->engine, dirname, NULL);
POP_USE_ENGINE (client);
cache_pairs_in_dir(client, dirname);
recurse_subdir_list(client, subdirs);
}
break;
default:
g_assert_not_reached();
break;
}
}
/*
* Basic key-manipulation facilities
*/
void
gconf_client_set (GConfClient* client,
const gchar* key,
const GConfValue* val,
GError** err)
{
GError* error = NULL;
trace ("Setting value of '%s'\n", key);
PUSH_USE_ENGINE (client);
gconf_engine_set (client->engine, key, val, &error);
POP_USE_ENGINE (client);
handle_error(client, error, err);
}
gboolean
gconf_client_unset (GConfClient* client,
const gchar* key, GError** err)
{
GError* error = NULL;
trace ("Unsetting '%s'\n", key);
PUSH_USE_ENGINE (client);
gconf_engine_unset(client->engine, key, &error);
POP_USE_ENGINE (client);
handle_error(client, error, err);
if (error != NULL)
return FALSE;
else
return TRUE;
}
gboolean
gconf_client_recursive_unset (GConfClient *client,
const char *key,
GConfUnsetFlags flags,
GError **err)
{
GError* error = NULL;
trace ("Unsetting '%s'\n", key);
PUSH_USE_ENGINE (client);
gconf_engine_recursive_unset(client->engine, key, flags, &error);
POP_USE_ENGINE (client);
handle_error(client, error, err);
if (error != NULL)
return FALSE;
else
return TRUE;
}
static GSList*
copy_entry_list (GSList *list)
{
GSList *copy;
GSList *tmp;
copy = NULL;
tmp = list;
while (tmp != NULL)
{
copy = g_slist_prepend (copy,
gconf_entry_copy (tmp->data));
tmp = tmp->next;
}
copy = g_slist_reverse (copy);
return copy;
}
GSList*
gconf_client_all_entries (GConfClient* client,
const gchar* dir,
GError** err)
{
GError *error = NULL;
GSList *retval;
trace ("Getting all values in '%s'\n", dir);
/* We could just use the cache to get all the entries,
* iff we have previously done an all_entries and the
* cache hasn't since been tossed out, and if we are monitoring
* this directory.
* FIXME
*/
PUSH_USE_ENGINE (client);
retval = gconf_engine_all_entries (client->engine, dir, &error);
POP_USE_ENGINE (client);
handle_error (client, error, err);
if (error != NULL)
return NULL;
if (key_being_monitored (client, dir))
cache_entry_list_destructively (client, copy_entry_list (retval));
return retval;
}
GSList*
gconf_client_all_dirs (GConfClient* client,
const gchar* dir, GError** err)
{
GError* error = NULL;
GSList* retval;
trace ("Getting all dirs in '%s'\n", dir);
PUSH_USE_ENGINE (client);
retval = gconf_engine_all_dirs(client->engine, dir, &error);
POP_USE_ENGINE (client);
handle_error(client, error, err);
return retval;
}
void
gconf_client_suggest_sync (GConfClient* client,
GError** err)
{
GError* error = NULL;
trace ("Suggesting sync\n");
PUSH_USE_ENGINE (client);
gconf_engine_suggest_sync(client->engine, &error);
POP_USE_ENGINE (client);
handle_error(client, error, err);
}
gboolean
gconf_client_dir_exists(GConfClient* client,
const gchar* dir, GError** err)
{
GError* error = NULL;
gboolean retval;
trace ("Checking whether directory '%s' exists...\n", dir);
PUSH_USE_ENGINE (client);
retval = gconf_engine_dir_exists (client->engine, dir, &error);
POP_USE_ENGINE (client);
handle_error (client, error, err);
if (retval)
trace ("%s exists\n", dir);
else
trace ("%s doesn't exist\n", dir);
return retval;
}
gboolean
gconf_client_key_is_writable (GConfClient* client,
const gchar* key,
GError** err)
{
GError* error = NULL;
GConfEntry *entry = NULL;
gboolean is_writable;
g_return_val_if_fail (key != NULL, FALSE);
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
trace ("Checking whether key '%s' is writable... \n", key);
if (gconf_client_lookup (client, key, &entry))
{
g_assert (entry != NULL);
return gconf_entry_get_is_writable (entry);
}
entry = get (client, key, TRUE, &error);
if (entry == NULL && error != NULL)
handle_error (client, error, err);
else
g_assert (error == NULL);
if (entry == NULL)
is_writable = FALSE;
else
is_writable = gconf_entry_get_is_writable (entry);
if (entry)
gconf_entry_free (entry);
if (is_writable)
trace ("%s is writable\n", key);
else
trace ("%s is not writable\n", key);
return is_writable;
}
static gboolean
check_type(const gchar* key, GConfValue* val, GConfValueType t, GError** err)
{
if (val->type != t)
{
gconf_set_error(err, GCONF_ERROR_TYPE_MISMATCH,
_("Expected `%s' got `%s' for key %s"),
gconf_value_type_to_string(t),
gconf_value_type_to_string(val->type),
key);
return FALSE;
}
else
return TRUE;
}
static GConfEntry*
get (GConfClient *client,
const gchar *key,
gboolean use_default,
GError **error)
{
GConfEntry *entry = NULL;
g_return_val_if_fail (client != NULL, NULL);
g_return_val_if_fail (GCONF_IS_CLIENT(client), NULL);
g_return_val_if_fail (error != NULL, NULL);
g_return_val_if_fail (*error == NULL, NULL);
/* Check our client-side cache */
if (gconf_client_lookup (client, key, &entry))
{
trace ("%s was in the client-side cache\n", key);
g_assert (entry != NULL);
if (gconf_entry_get_is_default (entry) && !use_default)
return NULL;
else
return gconf_entry_copy (entry);
}
g_assert (entry == NULL); /* if it was in the cache we should have returned */
/* Check the GConfEngine */
trace ("Doing remote query for %s\n", key);
PUSH_USE_ENGINE (client);
entry = gconf_engine_get_entry (client->engine, key,
gconf_current_locale(),
TRUE /* always use default here */,
error);
POP_USE_ENGINE (client);
if (*error != NULL)
{
g_return_val_if_fail (entry == NULL, NULL);
return NULL;
}
else
{
g_assert (entry != NULL); /* gconf_engine_get_entry shouldn't return NULL ever */
/* Cache this value, if it's in our directory list. */
if (key_being_monitored (client, key))
{
/* cache a copy of val */
gconf_client_cache (client, FALSE, entry, FALSE);
}
/* We don't own the entry, we're returning this copy belonging
* to the caller
*/
if (gconf_entry_get_is_default (entry) && !use_default)
return NULL;
else
return entry;
}
}
static GConfValue*
gconf_client_get_full (GConfClient* client,
const gchar* key, const gchar* locale,
gboolean use_schema_default,
GError** err)
{
GError* error = NULL;
GConfEntry *entry;
GConfValue *retval;
g_return_val_if_fail (err == NULL || *err == NULL, NULL);
if (locale != NULL)
g_warning ("haven't implemented getting a specific locale in GConfClient");
entry = get (client, key, use_schema_default,
&error);
if (entry == NULL && error != NULL)
handle_error(client, error, err);
else
g_assert (error == NULL);
retval = NULL;
if (entry && gconf_entry_get_value (entry))
retval = gconf_value_copy (gconf_entry_get_value (entry));
if (entry != NULL)
gconf_entry_free (entry);
return retval;
}
GConfEntry*
gconf_client_get_entry (GConfClient* client,
const gchar* key,
const gchar* locale,
gboolean use_schema_default,
GError** err)
{
GError* error = NULL;
GConfEntry *entry;
g_return_val_if_fail(err == NULL || *err == NULL, NULL);
if (locale != NULL)
g_warning("haven't implemented getting a specific locale in GConfClient");
entry = get (client, key, use_schema_default,
&error);
if (entry == NULL && error != NULL)
handle_error (client, error, err);
else
g_assert (error == NULL);
return entry;
}
GConfValue*
gconf_client_get (GConfClient* client,
const gchar* key,
GError** err)
{
g_return_val_if_fail (GCONF_IS_CLIENT (client), NULL);
g_return_val_if_fail (key != NULL, NULL);
return gconf_client_get_full (client, key, NULL, TRUE, err);
}
GConfValue*
gconf_client_get_without_default (GConfClient* client,
const gchar* key,
GError** err)
{
g_return_val_if_fail (GCONF_IS_CLIENT (client), NULL);
g_return_val_if_fail (key != NULL, NULL);
return gconf_client_get_full (client, key, NULL, FALSE, err);
}
GConfValue*
gconf_client_get_default_from_schema (GConfClient* client,
const gchar* key,
GError** err)
{
GError* error = NULL;
GConfEntry *entry = NULL;
GConfValue *val = NULL;
g_return_val_if_fail (err == NULL || *err == NULL, NULL);
g_return_val_if_fail (client != NULL, NULL);
g_return_val_if_fail (GCONF_IS_CLIENT(client), NULL);
g_return_val_if_fail (key != NULL, NULL);
trace ("Getting default for %s from schema\n", key);
/* Check our client-side cache to see if the default is the same as
* the regular value (FIXME put a default_value field in the
* cache and store both, lose the is_default flag)
*/
if (gconf_client_lookup (client, key, &entry))
{
g_assert (entry != NULL);
if (gconf_entry_get_is_default (entry))
{
trace ("Using cached value for schema default\n");
return gconf_entry_get_value (entry) ?
gconf_value_copy (gconf_entry_get_value (entry)) :
NULL;
}
}
/* Check the GConfEngine */
trace ("Asking engine for schema default\n");
PUSH_USE_ENGINE (client);
val = gconf_engine_get_default_from_schema (client->engine, key,
&error);
POP_USE_ENGINE (client);
if (error != NULL)
{
g_assert (val == NULL);
handle_error (client, error, err);
return NULL;
}
else
{
/* FIXME eventually we'll cache the value
* by adding a field to the cache
*/
return val;
}
}
gdouble
gconf_client_get_float (GConfClient* client, const gchar* key,
GError** err)
{
static const gdouble def = 0.0;
GError* error = NULL;
GConfValue *val;
g_return_val_if_fail (err == NULL || *err == NULL, 0.0);
val = gconf_client_get (client, key, &error);
if (val != NULL)
{
gdouble retval = def;
g_assert(error == NULL);
if (check_type (key, val, GCONF_VALUE_FLOAT, &error))
retval = gconf_value_get_float (val);
else
handle_error (client, error, err);
gconf_value_free (val);
return retval;
}
else
{
if (error != NULL)
handle_error (client, error, err);
return def;
}
}
gint
gconf_client_get_int (GConfClient* client, const gchar* key,
GError** err)
{
static const gint def = 0;
GError* error = NULL;
GConfValue* val;
g_return_val_if_fail (err == NULL || *err == NULL, 0);
val = gconf_client_get (client, key, &error);
if (val != NULL)
{
gint retval = def;
g_assert(error == NULL);
if (check_type (key, val, GCONF_VALUE_INT, &error))
retval = gconf_value_get_int(val);
else
handle_error (client, error, err);
gconf_value_free (val);
return retval;
}
else
{
if (error != NULL)
handle_error (client, error, err);
return def;
}
}
gchar*
gconf_client_get_string(GConfClient* client, const gchar* key,
GError** err)
{
GError* error = NULL;
GConfValue* val;
g_return_val_if_fail (err == NULL || *err == NULL, NULL);
val = gconf_client_get (client, key, &error);
if (val != NULL)
{
gchar* retval = NULL;
g_assert(error == NULL);
if (check_type (key, val, GCONF_VALUE_STRING, &error))
retval = gconf_value_steal_string (val);
else
handle_error (client, error, err);
gconf_value_free (val);
return retval;
}
else
{
if (error != NULL)
handle_error (client, error, err);
return NULL;
}
}
gboolean
gconf_client_get_bool (GConfClient* client, const gchar* key,
GError** err)
{
static const gboolean def = FALSE;
GError* error = NULL;
GConfValue* val;
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
val = gconf_client_get (client, key, &error);
if (val != NULL)
{
gboolean retval = def;
g_assert (error == NULL);
if (check_type (key, val, GCONF_VALUE_BOOL, &error))
retval = gconf_value_get_bool (val);
else
handle_error (client, error, err);
gconf_value_free (val);
return retval;
}
else
{
if (error != NULL)
handle_error (client, error, err);
return def;
}
}
GConfSchema*
gconf_client_get_schema (GConfClient* client,
const gchar* key, GError** err)
{
GError* error = NULL;
GConfValue* val;
g_return_val_if_fail (err == NULL || *err == NULL, NULL);
val = gconf_client_get (client, key, &error);
if (val != NULL)
{
GConfSchema* retval = NULL;
g_assert(error == NULL);
if (check_type (key, val, GCONF_VALUE_SCHEMA, &error))
retval = gconf_value_steal_schema (val);
else
handle_error (client, error, err);
gconf_value_free (val);
return retval;
}
else
{
if (error != NULL)
handle_error (client, error, err);
return NULL;
}
}
GSList*
gconf_client_get_list (GConfClient* client, const gchar* key,
GConfValueType list_type, GError** err)
{
GError* error = NULL;
GConfValue* val;
g_return_val_if_fail (err == NULL || *err == NULL, NULL);
val = gconf_client_get (client, key, &error);
if (val != NULL)
{
GSList* retval;
g_assert (error == NULL);
/* This function checks the type and destroys "val" */
retval = gconf_value_list_to_primitive_list_destructive (val, list_type, &error);
if (error != NULL)
{
g_assert (retval == NULL);
handle_error (client, error, err);
return NULL;
}
else
return retval;
}
else
{
if (error != NULL)
handle_error (client, error, err);
return NULL;
}
}
gboolean
gconf_client_get_pair (GConfClient* client, const gchar* key,
GConfValueType car_type, GConfValueType cdr_type,
gpointer car_retloc, gpointer cdr_retloc,
GError** err)
{
GError* error = NULL;
GConfValue* val;
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
val = gconf_client_get (client, key, &error);
if (val != NULL)
{
g_assert(error == NULL);
/* This function checks the type and destroys "val" */
if (gconf_value_pair_to_primitive_pair_destructive (val, car_type, cdr_type,
car_retloc, cdr_retloc,
&error))
{
g_assert (error == NULL);
return TRUE;
}
else
{
g_assert (error != NULL);
handle_error (client, error, err);
return FALSE;
}
}
else
{
if (error != NULL)
{
handle_error (client, error, err);
return FALSE;
}
else
return TRUE;
}
}
/*
* For the set functions, we just set normally, and wait for the
* notification to come back from the server before we update
* our cache. This may be the wrong thing; maybe we should
* update immediately?
* Problem with delayed update: user calls set() then get(),
* results in weirdness
* Problem with with regular update: get() before the notify
* is out of sync with the listening parts of the application
*
*/
gboolean
gconf_client_set_float (GConfClient* client, const gchar* key,
gdouble val, GError** err)
{
GError* error = NULL;
gboolean result;
g_return_val_if_fail(client != NULL, FALSE);
g_return_val_if_fail(GCONF_IS_CLIENT(client), FALSE);
g_return_val_if_fail(key != NULL, FALSE);
trace ("Setting float %s\n", key);
PUSH_USE_ENGINE (client);
result = gconf_engine_set_float (client->engine, key, val, &error);
POP_USE_ENGINE (client);
if (result)
return TRUE;
else
{
handle_error(client, error, err);
return FALSE;
}
}
gboolean
gconf_client_set_int (GConfClient* client, const gchar* key,
gint val, GError** err)
{
GError* error = NULL;
gboolean result;
g_return_val_if_fail(client != NULL, FALSE);
g_return_val_if_fail(GCONF_IS_CLIENT(client), FALSE);
g_return_val_if_fail(key != NULL, FALSE);
trace ("Setting int %s\n", key);
PUSH_USE_ENGINE (client);
result = gconf_engine_set_int (client->engine, key, val, &error);
POP_USE_ENGINE (client);
if (result)
return TRUE;
else
{
handle_error(client, error, err);
return FALSE;
}
}
gboolean
gconf_client_set_string (GConfClient* client, const gchar* key,
const gchar* val, GError** err)
{
GError* error = NULL;
gboolean result;
g_return_val_if_fail(client != NULL, FALSE);
g_return_val_if_fail(GCONF_IS_CLIENT(client), FALSE);
g_return_val_if_fail(key != NULL, FALSE);
g_return_val_if_fail(val != NULL, FALSE);
trace ("Setting string %s\n", key);
PUSH_USE_ENGINE (client);
result = gconf_engine_set_string(client->engine, key, val, &error);
POP_USE_ENGINE (client);
if (result)
return TRUE;
else
{
handle_error(client, error, err);
return FALSE;
}
}
gboolean
gconf_client_set_bool (GConfClient* client, const gchar* key,
gboolean val, GError** err)
{
GError* error = NULL;
gboolean result;
g_return_val_if_fail(client != NULL, FALSE);
g_return_val_if_fail(GCONF_IS_CLIENT(client), FALSE);
g_return_val_if_fail(key != NULL, FALSE);
PUSH_USE_ENGINE (client);
result = gconf_engine_set_bool (client->engine, key, val, &error);
POP_USE_ENGINE (client);
if (result)
return TRUE;
else
{
handle_error(client, error, err);
return FALSE;
}
}
gboolean
gconf_client_set_schema (GConfClient* client, const gchar* key,
const GConfSchema* val, GError** err)
{
GError* error = NULL;
gboolean result;
g_return_val_if_fail(client != NULL, FALSE);
g_return_val_if_fail(GCONF_IS_CLIENT(client), FALSE);
g_return_val_if_fail(key != NULL, FALSE);
g_return_val_if_fail(val != NULL, FALSE);
trace ("Setting schema %s\n", key);
PUSH_USE_ENGINE (client);
result = gconf_engine_set_schema(client->engine, key, val, &error);
POP_USE_ENGINE (client);
if (result)
return TRUE;
else
{
handle_error(client, error, err);
return FALSE;
}
}
gboolean
gconf_client_set_list (GConfClient* client, const gchar* key,
GConfValueType list_type,
GSList* list,
GError** err)
{
GError* error = NULL;
gboolean result;
g_return_val_if_fail(client != NULL, FALSE);
g_return_val_if_fail(GCONF_IS_CLIENT(client), FALSE);
g_return_val_if_fail(key != NULL, FALSE);
trace ("Setting list %s\n", key);
PUSH_USE_ENGINE (client);
result = gconf_engine_set_list(client->engine, key, list_type, list, &error);
POP_USE_ENGINE (client);
if (result)
return TRUE;
else
{
handle_error(client, error, err);
return FALSE;
}
}
gboolean
gconf_client_set_pair (GConfClient* client, const gchar* key,
GConfValueType car_type, GConfValueType cdr_type,
gconstpointer address_of_car,
gconstpointer address_of_cdr,
GError** err)
{
GError* error = NULL;
gboolean result;
g_return_val_if_fail(client != NULL, FALSE);
g_return_val_if_fail(GCONF_IS_CLIENT(client), FALSE);
g_return_val_if_fail(key != NULL, FALSE);
trace ("Setting pair %s\n", key);
PUSH_USE_ENGINE (client);
result = gconf_engine_set_pair (client->engine, key, car_type, cdr_type,
address_of_car, address_of_cdr, &error);
POP_USE_ENGINE (client);
if (result)
return TRUE;
else
{
handle_error(client, error, err);
return FALSE;
}
}
/*
* Functions to emit signals
*/
void
gconf_client_error (GConfClient* client, GError* error)
{
g_return_if_fail(client != NULL);
g_return_if_fail(GCONF_IS_CLIENT(client));
g_signal_emit (G_OBJECT(client), client_signals[ERROR], 0,
error);
}
void
gconf_client_unreturned_error (GConfClient* client, GError* error)
{
g_return_if_fail(client != NULL);
g_return_if_fail(GCONF_IS_CLIENT(client));
g_signal_emit (G_OBJECT(client), client_signals[UNRETURNED_ERROR], 0,
error);
}
void
gconf_client_value_changed (GConfClient* client,
const gchar* key,
GConfValue* value)
{
g_return_if_fail(client != NULL);
g_return_if_fail(GCONF_IS_CLIENT(client));
g_return_if_fail(key != NULL);
g_signal_emit(G_OBJECT(client), client_signals[VALUE_CHANGED], 0,
key, value);
}
/*
* Internal utility
*/
static gboolean
gconf_client_cache (GConfClient *client,
gboolean take_ownership,
GConfEntry *new_entry,
gboolean preserve_schema_name)
{
gpointer oldkey = NULL, oldval = NULL;
if (g_hash_table_lookup_extended (client->cache_hash, new_entry->key, &oldkey, &oldval))
{
/* Already have a value, update it */
GConfEntry *entry = oldval;
gboolean changed;
g_assert (entry != NULL);
changed = ! gconf_entry_equal (entry, new_entry);
if (changed)
{
trace ("Updating value of '%s' in the cache\n",
new_entry->key);
if (!take_ownership)
new_entry = gconf_entry_copy (new_entry);
if (preserve_schema_name)
gconf_entry_set_schema_name (new_entry,
gconf_entry_get_schema_name (entry));
g_hash_table_replace (client->cache_hash,
new_entry->key,
new_entry);
/* oldkey is inside entry */
gconf_entry_free (entry);
}
else
{
trace ("Value of '%s' hasn't actually changed, would have updated in cache if it had\n",
new_entry->key);
if (take_ownership)
gconf_entry_free (new_entry);
}
return changed;
}
else
{
/* Create a new entry */
if (!take_ownership)
new_entry = gconf_entry_copy (new_entry);
g_hash_table_insert (client->cache_hash, new_entry->key, new_entry);
trace ("Added value of '%s' to the cache\n",
new_entry->key);
return TRUE; /* changed */
}
}
static gboolean
gconf_client_lookup (GConfClient *client,
const char *key,
GConfEntry **entryp)
{
GConfEntry *entry;
g_return_val_if_fail (entryp != NULL, FALSE);
g_return_val_if_fail (*entryp == NULL, FALSE);
entry = g_hash_table_lookup (client->cache_hash, key);
*entryp = entry;
return entry != NULL;
}
/*
* Dir
*/
static Dir*
dir_new(const gchar* name, guint notify_id)
{
Dir* d;
d = g_new(Dir, 1);
d->name = g_strdup(name);
d->notify_id = notify_id;
d->add_count = 0;
return d;
}
static void
dir_destroy(Dir* d)
{
g_return_if_fail(d != NULL);
g_return_if_fail(d->notify_id == 0);
g_free(d->name);
g_free(d);
}
/*
* Listener
*/
static Listener*
listener_new(GConfClientNotifyFunc func,
GFreeFunc destroy_notify,
gpointer data)
{
Listener* l;
l = g_new(Listener, 1);
l->func = func;
l->data = data;
l->destroy_notify = destroy_notify;
return l;
}
static void
listener_destroy(Listener* l)
{
g_return_if_fail(l != NULL);
if (l->destroy_notify)
(* l->destroy_notify) (l->data);
g_free(l);
}
/*
* Change sets
*/
struct CommitData {
GConfClient* client;
GError* error;
GSList* remove_list;
gboolean remove_committed;
};
static void
commit_foreach (GConfChangeSet* cs,
const gchar* key,
GConfValue* value,
gpointer user_data)
{
struct CommitData* cd = user_data;
g_assert(cd != NULL);
if (cd->error != NULL)
return;
if (value)
gconf_client_set (cd->client, key, value, &cd->error);
else
gconf_client_unset (cd->client, key, &cd->error);
if (cd->error == NULL && cd->remove_committed)
{
/* Bad bad bad; we keep the key reference, knowing that it's
valid until we modify the change set, to avoid string copies. */
cd->remove_list = g_slist_prepend(cd->remove_list, (gchar*)key);
}
}
gboolean
gconf_client_commit_change_set (GConfClient* client,
GConfChangeSet* cs,
gboolean remove_committed,
GError** err)
{
struct CommitData cd;
GSList* tmp;
g_return_val_if_fail(client != NULL, FALSE);
g_return_val_if_fail(GCONF_IS_CLIENT(client), FALSE);
g_return_val_if_fail(cs != NULL, FALSE);
g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
cd.client = client;
cd.error = NULL;
cd.remove_list = NULL;
cd.remove_committed = remove_committed;
/* Because the commit could have lots of side
effects, this makes it safer */
gconf_change_set_ref(cs);
g_object_ref(G_OBJECT(client));
gconf_change_set_foreach(cs, commit_foreach, &cd);
tmp = cd.remove_list;
while (tmp != NULL)
{
const gchar* key = tmp->data;
gconf_change_set_remove(cs, key);
/* key is now invalid due to our little evil trick */
tmp = g_slist_next(tmp);
}
g_slist_free(cd.remove_list);
gconf_change_set_unref(cs);
g_object_unref(G_OBJECT(client));
if (cd.error != NULL)
{
if (err != NULL)
*err = cd.error;
else
g_error_free(cd.error);
return FALSE;
}
else
{
g_assert((!remove_committed) ||
(gconf_change_set_size(cs) == 0));
return TRUE;
}
}
struct RevertData {
GConfClient* client;
GError* error;
GConfChangeSet* revert_set;
};
static void
revert_foreach (GConfChangeSet* cs,
const gchar* key,
GConfValue* value,
gpointer user_data)
{
struct RevertData* rd = user_data;
GConfValue* old_value;
GError* error = NULL;
g_assert(rd != NULL);
if (rd->error != NULL)
return;
old_value = gconf_client_get_without_default(rd->client, key, &error);
if (error != NULL)
{
/* FIXME */
g_warning("error creating revert set: %s", error->message);
g_error_free(error);
error = NULL;
}
if (old_value == NULL &&
value == NULL)
return; /* this commit will have no effect. */
if (old_value == NULL)
gconf_change_set_unset(rd->revert_set, key);
else
gconf_change_set_set_nocopy(rd->revert_set, key, old_value);
}
GConfChangeSet*
gconf_client_reverse_change_set (GConfClient* client,
GConfChangeSet* cs,
GError** err)
{
struct RevertData rd;
rd.error = NULL;
rd.client = client;
rd.revert_set = gconf_change_set_new();
/* we're emitting signals and such, avoid
nasty side effects with these.
*/
g_object_ref(G_OBJECT(rd.client));
gconf_change_set_ref(cs);
gconf_change_set_foreach(cs, revert_foreach, &rd);
if (rd.error != NULL)
{
if (err != NULL)
*err = rd.error;
else
g_error_free(rd.error);
}
g_object_unref(G_OBJECT(rd.client));
gconf_change_set_unref(cs);
return rd.revert_set;
}
GConfChangeSet*
gconf_client_change_set_from_currentv (GConfClient* client,
const gchar** keys,
GError** err)
{
GConfValue* old_value;
GConfChangeSet* new_set;
const gchar** keyp;
g_return_val_if_fail(err == NULL || *err == NULL, NULL);
new_set = gconf_change_set_new();
keyp = keys;
while (*keyp != NULL)
{
GError* error = NULL;
const gchar* key = *keyp;
old_value = gconf_client_get_without_default(client, key, &error);
if (error != NULL)
{
/* FIXME */
g_warning("error creating change set from current keys: %s", error->message);
g_error_free(error);
error = NULL;
}
if (old_value == NULL)
gconf_change_set_unset(new_set, key);
else
gconf_change_set_set_nocopy(new_set, key, old_value);
++keyp;
}
return new_set;
}
GConfChangeSet*
gconf_client_change_set_from_current (GConfClient* client,
GError** err,
const gchar* first_key,
...)
{
GSList* keys = NULL;
va_list args;
const gchar* arg;
const gchar** vec;
GConfChangeSet* retval;
GSList* tmp;
guint i;
g_return_val_if_fail(err == NULL || *err == NULL, NULL);
va_start (args, first_key);
arg = first_key;
while (arg != NULL)
{
keys = g_slist_prepend(keys, (/*non-const*/gchar*)arg);
arg = va_arg (args, const gchar*);
}
va_end (args);
vec = g_new0(const gchar*, g_slist_length(keys) + 1);
i = 0;
tmp = keys;
while (tmp != NULL)
{
vec[i] = tmp->data;
++i;
tmp = g_slist_next(tmp);
}
g_slist_free(keys);
retval = gconf_client_change_set_from_currentv(client, vec, err);
g_free(vec);
return retval;
}
static GHashTable * clients = NULL;
static void
register_client (GConfClient *client)
{
if (clients == NULL)
clients = g_hash_table_new (NULL, NULL);
g_hash_table_insert (clients, client->engine, client);
}
static GConfClient *
lookup_client (GConfEngine *engine)
{
if (clients == NULL)
return NULL;
else
return g_hash_table_lookup (clients, engine);
}
static void
unregister_client (GConfClient *client)
{
g_return_if_fail (clients != NULL);
g_hash_table_remove (clients, client->engine);
}
/*
* Notification
*/
static gboolean
notify_idle_callback (gpointer data)
{
GConfClient *client = data;
client->notify_handler = 0; /* avoid g_source_remove */
gconf_client_flush_notifies (client);
/* remove handler */
return FALSE;
}
static void
gconf_client_queue_notify (GConfClient *client,
const char *key)
{
trace ("Queing notify on %s, %d pending already\n", key,
client->pending_notify_count);
if (client->notify_handler == 0)
client->notify_handler = g_idle_add (notify_idle_callback, client);
client->notify_list = g_slist_prepend (client->notify_list, g_strdup (key));
client->pending_notify_count += 1;
}
struct ClientAndEntry {
GConfClient* client;
GConfEntry* entry;
};
static void
notify_listeners_callback(GConfListeners* listeners,
const gchar* key,
guint cnxn_id,
gpointer listener_data,
gpointer user_data)
{
Listener* l = listener_data;
struct ClientAndEntry* cae = user_data;
g_return_if_fail (cae != NULL);
g_return_if_fail (cae->client != NULL);
g_return_if_fail (GCONF_IS_CLIENT (cae->client));
g_return_if_fail (l != NULL);
g_return_if_fail (l->func != NULL);
(*l->func) (cae->client, cnxn_id, cae->entry, l->data);
}
static void
notify_one_entry (GConfClient *client,
GConfEntry *entry)
{
g_object_ref (G_OBJECT (client));
gconf_entry_ref (entry);
/* Emit the value_changed signal before notifying specific listeners;
* I'm not sure there's a reason this matters though
*/
gconf_client_value_changed (client,
entry->key,
gconf_entry_get_value (entry));
/* Now notify our listeners, if any */
if (client->listeners != NULL)
{
struct ClientAndEntry cae;
cae.client = client;
cae.entry = entry;
gconf_listeners_notify (client->listeners,
entry->key,
notify_listeners_callback,
&cae);
}
gconf_entry_unref (entry);
g_object_unref (G_OBJECT (client));
}
static void
gconf_client_flush_notifies (GConfClient *client)
{
GSList *tmp;
GSList *to_notify;
GConfEntry *last_entry;
trace ("Flushing notify queue\n");
/* Adopt notify list and clear it, to avoid reentrancy concerns.
* Sort it to compress duplicates, and keep people from relying on
* the notify order.
*/
to_notify = g_slist_sort (client->notify_list, (GCompareFunc) strcmp);
client->notify_list = NULL;
client->pending_notify_count = 0;
gconf_client_unqueue_notifies (client);
last_entry = NULL;
tmp = to_notify;
while (tmp != NULL)
{
GConfEntry *entry = NULL;
if (gconf_client_lookup (client, tmp->data, &entry))
{
if (entry != last_entry)
{
trace ("Doing notification for %s\n", entry->key);
notify_one_entry (client, entry);
last_entry = entry;
}
else
{
trace ("Ignoring duplicate notify for %s\n", entry->key);
}
}
else
{
trace ("Key %s was in notify queue but not in cache; we must have stopped monitoring it; not notifying\n",
tmp->data);
}
tmp = tmp->next;
}
g_slist_foreach (to_notify, (GFunc) g_free, NULL);
g_slist_free (to_notify);
}
static void
gconf_client_unqueue_notifies (GConfClient *client)
{
if (client->notify_handler != 0)
{
g_source_remove (client->notify_handler);
client->notify_handler = 0;
}
if (client->notify_list != NULL)
{
g_slist_foreach (client->notify_list, (GFunc) g_free, NULL);
g_slist_free (client->notify_list);
client->notify_list = NULL;
client->pending_notify_count = 0;
}
}
syntax highlighted by Code2HTML, v. 0.9.1