GSettingsBackend: simplify event handling

Drop the 'vtable' business and switch to a single callback that takes a
new GSettingsEvent structure.

This patch temporarily regresses delayed settings: if a key set within a
delayed settings backend becomes non-writable, it is no longer removed
from the changeset.
This commit is contained in:
Ryan Lortie
2011-12-29 02:40:01 -05:00
parent 3339a395c2
commit acd4439e6f
7 changed files with 296 additions and 405 deletions

View File

@@ -111,7 +111,6 @@ gdbus-daemon-generated.h gdbus-daemon-generated.c: $(srcdir)/dbus-daemon.xml $(s
$(NULL)
settings_headers = \
gsettingsbackend.h \
gsettingsschema.h \
gsettings.h
@@ -624,6 +623,7 @@ gio_headers = \
gioincludedir=$(includedir)/glib-2.0/gio/
gioinclude_HEADERS = \
gsettingsbackend.h \
$(gio_headers) \
gioenumtypes.h

View File

@@ -264,146 +264,14 @@ g_delayed_settings_backend_revert (GDelayedSettingsBackend *delayed)
}
}
/* change notification */
static void
delayed_backend_changed (GObject *target,
GSettingsBackend *backend,
const gchar *key,
gpointer origin_tag)
g_delayed_settings_got_event (GObject *target,
const GSettingsEvent *event)
{
GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
if (origin_tag != delayed->priv)
g_settings_backend_changed (G_SETTINGS_BACKEND (delayed),
key, origin_tag);
}
static void
delayed_backend_keys_changed (GObject *target,
GSettingsBackend *backend,
const gchar *path,
const gchar * const *items,
gpointer origin_tag)
{
GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
if (origin_tag != delayed->priv)
g_settings_backend_keys_changed (G_SETTINGS_BACKEND (delayed),
path, items, origin_tag);
}
static void
delayed_backend_path_changed (GObject *target,
GSettingsBackend *backend,
const gchar *path,
gpointer origin_tag)
{
GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
if (origin_tag != delayed->priv)
g_settings_backend_path_changed (G_SETTINGS_BACKEND (delayed),
path, origin_tag);
}
static void
delayed_backend_writable_changed (GObject *target,
GSettingsBackend *backend,
const gchar *key)
{
GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
gboolean last_one = FALSE;
g_mutex_lock (&delayed->priv->lock);
if (g_tree_lookup (delayed->priv->delayed, key) != NULL &&
!g_settings_backend_get_writable (delayed->priv->backend, key))
{
/* drop the key from our changeset if it just became read-only.
* no need to signal since the writable change below implies it.
*
* note that the item in the tree may very well be set to NULL in
* the case that the user stored a reset. we intentionally don't
* drop the key in this case since a reset will always succeed
* (even against a non-writable key).
*/
g_tree_remove (delayed->priv->delayed, key);
/* if that was the only key... */
last_one = g_tree_nnodes (delayed->priv->delayed) == 0;
}
g_mutex_unlock (&delayed->priv->lock);
if (last_one)
g_delayed_settings_backend_notify_unapplied (delayed);
g_settings_backend_writable_changed (G_SETTINGS_BACKEND (delayed), key);
}
/* slow method until we get foreach-with-remove in GTree
*/
typedef struct
{
const gchar *path;
const gchar **keys;
gsize index;
} CheckPrefixState;
static gboolean
check_prefix (gpointer key,
gpointer value,
gpointer data)
{
CheckPrefixState *state = data;
if (g_str_has_prefix (key, state->path))
state->keys[state->index++] = key;
return FALSE;
}
static void
delayed_backend_path_writable_changed (GObject *target,
GSettingsBackend *backend,
const gchar *path)
{
GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (target);
gboolean last_one = FALSE;
gsize n_keys;
g_mutex_lock (&delayed->priv->lock);
n_keys = g_tree_nnodes (delayed->priv->delayed);
if (n_keys > 0)
{
CheckPrefixState state = { path, g_new (const gchar *, n_keys) };
gsize i;
/* collect a list of possibly-affected keys (ie: matching the path) */
g_tree_foreach (delayed->priv->delayed, check_prefix, &state);
/* drop the keys that have been affected.
*
* don't drop 'reset' keys (see above) */
for (i = 0; i < state.index; i++)
if (g_tree_lookup (delayed->priv->delayed, state.keys[i]) != NULL &&
!g_settings_backend_get_writable (delayed->priv->backend,
state.keys[i]))
g_tree_remove (delayed->priv->delayed, state.keys[i]);
g_free (state.keys);
last_one = g_tree_nnodes (delayed->priv->delayed) == 0;
}
g_mutex_unlock (&delayed->priv->lock);
if (last_one)
g_delayed_settings_backend_notify_unapplied (delayed);
g_settings_backend_path_writable_changed (G_SETTINGS_BACKEND (delayed),
path);
if (event->origin_tag != delayed->priv)
g_settings_backend_report_event (G_SETTINGS_BACKEND (delayed), event);
}
static void
@@ -470,13 +338,6 @@ g_delayed_settings_backend_new (GSettingsBackend *backend,
gpointer owner,
GMainContext *owner_context)
{
static GSettingsListenerVTable vtable = {
delayed_backend_changed,
delayed_backend_path_changed,
delayed_backend_keys_changed,
delayed_backend_writable_changed,
delayed_backend_path_writable_changed
};
GDelayedSettingsBackend *delayed;
delayed = g_object_new (G_TYPE_DELAYED_SETTINGS_BACKEND, NULL);
@@ -487,7 +348,7 @@ g_delayed_settings_backend_new (GSettingsBackend *backend,
g_object_weak_ref (owner, g_delayed_settings_backend_disown, delayed);
g_settings_backend_watch (delayed->priv->backend,
&vtable, G_OBJECT (delayed), NULL);
g_delayed_settings_got_event, G_OBJECT (delayed), NULL);
return delayed;
}

