| /* | |
| * Copyright © 2009, 2010 Codethink Limited | |
| * | |
| * SPDX-License-Identifier: LGPL-2.1-or-later | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 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 | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, see <http://www.gnu.org/licenses/>. | |
| * | |
| * Author: Ryan Lortie <desrt@desrt.ca> | |
| */ | |
| #include "config.h" | |
| #include "gdelayedsettingsbackend.h" | |
| #include "gsettingsbackendinternal.h" | |
| #include <string.h> | |
| struct _GDelayedSettingsBackendPrivate | |
| { | |
| GSettingsBackend *backend; | |
| GMutex lock; | |
| GTree *delayed; | |
| GMainContext *owner_context; | |
| gpointer owner; | |
| }; | |
| G_DEFINE_TYPE_WITH_PRIVATE (GDelayedSettingsBackend, | |
| g_delayed_settings_backend, | |
| G_TYPE_SETTINGS_BACKEND) | |
| static gboolean | |
| invoke_notify_unapplied (gpointer data) | |
| { | |
| g_object_notify (data, "has-unapplied"); | |
| g_object_unref (data); | |
| return FALSE; | |
| } | |
| static void | |
| g_delayed_settings_backend_notify_unapplied (GDelayedSettingsBackend *delayed) | |
| { | |
| GMainContext *target_context; | |
| GObject *target; | |
| g_mutex_lock (&delayed->priv->lock); | |
| if (delayed->priv->owner) | |
| { | |
| target_context = delayed->priv->owner_context; | |
| target = g_object_ref (delayed->priv->owner); | |
| } | |
| else | |
| { | |
| target_context = NULL; | |
| target = NULL; | |
| } | |
| g_mutex_unlock (&delayed->priv->lock); | |
| if (target != NULL) | |
| g_main_context_invoke (target_context, invoke_notify_unapplied, target); | |
| } | |
| static GVariant * | |
| g_delayed_settings_backend_read (GSettingsBackend *backend, | |
| const gchar *key, | |
| const GVariantType *expected_type, | |
| gboolean default_value) | |
| { | |
| GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend); | |
| gpointer result = NULL; | |
| if (!default_value) | |
| { | |
| g_mutex_lock (&delayed->priv->lock); | |
| if (g_tree_lookup_extended (delayed->priv->delayed, key, NULL, &result)) | |
| { | |
| /* NULL in the tree means we should consult the default value */ | |
| if (result != NULL) | |
| g_variant_ref (result); | |
| else | |
| default_value = TRUE; | |
| } | |
| g_mutex_unlock (&delayed->priv->lock); | |
| } | |
| if (result == NULL) | |
| result = g_settings_backend_read (delayed->priv->backend, key, | |
| expected_type, default_value); | |
| return result; | |
| } | |
| static GVariant * | |
| g_delayed_settings_backend_read_user_value (GSettingsBackend *backend, | |
| const gchar *key, | |
| const GVariantType *expected_type) | |
| { | |
| GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend); | |
| gboolean value_found = FALSE; | |
| gpointer result = NULL; | |
| /* If we find an explicit NULL in our changeset then we want to return | |
| * NULL (because the user value has been reset). | |
| * | |
| * Otherwise, chain up. | |
| */ | |
| g_mutex_lock (&delayed->priv->lock); | |
| value_found = g_tree_lookup_extended (delayed->priv->delayed, key, NULL, &result); | |
| if (result) | |
| g_variant_ref (result); | |
| g_mutex_unlock (&delayed->priv->lock); | |
| if (value_found) | |
| return result; | |
| return g_settings_backend_read_user_value (delayed->priv->backend, key, expected_type); | |
| } | |
| static gboolean | |
| g_delayed_settings_backend_write (GSettingsBackend *backend, | |
| const gchar *key, | |
| GVariant *value, | |
| gpointer origin_tag) | |
| { | |
| GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend); | |
| gboolean was_empty; | |
| g_mutex_lock (&delayed->priv->lock); | |
| was_empty = g_tree_nnodes (delayed->priv->delayed) == 0; | |
| g_tree_insert (delayed->priv->delayed, g_strdup (key), | |
| g_variant_ref_sink (value)); | |
| g_mutex_unlock (&delayed->priv->lock); | |
| g_settings_backend_changed (backend, key, origin_tag); | |
| if (was_empty) | |
| g_delayed_settings_backend_notify_unapplied (delayed); | |
| return TRUE; | |
| } | |
| static gboolean | |
| add_to_tree (gpointer key, | |
| gpointer value, | |
| gpointer user_data) | |
| { | |
| /* A value may be %NULL if its key has been reset */ | |
| g_tree_insert (user_data, g_strdup (key), (value != NULL) ? g_variant_ref (value) : NULL); | |
| return FALSE; | |
| } | |
| static gboolean | |
| g_delayed_settings_backend_write_tree (GSettingsBackend *backend, | |
| GTree *tree, | |
| gpointer origin_tag) | |
| { | |
| GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend); | |
| gboolean was_empty; | |
| g_mutex_lock (&delayed->priv->lock); | |
| was_empty = g_tree_nnodes (delayed->priv->delayed) == 0; | |
| g_tree_foreach (tree, add_to_tree, delayed->priv->delayed); | |
| g_mutex_unlock (&delayed->priv->lock); | |
| g_settings_backend_changed_tree (backend, tree, origin_tag); | |
| if (was_empty) | |
| g_delayed_settings_backend_notify_unapplied (delayed); | |
| return TRUE; | |
| } | |
| static gboolean | |
| g_delayed_settings_backend_get_writable (GSettingsBackend *backend, | |
| const gchar *name) | |
| { | |
| GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend); | |
| return g_settings_backend_get_writable (delayed->priv->backend, name); | |
| } | |
| static void | |
| g_delayed_settings_backend_reset (GSettingsBackend *backend, | |
| const gchar *key, | |
| gpointer origin_tag) | |
| { | |
| GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend); | |
| gboolean was_empty; | |
| g_mutex_lock (&delayed->priv->lock); | |
| was_empty = g_tree_nnodes (delayed->priv->delayed) == 0; | |
| g_tree_insert (delayed->priv->delayed, g_strdup (key), NULL); | |
| g_mutex_unlock (&delayed->priv->lock); | |
| g_settings_backend_changed (backend, key, origin_tag); | |
| if (was_empty) | |
| g_delayed_settings_backend_notify_unapplied (delayed); | |
| } | |
| static void | |
| g_delayed_settings_backend_subscribe (GSettingsBackend *backend, | |
| const char *name) | |
| { | |
| GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend); | |
| g_settings_backend_subscribe (delayed->priv->backend, name); | |
| } | |
| static void | |
| g_delayed_settings_backend_unsubscribe (GSettingsBackend *backend, | |
| const char *name) | |
| { | |
| GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend); | |
| g_settings_backend_unsubscribe (delayed->priv->backend, name); | |
| } | |
| static GPermission * | |
| g_delayed_settings_backend_get_permission (GSettingsBackend *backend, | |
| const gchar *path) | |
| { | |
| GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend); | |
| return g_settings_backend_get_permission (delayed->priv->backend, path); | |
| } | |
| /* method calls */ | |
| gboolean | |
| g_delayed_settings_backend_get_has_unapplied (GDelayedSettingsBackend *delayed) | |
| { | |
| /* we don't need to lock for this... */ | |
| return g_tree_nnodes (delayed->priv->delayed) > 0; | |
| } | |
| void | |
| g_delayed_settings_backend_apply (GDelayedSettingsBackend *delayed) | |
| { | |
| if (g_tree_nnodes (delayed->priv->delayed) > 0) | |
| { | |
| gboolean success; | |
| GTree *tmp; | |
| g_mutex_lock (&delayed->priv->lock); | |
| tmp = delayed->priv->delayed; | |
| delayed->priv->delayed = g_settings_backend_create_tree (); | |
| success = g_settings_backend_write_tree (delayed->priv->backend, | |
| tmp, delayed->priv); | |
| g_mutex_unlock (&delayed->priv->lock); | |
| if (!success) | |
| g_settings_backend_changed_tree (G_SETTINGS_BACKEND (delayed), | |
| tmp, NULL); | |
| g_tree_unref (tmp); | |
| g_delayed_settings_backend_notify_unapplied (delayed); | |
| } | |
| } | |
| void | |
| g_delayed_settings_backend_revert (GDelayedSettingsBackend *delayed) | |
| { | |
| if (g_tree_nnodes (delayed->priv->delayed) > 0) | |
| { | |
| GTree *tmp; | |
| g_mutex_lock (&delayed->priv->lock); | |
| tmp = delayed->priv->delayed; | |
| delayed->priv->delayed = g_settings_backend_create_tree (); | |
| g_mutex_unlock (&delayed->priv->lock); | |
| g_settings_backend_changed_tree (G_SETTINGS_BACKEND (delayed), tmp, NULL); | |
| g_tree_unref (tmp); | |
| g_delayed_settings_backend_notify_unapplied (delayed); | |
| } | |
| } | |
| /* change notification */ | |
| static void | |
| delayed_backend_changed (GObject *target, | |
| GSettingsBackend *backend, | |
| const gchar *key, | |
| gpointer origin_tag) | |
| { | |
| 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, | |
| gpointer origin_tag, | |
| const gchar * const *items) | |
| { | |
| 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), 0 }; | |
| 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); | |
| } | |
| static void | |
| g_delayed_settings_backend_finalize (GObject *object) | |
| { | |
| GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (object); | |
| g_mutex_clear (&delayed->priv->lock); | |
| g_object_unref (delayed->priv->backend); | |
| g_tree_unref (delayed->priv->delayed); | |
| /* if our owner is still alive, why are we finalizing? */ | |
| g_assert (delayed->priv->owner == NULL); | |
| G_OBJECT_CLASS (g_delayed_settings_backend_parent_class) | |
| ->finalize (object); | |
| } | |
| static void | |
| g_delayed_settings_backend_class_init (GDelayedSettingsBackendClass *class) | |
| { | |
| GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class); | |
| GObjectClass *object_class = G_OBJECT_CLASS (class); | |
| backend_class->read = g_delayed_settings_backend_read; | |
| backend_class->read_user_value = g_delayed_settings_backend_read_user_value; | |
| backend_class->write = g_delayed_settings_backend_write; | |
| backend_class->write_tree = g_delayed_settings_backend_write_tree; | |
| backend_class->reset = g_delayed_settings_backend_reset; | |
| backend_class->get_writable = g_delayed_settings_backend_get_writable; | |
| backend_class->subscribe = g_delayed_settings_backend_subscribe; | |
| backend_class->unsubscribe = g_delayed_settings_backend_unsubscribe; | |
| backend_class->get_permission = g_delayed_settings_backend_get_permission; | |
| object_class->finalize = g_delayed_settings_backend_finalize; | |
| } | |
| static void | |
| g_delayed_settings_backend_init (GDelayedSettingsBackend *delayed) | |
| { | |
| delayed->priv = g_delayed_settings_backend_get_instance_private (delayed); | |
| delayed->priv->delayed = g_settings_backend_create_tree (); | |
| g_mutex_init (&delayed->priv->lock); | |
| } | |
| static void | |
| g_delayed_settings_backend_disown (gpointer data, | |
| GObject *where_the_object_was) | |
| { | |
| GDelayedSettingsBackend *delayed = data; | |
| g_mutex_lock (&delayed->priv->lock); | |
| delayed->priv->owner_context = NULL; | |
| delayed->priv->owner = NULL; | |
| g_mutex_unlock (&delayed->priv->lock); | |
| } | |
| GDelayedSettingsBackend * | |
| 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); | |
| delayed->priv->backend = g_object_ref (backend); | |
| delayed->priv->owner_context = owner_context; | |
| delayed->priv->owner = owner; | |
| g_object_weak_ref (owner, g_delayed_settings_backend_disown, delayed); | |
| g_settings_backend_watch (delayed->priv->backend, | |
| &vtable, G_OBJECT (delayed), NULL); | |
| return delayed; | |
| } |