Author: kelnos Date: 2009-07-29 21:42:58 +0000 (Wed, 29 Jul 2009) New Revision: 30417
Added: xfconf/trunk/xfconf/xfconf-cache.c xfconf/trunk/xfconf/xfconf-cache.h Modified: xfconf/trunk/NEWS xfconf/trunk/xfconf/Makefile.am xfconf/trunk/xfconf/xfconf-channel.c xfconf/trunk/xfconf/xfconf-private.h Log: add transparent property prefetching and caching XfconfCache is a transparent cache that sits behind XfconfChannel, and, so far, involves no new API. this moves most of the dbus code from XfconfChannel to XfconfCache. on creation, XfconfChannel gets an XfconfCache object for that channel (the cache objects are per-channel-name singletons, regardless of property_base or whether or not the XfconfChannel is a singleton). on creation, XfconfChannel prefetches all properties in the channel. this might turn out to be a bad idea for large channels. property setting does not block if the setting is already in the cache. i may change this in the future to not block at all ever, but that might make reporting certain kinds of errors impossible. property resets are completely non-blocking. lookups are non-blocking if the setting is already in the cache. the cache automatically updates itself if another application modifies a property. the cache has hooks to set the maximum age of entries (in seconds) and the max number of entries to store in the cache. currently these two are unimplemented, and i'm not sure if there's value to exposing these in the public API. Modified: xfconf/trunk/NEWS =================================================================== --- xfconf/trunk/NEWS 2009-07-29 19:11:56 UTC (rev 30416) +++ xfconf/trunk/NEWS 2009-07-29 21:42:58 UTC (rev 30417) @@ -1,3 +1,11 @@ +Xfce 4.7.0 +========== + + * Add prefetching and caching support to XfconfChannel. Should speed + things up a bit on app startup, and avoid blocking waiting on DBus + in other cases. + + Xfce 4.6.2 ========== Modified: xfconf/trunk/xfconf/Makefile.am =================================================================== --- xfconf/trunk/xfconf/Makefile.am 2009-07-29 19:11:56 UTC (rev 30416) +++ xfconf/trunk/xfconf/Makefile.am 2009-07-29 21:42:58 UTC (rev 30417) @@ -12,6 +12,8 @@ libxfconf_0_la_SOURCES = \ $(libxfconfinclude_HEADERS) \ xfconf-binding.c \ + xfconf-cache.c \ + xfconc-cache.h \ xfconf-channel.c \ xfconf-dbus-bindings.h \ xfconf-private.h \ @@ -37,6 +39,7 @@ libxfconf_0_la_LIBADD = \ $(top_builddir)/common/libxfconf-common.la \ + $(top_builddir)/common/libxfconf-gvaluefuncs.la \ $(GLIB_LIBS) \ $(DBUS_LIBS) \ $(DBUS_GLIB_LIBS) Added: xfconf/trunk/xfconf/xfconf-cache.c =================================================================== --- xfconf/trunk/xfconf/xfconf-cache.c (rev 0) +++ xfconf/trunk/xfconf/xfconf-cache.c 2009-07-29 21:42:58 UTC (rev 30417) @@ -0,0 +1,844 @@ +/* + * xfconf + * + * Copyright (c) 2009 Brian Tarricone <br...@tarricone.org> + * + * 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; version 2 of the License ONLY. + * + * 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 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "xfconf-cache.h" +#include "xfconf-channel.h" +#include "xfconf-errors.h" +#include "xfconf-dbus-bindings.h" +#include "xfconf-gvaluefuncs.h" +#include "xfconf-private.h" +#include "xfconf-marshal.h" +//#include "xfconf-types.h" +//#include "xfconf-common-private.h" +//#include "xfconf.h" +//#include "xfconf-alias.h" + +#define DEFAULT_MAX_ENTRIES -1 /* no limit */ +#define DEFAULT_MAX_AGE (60*60) /* 1 hour */ + +#define ALIGN_VAL(val, align) ( ((val) + ((align) -1)) & ~((align) - 1) ) + +static void xfconf_cache_mutex_lock(GStaticMutex *mtx) __attribute__((noinline)); +static void xfconf_cache_mutex_lock(GStaticMutex *mtx) { g_static_mutex_lock(mtx); } +static void xfconf_cache_mutex_unlock(GStaticMutex *mtx) __attribute__((noinline)); +static void xfconf_cache_mutex_unlock(GStaticMutex *mtx) { g_static_mutex_unlock(mtx); } + + +/**************** XfconfCacheItem ****************/ + + +typedef struct +{ + GTimeVal last_used; + GValue value; +} XfconfCacheItem; + +static XfconfCacheItem * +xfconf_cache_item_new(const GValue *value) +{ + XfconfCacheItem *item; + + g_return_val_if_fail(value, NULL); + + item = g_slice_new0(XfconfCacheItem); + g_get_current_time(&item->last_used); + g_value_init(&item->value, G_VALUE_TYPE(value)); + g_value_copy(value, &item->value); + + return item; +} + +static gboolean +xfconf_cache_item_update(XfconfCacheItem *item, + const GValue *value) +{ + if(value && _xfconf_gvalue_is_equal(&item->value, value)) + return FALSE; + + g_get_current_time(&item->last_used); + + if(value) { + g_value_unset(&item->value); + g_value_init(&item->value, G_VALUE_TYPE(value)); + g_value_copy(value, &item->value); + + return TRUE; + } + + return FALSE; +} + +static void +xfconf_cache_item_free(XfconfCacheItem *item) +{ + g_return_if_fail(item); + + g_value_unset(&item->value); + + g_slice_free(XfconfCacheItem, item); +} + + +/******************* XfconfCacheOldItem *******************/ + + +typedef struct +{ + gchar *property; + DBusGProxyCall *call; + XfconfCacheItem *item; +} XfconfCacheOldItem; + +static XfconfCacheOldItem * +xfconf_cache_old_item_new(const gchar *property) +{ + XfconfCacheOldItem *old_item; + + g_return_val_if_fail(property, NULL); + + old_item = g_slice_new0(XfconfCacheOldItem); + old_item->property = g_strdup(property); + + return old_item; +} + +static void +xfconf_cache_old_item_free(XfconfCacheOldItem *old_item) +{ + g_return_if_fail(old_item); + + if(old_item->call) { + /* FIXME: maybe should force them to complete */ + DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); + dbus_g_proxy_cancel_call(proxy, old_item->call); + } + + g_free(old_item->property); + + if(old_item->item) + xfconf_cache_item_free(old_item->item); + + g_slice_free(XfconfCacheOldItem, old_item); +} + + +/************************* XfconfCache ********************/ + + +/** + * XfconfCache: + * + * An opaque structure that holds state about a cache. + **/ +struct _XfconfCache +{ + GObject parent; + + gchar *channel_name; + + gint max_entries; + gint max_age; + + GTree *properties; + + GHashTable *pending_calls; + GHashTable *old_properties; + + GStaticMutex cache_lock; +}; + +typedef struct _XfconfCacheClass +{ + GObjectClass parent; + + void (*property_changed)(XfconfCache *cache, + const gchar *channel_name, + const gchar *property, + const GValue *value); +} XfconfCacheClass; + +enum +{ + SIG_PROPERTY_CHANGED = 0, + N_SIGS, +}; + +enum +{ + PROP0 = 0, + PROP_CHANNEL_NAME, + PROP_MAX_ENTRIES, + PROP_MAX_AGE, +}; + +static void xfconf_cache_set_g_property(GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void xfconf_cache_get_g_property(GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void xfconf_cache_finalize(GObject *obj); + +static void xfconf_cache_property_changed(DBusGProxy *proxy, + const gchar *cache_name, + const gchar *property, + const GValue *value, + gpointer user_data); +static void xfconf_cache_property_removed(DBusGProxy *proxy, + const gchar *cache_name, + const gchar *property, + gpointer user_data); + + +static guint signals[N_SIGS] = { 0, }; +G_LOCK_DEFINE_STATIC(singletons); +static GHashTable *singletons = NULL; + + +G_DEFINE_TYPE(XfconfCache, xfconf_cache, G_TYPE_OBJECT) + + +static void +xfconf_cache_class_init(XfconfCacheClass *klass) +{ + GObjectClass *object_class = (GObjectClass *)klass; + + object_class->set_property = xfconf_cache_set_g_property; + object_class->get_property = xfconf_cache_get_g_property; + object_class->finalize = xfconf_cache_finalize; + + signals[SIG_PROPERTY_CHANGED] = g_signal_new("property-changed", + XFCONF_TYPE_CACHE, + G_SIGNAL_RUN_LAST + | G_SIGNAL_DETAILED, + G_STRUCT_OFFSET(XfconfCacheClass, + property_changed), + NULL, + NULL, + _xfconf_marshal_VOID__STRING_STRING_BOXED, + G_TYPE_NONE, + 3, G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_VALUE); + + g_object_class_install_property(object_class, PROP_CHANNEL_NAME, + g_param_spec_string("channel-name", + "Channel Name", + "The name of the channel managed by the cache", + NULL, + G_PARAM_READWRITE + | G_PARAM_CONSTRUCT_ONLY + | G_PARAM_STATIC_NAME + | G_PARAM_STATIC_NICK + | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property(object_class, PROP_MAX_ENTRIES, + g_param_spec_int("max-entries", + "Maximum entries", + "Maximum number of cache entries to hold at once", + -1, G_MAXINT, + DEFAULT_MAX_ENTRIES, + G_PARAM_READWRITE + | G_PARAM_CONSTRUCT + | G_PARAM_STATIC_NAME + | G_PARAM_STATIC_NICK + | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property(object_class, PROP_MAX_AGE, + g_param_spec_int("max-age", + "Maximum age", + "Maximum age (in seconds) before an entry gets evicted from the cache", + 0, G_MAXINT, + DEFAULT_MAX_AGE, + G_PARAM_READWRITE + | G_PARAM_CONSTRUCT + | G_PARAM_STATIC_NAME + | G_PARAM_STATIC_NICK + | G_PARAM_STATIC_BLURB)); +} + +static void +xfconf_cache_init(XfconfCache *cache) +{ + DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); + + dbus_g_proxy_connect_signal(proxy, "PropertyChanged", + G_CALLBACK(xfconf_cache_property_changed), + cache, NULL); + dbus_g_proxy_connect_signal(proxy, "PropertyRemoved", + G_CALLBACK(xfconf_cache_property_removed), + cache, NULL); + + cache->properties = g_tree_new_full((GCompareDataFunc)strcmp, NULL, + (GDestroyNotify)g_free, + (GDestroyNotify)xfconf_cache_item_free); + + cache->pending_calls = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, + (GDestroyNotify)xfconf_cache_old_item_free); + cache->old_properties = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, NULL); + + g_static_mutex_init(&cache->cache_lock); +} + +static void +xfconf_cache_set_g_property(GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + XfconfCache *cache = XFCONF_CACHE(object); + + switch(property_id) { + case PROP_CHANNEL_NAME: + g_free(cache->channel_name); + cache->channel_name = g_value_dup_string(value); + break; + + case PROP_MAX_ENTRIES: + xfconf_cache_set_max_entries(cache, g_value_get_int(value)); + break; + + case PROP_MAX_AGE: + xfconf_cache_set_max_age(cache, g_value_get_int(value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void +xfconf_cache_get_g_property(GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + XfconfCache *cache = XFCONF_CACHE(object); + + switch(property_id) { + case PROP_CHANNEL_NAME: + g_value_set_string(value, cache->channel_name); + break; + + case PROP_MAX_ENTRIES: + g_value_set_int(value, cache->max_entries); + break; + + case PROP_MAX_AGE: + g_value_set_int(value, cache->max_age); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void +xfconf_cache_finalize(GObject *obj) +{ + XfconfCache *cache = XFCONF_CACHE(obj); + DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); + + dbus_g_proxy_disconnect_signal(proxy, "PropertyChanged", + G_CALLBACK(xfconf_cache_property_changed), + cache); + + dbus_g_proxy_disconnect_signal(proxy, "PropertyRemoved", + G_CALLBACK(xfconf_cache_property_removed), + cache); + + g_free(cache->channel_name); + + g_tree_destroy(cache->properties); + g_hash_table_destroy(cache->pending_calls); + g_hash_table_destroy(cache->old_properties); + + g_static_mutex_free(&cache->cache_lock); + + G_LOCK(singletons); + if(singletons) + g_hash_table_remove(singletons, cache); + G_UNLOCK(singletons); + + G_OBJECT_CLASS(xfconf_cache_parent_class)->finalize(obj); +} + + + +static void +xfconf_cache_property_changed(DBusGProxy *proxy, + const gchar *channel_name, + const gchar *property, + const GValue *value, + gpointer user_data) +{ + XfconfCache *cache = XFCONF_CACHE(user_data); + XfconfCacheItem *item; + gboolean changed = TRUE; + + if(strcmp(channel_name, cache->channel_name)) + return; + + item = g_tree_lookup(cache->properties, property); + if(item) + changed = xfconf_cache_item_update(item, value); + else { + item = xfconf_cache_item_new(value); + g_tree_insert(cache->properties, g_strdup(property), item); + } + + if(changed) { + g_signal_emit(G_OBJECT(cache), signals[SIG_PROPERTY_CHANGED], + g_quark_from_string(property), property, value); + } +} + +static void +xfconf_cache_property_removed(DBusGProxy *proxy, + const gchar *channel_name, + const gchar *property, + gpointer user_data) +{ + XfconfCache *cache = XFCONF_CACHE(user_data); + GValue value = { 0, }; + + if(strcmp(channel_name, cache->channel_name)) + return; + + g_tree_remove(cache->properties, property); + + g_signal_emit(G_OBJECT(cache), signals[SIG_PROPERTY_CHANGED], + g_quark_from_string(property), property, &value); +} + + + +static void +xfconf_cache_set_property_reply_handler(DBusGProxy *proxy, + DBusGProxyCall *call, + gpointer user_data) +{ + XfconfCache *cache = user_data; + XfconfCacheOldItem *old_item = NULL; + XfconfCacheItem *item; + GError *error = NULL; + + xfconf_cache_mutex_lock(&cache->cache_lock); + + old_item = g_hash_table_lookup(cache->pending_calls, call); + if(G_UNLIKELY(!old_item)) { + g_critical("Couldn't find old cache item based on pending call (libxfconf bug?)"); + goto out; + } + + item = g_tree_lookup(cache->properties, old_item->property); + if(G_UNLIKELY(!item)) { + g_critical("Couldn't find current cache item based on pending call (libxfconf bug?)"); + goto out; + } + + /* NULL this out so we don't try to cancel it in the remove function */ + old_item->call = NULL; + if(!dbus_g_proxy_end_call(proxy, call, &error, G_TYPE_INVALID)) { + /* failed to set the value. reset it to the old value and send + * a prop changed signal to the channel */ + GValue empty_val = { 0, }; + + g_warning("Failed to set property \"%s::%s\": %s", + cache->channel_name, old_item->property, error->message); + g_error_free(error); + + if(old_item->item) + xfconf_cache_item_update(item, &old_item->item->value); + else { + g_tree_remove(cache->properties, item); + item = NULL; + } + + /* we need to drop the lock when running the signal handlers */ + xfconf_cache_mutex_unlock(&cache->cache_lock); + g_signal_emit(G_OBJECT(cache), signals[SIG_PROPERTY_CHANGED], + g_quark_from_string(old_item->property), + cache->channel_name, old_item->property, + item ? &item->value : &empty_val); + xfconf_cache_mutex_lock(&cache->cache_lock); + } + +out: + if(old_item) { + g_hash_table_remove(cache->old_properties, old_item->property); + g_hash_table_remove(cache->pending_calls, old_item->call); + } + + xfconf_cache_mutex_unlock(&cache->cache_lock); +} + + + +static void +xfconf_cache_reset_property_reply_handler(DBusGProxy *proxy, + DBusGProxyCall *call, + gpointer user_data) +{ + XfconfCache *cache = user_data; + XfconfCacheOldItem *old_item; + GError *error = NULL; + + xfconf_cache_mutex_lock(&cache->cache_lock); + + old_item = g_hash_table_lookup(cache->pending_calls, call); + if(G_UNLIKELY(!old_item)) { + g_critical("Couldn't find old cache item based on pending call (libxfconf bug?)"); + goto out; + } + + if(!dbus_g_proxy_end_call(proxy, call, &error, G_TYPE_INVALID)) { + g_warning("Failed to reset property \"%s::%s\": %s", + cache->channel_name, old_item->property, error->message); + g_error_free(error); + } + +out: + if(old_item) + g_hash_table_remove(cache->pending_calls, old_item->call); + + xfconf_cache_mutex_unlock(&cache->cache_lock); +} + +static void +xfconf_cache_destroyed(gpointer data, + GObject *where_the_object_was) +{ + gchar *channel_name = data; + + G_LOCK(singletons); + g_hash_table_remove(singletons, channel_name); + G_UNLOCK(singletons); +} + + + +XfconfCache * +xfconf_cache_get(const gchar *channel_name) +{ + XfconfCache *cache; + + G_LOCK(singletons); + + if(!singletons) { + singletons = g_hash_table_new_full(g_str_hash, g_str_equal, + (GDestroyNotify)g_free, NULL); + } + + cache = g_hash_table_lookup(singletons, channel_name); + if(cache) + g_object_ref(G_OBJECT(cache)); + else { + gchar *tmp = g_strdup(channel_name); + + cache = g_object_new(XFCONF_TYPE_CACHE, + "channel-name", channel_name, + NULL); + g_hash_table_insert(singletons, tmp, cache); + g_object_weak_ref(G_OBJECT(cache), xfconf_cache_destroyed, tmp); + } + + G_UNLOCK(singletons); + + return cache; +} + +static void +xfconf_cache_prefetch_ht(gpointer key, + gpointer value, + gpointer user_data) +{ + gchar *property = key; + GValue *val = value; + XfconfCache *cache = XFCONF_CACHE(user_data); + XfconfCacheItem *item; + + item = g_tree_lookup(cache->properties, property); + if(item) + xfconf_cache_item_update(item, val); + else { + item = xfconf_cache_item_new(val); + g_tree_insert(cache->properties, g_strdup(property), item); + } +} + +gboolean +xfconf_cache_prefetch(XfconfCache *cache, + const gchar *property_base, + GError **error) +{ + gboolean ret = FALSE; + GHashTable *props = NULL; + DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); + GError *tmp_error = NULL; + + xfconf_cache_mutex_lock(&cache->cache_lock); + + if(xfconf_client_get_all_properties(proxy, cache->channel_name, + property_base ? property_base : "/", + &props, &tmp_error)) + { + /* FIXME: perhaps change item API to allow 'stealing' a GValue rather + * than copying all the time */ + g_hash_table_foreach(props, xfconf_cache_prefetch_ht, cache); + /* TODO: honor max entries */ + ret = TRUE; + } else + g_propagate_error(error, tmp_error); + + xfconf_cache_mutex_unlock(&cache->cache_lock); + + return ret; +} + +static gboolean +xfconf_cache_lookup_locked(XfconfCache *cache, + const gchar *property, + GValue *value, + GError **error) +{ + XfconfCacheItem *item = NULL; + + item = g_tree_lookup(cache->properties, property); + if(!item) { + DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); + GValue tmpval = { 0, }; + GError *tmp_error = NULL; + + /* blocking, ugh */ + if(xfconf_client_get_property(proxy, cache->channel_name, + property, &tmpval, &tmp_error)) + { + item = xfconf_cache_item_new(&tmpval); + g_tree_insert(cache->properties, g_strdup(property), item); + g_value_unset(&tmpval); + /* TODO: check tree for evictions */ + } else + g_propagate_error(error, tmp_error); + } + + if(item) { + if(value) { + if(!G_VALUE_TYPE(value)) + g_value_init(value, G_VALUE_TYPE(&item->value)); + + if(G_VALUE_TYPE(value) == G_VALUE_TYPE(&item->value)) + g_value_copy(&item->value, value); + else { + if(!g_value_transform(&item->value, value)) + item = NULL; + } + } + if(item) + xfconf_cache_item_update(item, NULL); + } + + return !!item; +} + +gboolean +xfconf_cache_lookup(XfconfCache *cache, + const gchar *property, + GValue *value, + GError **error) +{ + gboolean ret; + + g_return_val_if_fail(XFCONF_IS_CACHE(cache) && property + && (!error || !*error), FALSE); + + xfconf_cache_mutex_lock(&cache->cache_lock); + ret = xfconf_cache_lookup_locked(cache, property, value, error); + xfconf_cache_mutex_unlock(&cache->cache_lock); + + return ret; +} + +gboolean +xfconf_cache_set(XfconfCache *cache, + const gchar *property, + const GValue *value, + GError **error) +{ + DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); + XfconfCacheItem *item = NULL; + XfconfCacheOldItem *old_item = NULL; + + xfconf_cache_mutex_lock(&cache->cache_lock); + + item = g_tree_lookup(cache->properties, property); + if(!item) { + /* this is really quite the opposite of what we want here, + * but i can't think of a better way yet. */ + GValue tmp_val = { 0, }; + GError *tmp_error = NULL; + + if(!xfconf_cache_lookup_locked(cache, property, &tmp_val, &tmp_error)) { + if(G_UNLIKELY(tmp_error->domain != XFCONF_ERROR + || (tmp_error->code != XFCONF_ERROR_CHANNEL_NOT_FOUND + && tmp_error->code != XFCONF_ERROR_PROPERTY_NOT_FOUND))) + { + /* this is bad... */ + g_propagate_error(error, tmp_error); + g_error_free(tmp_error); + xfconf_cache_mutex_unlock(&cache->cache_lock); + return FALSE; + } + + /* prop just doesn't exist; continue */ + g_error_free(tmp_error); + } else { + g_value_unset(&tmp_val); + item = g_tree_lookup(cache->properties, property); + } + } + + if(item) { + /* if the value isn't changing, there's no reason to continue */ + if(_xfconf_gvalue_is_equal(&item->value, value)) { + xfconf_cache_mutex_unlock(&cache->cache_lock); + return TRUE; + } + } + + old_item = g_hash_table_lookup(cache->old_properties, property); + if(old_item) { + /* if we have an old item, it means that a previous set + * call hasn't returned yet. let's cancel that call and + * throw away the current not-yet-committed value of + * the property. */ + if(old_item->call) { + dbus_g_proxy_cancel_call(proxy, old_item->call); + old_item->call = NULL; + } + } else { + old_item = xfconf_cache_old_item_new(property); + if(item) + old_item->item = xfconf_cache_item_new(&item->value); + g_hash_table_insert(cache->old_properties, old_item->property, old_item); + } + + /* can't use the generated dbus-glib binding here cuz we won't + * get the pending call pointer in the callback */ + old_item->call = dbus_g_proxy_begin_call(proxy, "SetProperty", + xfconf_cache_set_property_reply_handler, + cache, NULL, + G_TYPE_STRING, cache->channel_name, + G_TYPE_STRING, property, + G_TYPE_VALUE, value, + G_TYPE_INVALID); + g_hash_table_insert(cache->pending_calls, old_item->call, old_item); + + if(item) + xfconf_cache_item_update(item, value); + else { + item = xfconf_cache_item_new(value); + g_tree_insert(cache->properties, g_strdup(property), item); + } + + xfconf_cache_mutex_unlock(&cache->cache_lock); + + g_signal_emit(G_OBJECT(cache), signals[SIG_PROPERTY_CHANGED], 0, + cache->channel_name, property, value); + + return TRUE; +} + +gboolean +xfconf_cache_reset(XfconfCache *cache, + const gchar *property_base, + gboolean recursive, + GError **error) +{ + DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); + XfconfCacheOldItem *old_item = NULL; + + xfconf_cache_mutex_lock(&cache->cache_lock); + + /* it's not really feasible here to look up all the old/new values + * here, so we're just gonna rely on the normal signals from the + * daemon to notify us of changes */ + + /* can't use the generated dbus-glib binding here cuz we won't + * get the pending call pointer in the callback */ + old_item = xfconf_cache_old_item_new(property_base); + old_item->call = dbus_g_proxy_begin_call(proxy, "ResetProperty", + xfconf_cache_reset_property_reply_handler, + cache, NULL, + G_TYPE_STRING, cache->channel_name, + G_TYPE_STRING, property_base, + G_TYPE_BOOLEAN, recursive, + G_TYPE_INVALID); + g_hash_table_insert(cache->pending_calls, old_item->call, old_item); + + xfconf_cache_mutex_unlock(&cache->cache_lock); + + return TRUE; +} + +void +xfconf_cache_set_max_entries(XfconfCache *cache, + gint max_entries) +{ + xfconf_cache_mutex_lock(&cache->cache_lock); + cache->max_entries = max_entries; + /* TODO: check tree for eviction */ + xfconf_cache_mutex_unlock(&cache->cache_lock); +} + +gint +xfconf_cache_get_max_entries(XfconfCache *cache) +{ + return cache->max_entries; +} + +void +xfconf_cache_set_max_age(XfconfCache *cache, + gint max_age) +{ + xfconf_cache_mutex_lock(&cache->cache_lock); + cache->max_age = max_age; + /* TODO: check tree for eviction */ + xfconf_cache_mutex_unlock(&cache->cache_lock); +} + +gint +xfconf_cache_get_max_age(XfconfCache *cache) +{ + return cache->max_age; +} Added: xfconf/trunk/xfconf/xfconf-cache.h =================================================================== --- xfconf/trunk/xfconf/xfconf-cache.h (rev 0) +++ xfconf/trunk/xfconf/xfconf-cache.h 2009-07-29 21:42:58 UTC (rev 30417) @@ -0,0 +1,79 @@ +/* + * xfconf + * + * Copyright (c) 2009 Brian Tarricone <br...@tarricone.org> + * + * 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; version 2 of the License ONLY. + * + * 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 02110-1301 USA + */ + +#ifndef __XFCONF_CACHE_H__ +#define __XFCONF_CACHE_H__ + +#include <glib-object.h> + +#define XFCONF_TYPE_CACHE (xfconf_cache_get_type()) +#define XFCONF_CACHE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), XFCONF_TYPE_CACHE, XfconfCache)) +#define XFCONF_IS_CACHE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), XFCONF_TYPE_CACHE)) +#define XFCONF_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), XFCONF_TYPE_CACHE, XfconfCacheClass)) +#define XFCONF_IS_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), XFCONF_TYPE_CACHE)) +#define XFCONF_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), XFCONF_TYPE_CACHE, XfconfCacheClass)) + +G_BEGIN_DECLS + +typedef struct _XfconfCache XfconfCache; + +G_GNUC_INTERNAL +GType xfconf_cache_get_type(void) G_GNUC_CONST; + +G_GNUC_INTERNAL +XfconfCache *xfconf_cache_get(const gchar *channel_name); + +G_GNUC_INTERNAL +gboolean xfconf_cache_prefetch(XfconfCache *cache, + const gchar *property_base, + GError **error); + +G_GNUC_INTERNAL +gboolean xfconf_cache_lookup(XfconfCache *cache, + const gchar *property, + GValue *value, + GError **error); + +G_GNUC_INTERNAL +gboolean xfconf_cache_set(XfconfCache *cache, + const gchar *property, + const GValue *value, + GError **error); + +G_GNUC_INTERNAL +gboolean xfconf_cache_reset(XfconfCache *cache, + const gchar *property_base, + gboolean recursive, + GError **error); + +G_GNUC_INTERNAL +void xfconf_cache_set_max_entries(XfconfCache *cache, + gint max_entries); +G_GNUC_INTERNAL +gint xfconf_cache_get_max_entries(XfconfCache *cache); + +G_GNUC_INTERNAL +void xfconf_cache_set_max_age(XfconfCache *cache, + gint max_age); +G_GNUC_INTERNAL +gint xfconf_cache_get_max_age(XfconfCache *cache); + +G_END_DECLS + +#endif /* __XFCONF_CACHE_H__ */ Modified: xfconf/trunk/xfconf/xfconf-channel.c =================================================================== --- xfconf/trunk/xfconf/xfconf-channel.c 2009-07-29 19:11:56 UTC (rev 30416) +++ xfconf/trunk/xfconf/xfconf-channel.c 2009-07-29 21:42:58 UTC (rev 30417) @@ -26,7 +26,9 @@ #endif #include "xfconf-channel.h" +#include "xfconf-cache.h" #include "xfconf-dbus-bindings.h" +#include "xfconf-gvaluefuncs.h" #include "xfconf-private.h" #include "xfconf-marshal.h" #include "xfconf-types.h" @@ -34,6 +36,8 @@ #include "xfconf.h" #include "xfconf-alias.h" +#define IS_SINGLETON_DEFAULT TRUE + #define ALIGN_VAL(val, align) ( ((val) + ((align) -1)) & ~((align) - 1) ) #define REAL_PROP(channel, property) ( (channel)->property_base \ @@ -41,28 +45,6 @@ (property), NULL) \ : (gchar *)(property) ) -#define ERROR_DEFINE GError *___error = NULL -#define ERROR &___error - -#ifdef XFCONF_ENABLE_CHECKS - -#define ERROR_CHECK G_STMT_START{ \ - if(___error) { \ - g_warning("Error check failed at %s():%d: %s", __FUNCTION__, __LINE__, \ - ___error->message); \ - g_error_free(___error); \ - } \ -}G_STMT_END - -#else - -#define ERROR_CHECK G_STMT_START{ \ - if(___error) \ - g_error_free(___error); \ -}G_STMT_END - -#endif - /** * XfconfChannel: * @@ -72,8 +54,12 @@ { GObject parent; + gboolean is_singleton; + gchar *channel_name; gchar *property_base; + + XfconfCache *cache; }; typedef struct _XfconfChannelClass @@ -96,8 +82,12 @@ PROP0 = 0, PROP_CHANNEL_NAME, PROP_PROPERTY_BASE, + PROP_IS_SINGLETON, }; +static GObject *xfconf_channel_constructor(GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties); static void xfconf_channel_set_g_property(GObject *object, guint property_id, const GValue *value, @@ -108,17 +98,14 @@ GParamSpec *pspec); static void xfconf_channel_finalize(GObject *obj); -static void xfconf_channel_property_changed(DBusGProxy *proxy, +static void xfconf_channel_property_changed(XfconfCache *cache, const gchar *channel_name, const gchar *property, const GValue *value, gpointer user_data); -static void xfconf_channel_property_removed(DBusGProxy *proxy, - const gchar *channel_name, - const gchar *property, - gpointer user_data); +G_LOCK_DEFINE_STATIC(__singletons); static guint signals[N_SIGS] = { 0, }; static GHashTable *__channel_singletons = NULL; @@ -131,6 +118,7 @@ { GObjectClass *object_class = (GObjectClass *)klass; + object_class->constructor = xfconf_channel_constructor; object_class->set_property = xfconf_channel_set_g_property; object_class->get_property = xfconf_channel_get_g_property; object_class->finalize = xfconf_channel_finalize; @@ -193,35 +181,111 @@ | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + /** + * XfconfChannel::is-singleton: + * + * Identifies the instance of the class as a singleton instance + * or not. This is mainly used internally by #XfconfChannel + * but may be useful for API users. + **/ + g_object_class_install_property(object_class, PROP_IS_SINGLETON, + g_param_spec_boolean("is-singleton", + "Is Singleton", + "Whether or not this instance is a singleton", + IS_SINGLETON_DEFAULT, + G_PARAM_READWRITE + | G_PARAM_CONSTRUCT_ONLY + | G_PARAM_STATIC_NAME + | G_PARAM_STATIC_NICK + | G_PARAM_STATIC_BLURB)); } static void xfconf_channel_init(XfconfChannel *instance) { - DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); - dbus_g_proxy_connect_signal(proxy, "PropertyChanged", - G_CALLBACK(xfconf_channel_property_changed), - instance, NULL); - dbus_g_proxy_connect_signal(proxy, "PropertyRemoved", - G_CALLBACK(xfconf_channel_property_removed), - instance, NULL); } +static GObject * +xfconf_channel_constructor(GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + const gchar *channel_name = NULL; + gboolean is_singleton = IS_SINGLETON_DEFAULT; + guint i; + XfconfChannel *channel; + + for(i = 0; i < n_construct_properties; ++i) { + if(!strcmp(g_param_spec_get_name(construct_properties[i].pspec), "channel-name")) + channel_name = g_value_get_string(construct_properties[i].value); + else if(!strcmp(g_param_spec_get_name(construct_properties[i].pspec), "is-singleton")) + is_singleton = g_value_get_boolean(construct_properties[i].value); + } + + if(G_UNLIKELY(!channel_name)) { + g_warning("Assertion 'channel_name != NULL' failed"); + return NULL; + } + + if(is_singleton) { + G_LOCK(__singletons); + + if(!__channel_singletons) { + __channel_singletons = g_hash_table_new_full(g_str_hash, g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify)g_object_unref); + } + + channel = g_hash_table_lookup(__channel_singletons, channel_name); + if(!channel) { + channel = XFCONF_CHANNEL(G_OBJECT_CLASS(xfconf_channel_parent_class)->constructor(type, + n_construct_properties, + construct_properties)); + g_hash_table_insert(__channel_singletons, g_strdup(channel_name), + channel); + } + + G_UNLOCK(__singletons); + } else { + channel = XFCONF_CHANNEL(G_OBJECT_CLASS(xfconf_channel_parent_class)->constructor(type, + n_construct_properties, + construct_properties)); + } + + if(!channel->cache) { + channel->cache = xfconf_cache_get(channel_name); + xfconf_cache_prefetch(channel->cache, channel->property_base, NULL); + g_signal_connect(channel->cache, "property-changed", + G_CALLBACK(xfconf_channel_property_changed), channel); + } + + return G_OBJECT(channel); +} + static void xfconf_channel_set_g_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { + XfconfChannel *channel = XFCONF_CHANNEL(object); + switch(property_id) { case PROP_CHANNEL_NAME: - XFCONF_CHANNEL(object)->channel_name = g_value_dup_string(value); + g_assert(channel->channel_name == NULL); + channel->channel_name = g_value_dup_string(value); break; case PROP_PROPERTY_BASE: - XFCONF_CHANNEL(object)->property_base = g_value_dup_string(value); + g_assert(channel->property_base == NULL); + channel->property_base = g_value_dup_string(value); break; + case PROP_IS_SINGLETON: + channel->is_singleton = g_value_get_boolean(value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; @@ -234,15 +298,20 @@ GValue *value, GParamSpec *pspec) { + XfconfChannel *channel = XFCONF_CHANNEL(object); + switch(property_id) { case PROP_CHANNEL_NAME: - g_value_set_string(value, XFCONF_CHANNEL(object)->channel_name); + g_value_set_string(value, channel->channel_name); break; case PROP_PROPERTY_BASE: - g_value_set_string(value, XFCONF_CHANNEL(object)->property_base); + g_value_set_string(value, channel->property_base); break; + case PROP_IS_SINGLETON: + g_value_set_boolean(value, channel->is_singleton); + default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; @@ -253,21 +322,17 @@ xfconf_channel_finalize(GObject *obj) { XfconfChannel *channel = XFCONF_CHANNEL(obj); - DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); - dbus_g_proxy_disconnect_signal(proxy, "PropertyChanged", - G_CALLBACK(xfconf_channel_property_changed), - channel); + g_signal_handlers_disconnect_by_func(channel->cache, + G_CALLBACK(xfconf_channel_property_changed), + channel); + g_object_unref(G_OBJECT(channel->cache)); - dbus_g_proxy_disconnect_signal(proxy, "PropertyRemoved", - G_CALLBACK(xfconf_channel_property_removed), - channel); - g_free(channel->channel_name); g_free(channel->property_base); - if(__channel_singletons) - g_hash_table_remove(__channel_singletons, channel); + /* no need to remove the channel from the hash table if it's a + * singleton, since the hash table owns the channel's only reference */ G_OBJECT_CLASS(xfconf_channel_parent_class)->finalize(obj); } @@ -277,15 +342,17 @@ void _xfconf_channel_shutdown(void) { + G_LOCK(__singletons); if(G_LIKELY(__channel_singletons)) { g_hash_table_destroy(__channel_singletons); __channel_singletons = NULL; } + G_UNLOCK(__singletons); } static void -xfconf_channel_property_changed(DBusGProxy *proxy, +xfconf_channel_property_changed(XfconfCache *cache, const gchar *channel_name, const gchar *property, const GValue *value, @@ -310,54 +377,28 @@ g_quark_from_string(property), property, value); } -static void -xfconf_channel_property_removed(DBusGProxy *proxy, - const gchar *channel_name, - const gchar *property, - gpointer user_data) -{ - XfconfChannel *channel = XFCONF_CHANNEL(user_data); - GValue value = { 0, }; - if(strcmp(channel_name, channel->channel_name) - || (channel->property_base - && !g_str_has_prefix(property, channel->property_base))) - { - return; - } - - if(channel->property_base) { - property += strlen(channel->property_base); - if(!*property) - property = "/"; - } - - g_signal_emit(G_OBJECT(channel), signals[SIG_PROPERTY_CHANGED], - g_quark_from_string(property), property, &value); -} - - - static gboolean xfconf_channel_set_internal(XfconfChannel *channel, const gchar *property, GValue *value) { - DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); gboolean ret; gchar *real_property = REAL_PROP(channel, property); ERROR_DEFINE; g_return_val_if_fail(XFCONF_IS_CHANNEL(channel) && property, FALSE); - ret = xfconf_client_set_property(proxy, channel->channel_name, real_property, - value, ERROR); + ret = xfconf_cache_set(channel->cache, real_property, value, ERROR); if(!ret) ERROR_CHECK; if(real_property != property) g_free(real_property); + if(ret) + g_signal_emit(channel, signals[SIG_PROPERTY_CHANGED], 0, property, value); + return ret; } @@ -366,7 +407,6 @@ const gchar *property, GValue *value) { - DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); GValue tmp_val = { 0, }, *val; gboolean ret; gchar *real_property = REAL_PROP(channel, property); @@ -384,8 +424,7 @@ else val = value; - ret = xfconf_client_get_property(proxy, channel->channel_name, - real_property, val, ERROR); + ret = xfconf_cache_lookup(channel->cache, real_property, val, ERROR); if(!ret) ERROR_CHECK; @@ -468,9 +507,7 @@ else if(!g_value_transform(value_src, value_dest)) { g_warning("Unable to convert array member %d from type \"%s\" to type \"%s\"", i, G_VALUE_TYPE_NAME(value_src), g_type_name(gtype)); - /* avoid pulling in all of libxfconf-gvaluefuncs for _xfconf_gvalue_free() */ - g_value_unset(value_dest); - g_free(value_dest); + _xfconf_gvalue_free(value_dest); /* reuse i; we're returning anyway */ for(i = 0; i < arr_dest->len; ++i) { g_value_unset(g_ptr_array_index(arr_dest, i)); @@ -498,11 +535,6 @@ * * The reference count of the returned channel is owned by libxfconf. * - * In the future, #XfconfChannel may do client-side read caching; - * users of this function will of course benefit from caching done - * on behalf of any caller of the function, unlike callers of - * xfconf_channel_new(). - * * Returns: An #XfconfChannel singleton. * * Since: 4.5.91 @@ -510,27 +542,9 @@ XfconfChannel * xfconf_channel_get(const gchar *channel_name) { - G_LOCK_DEFINE_STATIC(singletons); - XfconfChannel *channel; - - G_LOCK(singletons); - - if(G_UNLIKELY(!__channel_singletons)) { - __channel_singletons = g_hash_table_new_full(g_str_hash, g_str_equal, - (GDestroyNotify)g_free, - (GDestroyNotify)g_object_unref); - } - - channel = g_hash_table_lookup(__channel_singletons, channel_name); - if(!channel) { - channel = xfconf_channel_new(channel_name); - g_hash_table_insert(__channel_singletons, g_strdup(channel_name), - channel); - } - - G_UNLOCK(singletons); - - return channel; + return g_object_new(XFCONF_TYPE_CHANNEL, + "channel-name", channel_name, + NULL); } /** @@ -556,6 +570,7 @@ { return g_object_new(XFCONF_TYPE_CHANNEL, "channel-name", channel_name, + "is-singleton", FALSE, NULL); } @@ -582,6 +597,7 @@ return g_object_new(XFCONF_TYPE_CHANNEL, "channel-name", channel_name, "property-base", property_base, + "is-singleton", FALSE, NULL); } @@ -598,17 +614,13 @@ xfconf_channel_has_property(XfconfChannel *channel, const gchar *property) { - DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); - gboolean exists = FALSE; + gboolean exists; gchar *real_property = REAL_PROP(channel, property); ERROR_DEFINE; - if(!xfconf_client_property_exists(proxy, channel->channel_name, - real_property, &exists, ERROR)) - { + exists = xfconf_cache_lookup(channel->cache, real_property, NULL, ERROR); + if(!exists) ERROR_CHECK; - exists = FALSE; - } if(real_property != property) g_free(real_property); @@ -680,7 +692,6 @@ const gchar *property_base, gboolean recursive) { - DBusGProxy *proxy = _xfconf_get_dbus_g_proxy(); gchar *real_property_base = REAL_PROP(channel, property_base); ERROR_DEFINE; @@ -688,11 +699,8 @@ ((property_base && property_base[0] && property_base[1]) || recursive)); - if(!xfconf_client_reset_property(proxy, channel->channel_name, - real_property_base, recursive, ERROR)) - { + if(!xfconf_cache_reset(channel->cache, real_property_base, recursive, ERROR)) ERROR_CHECK; - } if(real_property_base != property_base) g_free(real_property_base); Modified: xfconf/trunk/xfconf/xfconf-private.h =================================================================== --- xfconf/trunk/xfconf/xfconf-private.h 2009-07-29 19:11:56 UTC (rev 30416) +++ xfconf/trunk/xfconf/xfconf-private.h 2009-07-29 21:42:58 UTC (rev 30417) @@ -22,6 +22,26 @@ #include <dbus/dbus-glib.h> +#ifdef XFCONF_ENABLE_CHECKS + +#define ERROR_DEFINE GError *___error = NULL +#define ERROR &___error +#define ERROR_CHECK G_STMT_START{ \ + if(___error) { \ + g_warning("Error check failed at %s():%d: %s", __FUNCTION__, __LINE__, \ + ___error->message); \ + g_error_free(___error); \ + } \ +}G_STMT_END + +#else + +#define ERROR_DEFINE G_STMT_START{ }G_STMT_END +#define ERROR NULL +#define ERROR_CHECK G_STMT_START{ }G_STMT_END + +#endif + typedef struct { guint n_members; _______________________________________________ Xfce4-commits mailing list Xfce4-commits@xfce.org http://foo-projects.org/mailman/listinfo/xfce4-commits