View File

@@ -1132,6 +1132,7 @@ g_file_descriptor_based_get_type
g_file_descriptor_based_get_fd
#endif
g_settings_backend_get_type
g_settings_backend_report_event
g_settings_backend_changed
g_settings_backend_flatten_tree
g_settings_backend_keys_changed

View File

@@ -316,124 +316,175 @@ g_settings_real_writable_change_event (GSettings *settings,
}
static void
settings_backend_changed (GObject *target,
GSettingsBackend *backend,
const gchar *key,
gpointer origin_tag)
g_settings_emit_signal (GSettings *settings,
GSettingsEventType type,
const GQuark *quarks,
gint n_items)
{
GSettings *settings = G_SETTINGS (target);
gboolean ignore_this;
guint signal_id;
switch (type)
{
case G_SETTINGS_EVENT_CHANGE:
signal_id = g_settings_signals[SIGNAL_CHANGE_EVENT];
break;
case G_SETTINGS_EVENT_WRITABLE_CHANGE:
signal_id = g_settings_signals[SIGNAL_WRITABLE_CHANGE_EVENT];
break;
default:
g_assert_not_reached ();
}
/* writable-change-event signals are emitted in a different way */
if (signal_id == g_settings_signals[SIGNAL_WRITABLE_CHANGE_EVENT])
{
if (n_items > 0)
{
gint i;
/* We used to assert here:
for (i = 0; i < n_items; i++)
g_signal_emit (settings, signal_id, 0, quarks[i], &ignore_this);
}
else
g_signal_emit (settings, signal_id, 0, (GQuark) 0, &ignore_this);
return;
}
g_signal_emit (settings, signal_id, 0, quarks, n_items, &ignore_this);
}
static void
g_settings_got_event (GObject *target,
const GSettingsEvent *event)
{
GSettings *settings = G_SETTINGS (target);
const gchar *prefix;
const gchar *path;
gint prefix_len;
gint path_len;
/* The path of the GSettings always ends with '/'.
*
* settings->priv->backend == backend
* For path '/a/b/', consider these prefixes:
*
* but it could be the case that a notification is queued for delivery
* while someone calls g_settings_delay() (which changes the backend).
* - /x/ does not match
* - /a/b/ want to match -- this is us directly
* - /a/ want to match -- this may impact us
* - /a/b/c want to match -- 'c' may be a key
* - /a/b/c/ does not match
*
* Since the delay backend would just pass that straight through
* anyway, it doesn't make sense to try to detect this case.
* Therefore, we just accept it.
* We can quickly determine if we are in a 'want to match' situation
* by fast-forwarding the common part of the GSettings path and the
* event prefix.
*/
path = settings->priv->path;
prefix = event->prefix;
for (i = 0; key[i] == settings->priv->path[i]; i++);
while (*prefix && *prefix == *path)
{
prefix++;
path++;
}
prefix_len = strlen (prefix);
path_len = strlen (path);
if (settings->priv->path[i] == '\0' &&
g_settings_schema_has_key (settings->priv->schema, key + i))
/* If after removing the common prefix, we are left with characters in
* both then it is clear that we are in a non-matching situation.
*/
if (prefix_len && path_len)
return;
if (prefix_len)
{
/* If part of the prefix is remaining then the only possibility is
* that we are emitting a change notification for a single key
* belonging to this settings object. The remainder of that
* prefix (after stripping our path) is exactly the name of that
* key.
*
* Necessarily, this key must not contain a slash in any part of
* it. This is a somewhat common case, so we explicitly check
* that before attempting to do a lookup of the key.
*/
if (strchr (prefix, '/'))
return;
/* If the prefix doesn't end with a slash (as we just verified)
* then it had better be the case that the keys array is empty.
* We don't bother verifying that, though.
*
* We just check if the prefix is the name of one of our keys.
*/
if (g_settings_schema_has_key (settings->priv->schema, prefix))
{
GQuark quark;
quark = g_quark_from_string (key + i);
g_signal_emit (settings, g_settings_signals[SIGNAL_CHANGE_EVENT],
0, &quark, 1, &ignore_this);
quark = g_quark_from_string (prefix);
g_settings_emit_signal (settings, event->type, &quark, 1);
}
}
static void
settings_backend_path_changed (GObject *target,
GSettingsBackend *backend,
const gchar *path,
gpointer origin_tag)
else
{
GSettings *settings = G_SETTINGS (target);
gboolean ignore_this;
if (g_str_has_prefix (settings->priv->path, path))
g_signal_emit (settings, g_settings_signals[SIGNAL_CHANGE_EVENT],
0, NULL, 0, &ignore_this);
}
static void
settings_backend_keys_changed (GObject *target,
GSettingsBackend *backend,
const gchar *path,
const gchar * const *items,
gpointer origin_tag)
{
GSettings *settings = G_SETTINGS (target);
gboolean ignore_this;
gint i;
for (i = 0; settings->priv->path[i] &&
settings->priv->path[i] == path[i]; i++);
if (path[i] == '\0')
{
GQuark quarks[256];
gint j, l = 0;
for (j = 0; items[j]; j++)
{
const gchar *item = items[j];
gint k;
for (k = 0; item[k] == settings->priv->path[i + k]; k++);
if (settings->priv->path[i + k] == '\0' &&
g_settings_schema_has_key (settings->priv->schema, item + k))
quarks[l++] = g_quark_from_string (item + k);
/* "256 quarks ought to be enough for anybody!"
* If this bites you, I'm sorry. Please file a bug.
/* The entire prefix is consumed. This means that the prefix
* ended with a slash and matched our path.
*
* We may still have some remaining part of the path, however. If
* that is true, we need to verify that each item in the keys
* array has this component as a prefix.
*/
g_assert (l < 256);
}
if (l > 0)
g_signal_emit (settings, g_settings_signals[SIGNAL_CHANGE_EVENT],
0, quarks, l, &ignore_this);
}
}
/* If the key array is empty then all items under this path have
* changed. We don't care about any remaining part of our path in
* this case since everything has changed.
*/
if (event->keys[0] == NULL)
g_settings_emit_signal (settings, event->type, NULL, 0);
static void
settings_backend_writable_changed (GObject *target,
GSettingsBackend *backend,
const gchar *key)
else
{
GSettings *settings = G_SETTINGS (target);
gboolean ignore_this;
gint i;
GQuark *quarks;
gint n, i, j;
for (i = 0; key[i] == settings->priv->path[i]; i++);
n = g_strv_length (event->keys);
if (settings->priv->path[i] == '\0' &&
g_settings_schema_has_key (settings->priv->schema, key + i))
g_signal_emit (settings, g_settings_signals[SIGNAL_WRITABLE_CHANGE_EVENT],
0, g_quark_from_string (key + i), &ignore_this);
if (20 < n)
quarks = g_new (GQuark, n);
else
quarks = g_newa (GQuark, n);
j = 0;
for (i = 0; event->keys[i]; i++)
{
gchar *key = event->keys[i];
/* Check the prefix */
if (!g_str_has_prefix (key, path))
continue;
/* Remove that component from the key */
key += path_len;
/* Do the slash check as above, and for the same reason */
if (strchr (key, '/'))
continue;
/* Check if it's actually a key */
if (g_settings_schema_has_key (settings->priv->schema, key))
quarks[j++] = g_quark_from_string (key);
}
static void
settings_backend_path_writable_changed (GObject *target,
GSettingsBackend *backend,
const gchar *path)
{
GSettings *settings = G_SETTINGS (target);
gboolean ignore_this;
/* Only signal if we actually had a match. */
if (j > 0)
g_settings_emit_signal (settings, event->type, quarks, j);
if (g_str_has_prefix (settings->priv->path, path))
g_signal_emit (settings, g_settings_signals[SIGNAL_WRITABLE_CHANGE_EVENT],
0, (GQuark) 0, &ignore_this);
if (20 < n)
g_free (quarks);
}
}
}
/* Properties, Construction, Destruction {{{1 */
@@ -546,14 +597,6 @@ g_settings_get_property (GObject *object,
}
}
static const GSettingsListenerVTable listener_vtable = {
settings_backend_changed,
settings_backend_path_changed,
settings_backend_keys_changed,
settings_backend_writable_changed,
settings_backend_path_writable_changed
};
static void
g_settings_constructed (GObject *object)
{
@@ -579,7 +622,8 @@ g_settings_constructed (GObject *object)
settings->priv->backend = g_settings_backend_get_default ();
g_settings_backend_watch (settings->priv->backend,
&listener_vtable, G_OBJECT (settings),
g_settings_got_event,
G_OBJECT (settings),
settings->priv->main_context);
g_settings_backend_subscribe (settings->priv->backend,
settings->priv->path);
@@ -1907,7 +1951,7 @@ g_settings_delay (GSettings *settings)
settings->priv->backend = G_SETTINGS_BACKEND (settings->priv->delayed);
g_settings_backend_watch (settings->priv->backend,
&listener_vtable, G_OBJECT (settings),
g_settings_got_event, G_OBJECT (settings),
settings->priv->main_context);
g_object_notify (G_OBJECT (settings), "delay-apply");

View File

@@ -127,25 +127,17 @@ is_path (const gchar *path)
struct _GSettingsBackendWatch
{
GObject *target;
const GSettingsListenerVTable *vtable;
GSettingsEventFunc function;
GMainContext *context;
GSettingsBackendWatch *next;
};
struct _GSettingsBackendClosure
{
void (*function) (GObject *target,
GSettingsBackend *backend,
const gchar *name,
gpointer data1,
gpointer data2);
GSettingsEventFunc function;
GSettingsBackend *backend;
GObject *target;
gchar *name;
gpointer data1;
GBoxedFreeFunc data1_free;
gpointer data2;
GSettingsEvent event;
};
static void
@@ -199,7 +191,7 @@ g_settings_backend_watch_weak_notify (gpointer data,
**/
void
g_settings_backend_watch (GSettingsBackend *backend,
const GSettingsListenerVTable *vtable,
GSettingsEventFunc callback,
GObject *target,
GMainContext *context)
{
@@ -241,7 +233,7 @@ g_settings_backend_watch (GSettingsBackend *backend,
watch = g_slice_new (GSettingsBackendWatch);
watch->context = context;
watch->vtable = vtable;
watch->function = callback;
watch->target = target;
g_object_weak_ref (target, g_settings_backend_watch_weak_notify, backend);
@@ -268,47 +260,24 @@ g_settings_backend_invoke_closure (gpointer user_data)
{
GSettingsBackendClosure *closure = user_data;
closure->function (closure->target, closure->backend, closure->name,
closure->data1, closure->data2);
closure->function (closure->target, &closure->event);
closure->data1_free (closure->data1);
g_object_unref (closure->backend);
g_object_unref (closure->target);
g_free (closure->name);
g_strfreev (closure->event.keys);
g_free (closure->event.prefix);
g_slice_free (GSettingsBackendClosure, closure);
return FALSE;
}
static gpointer
pointer_id (gpointer a)
{
return a;
}
static void
pointer_ignore (gpointer a)
{
}
static void
g_settings_backend_dispatch_signal (GSettingsBackend *backend,
gsize function_offset,
const gchar *name,
gpointer data1,
GBoxedCopyFunc data1_copy,
GBoxedFreeFunc data1_free,
gpointer data2)
void
g_settings_backend_report_event (GSettingsBackend *backend,
const GSettingsEvent *event)
{
GSettingsBackendWatch *suffix, *watch, *next;
if (data1_copy == NULL)
data1_copy = pointer_id;
if (data1_free == NULL)
data1_free = pointer_ignore;
/* We're in a little bit of a tricky situation here. We need to hold
* a lock while traversing the list, but we don't want to hold the
* lock while calling back into user code.
@@ -337,12 +306,11 @@ g_settings_backend_dispatch_signal (GSettingsBackend *backend,
closure = g_slice_new (GSettingsBackendClosure);
closure->backend = g_object_ref (backend);
closure->target = watch->target; /* we took our ref above */
closure->function = G_STRUCT_MEMBER (void *, watch->vtable,
function_offset);
closure->name = g_strdup (name);
closure->data1 = data1_copy (data1);
closure->data1_free = data1_free;
closure->data2 = data2;
closure->function = watch->function;
closure->event.type = event->type;
closure->event.prefix = g_strdup (event->prefix);
closure->event.keys = g_strdupv (event->keys);
closure->event.origin_tag = event->origin_tag;
/* we do this here because 'watch' may not live to the end of this
* iteration of the loop (since we may unref the target below).
@@ -394,13 +362,18 @@ g_settings_backend_changed (GSettingsBackend *backend,
const gchar *key,
gpointer origin_tag)
{
GSettingsEvent event;
gchar *null = NULL;
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
g_return_if_fail (is_key (key));
g_settings_backend_dispatch_signal (backend,
G_STRUCT_OFFSET (GSettingsListenerVTable,
changed),
key, origin_tag, NULL, NULL, NULL);
event.type = G_SETTINGS_EVENT_CHANGE;
event.prefix = (gchar *) key;
event.keys = &null;
event.origin_tag = origin_tag;
g_settings_backend_report_event (backend, &event);
}
/**
@@ -440,19 +413,20 @@ g_settings_backend_keys_changed (GSettingsBackend *backend,
gchar const * const *items,
gpointer origin_tag)
{
GSettingsEvent event;
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
g_return_if_fail (is_path (path));
/* XXX: should do stricter checking (ie: inspect each item) */
g_return_if_fail (items != NULL);
g_settings_backend_dispatch_signal (backend,
G_STRUCT_OFFSET (GSettingsListenerVTable,
keys_changed),
path, (gpointer) items,
(GBoxedCopyFunc) g_strdupv,
(GBoxedFreeFunc) g_strfreev,
origin_tag);
event.type = G_SETTINGS_EVENT_CHANGE;
event.prefix = (gchar *) path;
event.keys = (gchar **) items;
event.origin_tag = origin_tag;
g_settings_backend_report_event (backend, &event);
}
/**
@@ -490,13 +464,18 @@ g_settings_backend_path_changed (GSettingsBackend *backend,
const gchar *path,
gpointer origin_tag)
{
GSettingsEvent event;
gchar *null = NULL;
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
g_return_if_fail (is_path (path));
g_settings_backend_dispatch_signal (backend,
G_STRUCT_OFFSET (GSettingsListenerVTable,
path_changed),
path, origin_tag, NULL, NULL, NULL);
event.type = G_SETTINGS_EVENT_CHANGE;
event.prefix = (gchar *) path;
event.keys = &null;
event.origin_tag = origin_tag;
g_settings_backend_report_event (backend, &event);
}
/**
@@ -515,13 +494,18 @@ void
g_settings_backend_writable_changed (GSettingsBackend *backend,
const gchar *key)
{
GSettingsEvent event;
gchar *null = NULL;
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
g_return_if_fail (is_key (key));
g_settings_backend_dispatch_signal (backend,
G_STRUCT_OFFSET (GSettingsListenerVTable,
writable_changed),
key, NULL, NULL, NULL, NULL);
event.type = G_SETTINGS_EVENT_WRITABLE_CHANGE;
event.prefix = (gchar *) key;
event.keys = &null;
event.origin_tag = NULL;
g_settings_backend_report_event (backend, &event);
}
/**
@@ -541,13 +525,18 @@ void
g_settings_backend_path_writable_changed (GSettingsBackend *backend,
const gchar *path)
{
GSettingsEvent event;
gchar *null = NULL;
g_return_if_fail (G_IS_SETTINGS_BACKEND (backend));
g_return_if_fail (is_path (path));
g_settings_backend_dispatch_signal (backend,
G_STRUCT_OFFSET (GSettingsListenerVTable,
path_writable_changed),
path, NULL, NULL, NULL, NULL);
event.type = G_SETTINGS_EVENT_WRITABLE_CHANGE;
event.prefix = (gchar *) path;
event.keys = &null;
event.origin_tag = NULL;
g_settings_backend_report_event (backend, &event);
}
typedef struct

View File

@@ -104,8 +104,24 @@ struct _GSettingsBackend
GSettingsBackendPrivate *priv;
};
typedef enum
{
G_SETTINGS_EVENT_CHANGE,
G_SETTINGS_EVENT_WRITABLE_CHANGE
} GSettingsEventType;
typedef struct
{
GSettingsEventType type;
gchar *prefix;
gchar **keys;
gpointer origin_tag;
} GSettingsEvent;
GType g_settings_backend_get_type (void);
void g_settings_backend_report_event (GSettingsBackend *backend,
const GSettingsEvent *event);
void g_settings_backend_changed (GSettingsBackend *backend,
const gchar *key,
gpointer origin_tag);

View File

@@ -26,32 +26,12 @@
#include "gsettingsbackend.h"
typedef struct
{
void (* changed) (GObject *target,
GSettingsBackend *backend,
const gchar *key,
gpointer origin_tag);
void (* path_changed) (GObject *target,
GSettingsBackend *backend,
const gchar *path,
gpointer origin_tag);
void (* keys_changed) (GObject *target,
GSettingsBackend *backend,
const gchar *prefix,
const gchar * const *names,
gpointer origin_tag);
void (* writable_changed) (GObject *target,
GSettingsBackend *backend,
const gchar *key);
void (* path_writable_changed) (GObject *target,
GSettingsBackend *backend,
const gchar *path);
} GSettingsListenerVTable;
typedef void (* GSettingsEventFunc) (GObject *target,
const GSettingsEvent *event);
G_GNUC_INTERNAL
void g_settings_backend_watch (GSettingsBackend *backend,
const GSettingsListenerVTable *vtable,
GSettingsEventFunc callback,
GObject *target,
GMainContext *context);
G_GNUC_INTERNAL