Add new API GSettingsBackendChangeset

This is an import of the DConfChangeset API.  It will eventually be used
as the primary way of communicating changesets between GSettings and
various GSettingsBackend implementations.

It will also be useful in simplifying the implementation of several
GSettingsBackend implementations contained within GLib as well as some
new ones being authored outside of it.

GSettingsBackend has always more or less represented a 'dconf-like'
database structure, which is why DConfChangeset is a generally useful
thing to have as part of the GSettingsBackend API.
This commit is contained in:
Allison Ryan Lortie 2016-01-13 12:22:20 -05:00
parent ff12503ab3
commit d77b68e203
4 changed files with 933 additions and 0 deletions

View File

@ -2508,6 +2508,24 @@ g_settings_backend_path_writable_changed
g_settings_backend_writable_changed
g_settings_backend_changed_tree
g_settings_backend_flatten_tree
GSettingsBackendChangeset
GSettingsBackendChangesetPredicate
g_settings_backend_changeset_all
g_settings_backend_changeset_change
g_settings_backend_changeset_describe
g_settings_backend_changeset_deserialise
g_settings_backend_changeset_diff
g_settings_backend_changeset_get
g_settings_backend_changeset_is_empty
g_settings_backend_changeset_is_similar_to
g_settings_backend_changeset_new
g_settings_backend_changeset_new_database
g_settings_backend_changeset_new_write
g_settings_backend_changeset_ref
g_settings_backend_changeset_seal
g_settings_backend_changeset_serialise
g_settings_backend_changeset_set
g_settings_backend_changeset_unref
g_keyfile_settings_backend_new
g_memory_settings_backend_new
g_null_settings_backend_new

View File

@ -116,6 +116,7 @@ settings_sources = \
gnullsettingsbackend.c \
gsettingsbackendinternal.h \
gsettingsbackend.c \
gsettingsbackendchangeset.c \
gsettingsschema.h \
gsettingsschema-internal.h \
gsettingsschema.c \

View File

@ -59,6 +59,13 @@ G_BEGIN_DECLS
typedef struct _GSettingsBackendPrivate GSettingsBackendPrivate;
typedef struct _GSettingsBackendClass GSettingsBackendClass;
/**
* GSettingsBackendChangeset:
*
* A utility class for grouping changes to a #GSettingsBackend.
*/
typedef struct _GSettingsBackendChangeset GSettingsBackendChangeset;
/**
* GSettingsBackendClass:
* @read: virtual method to read a key's value
@ -176,7 +183,72 @@ gboolean g_settings_backend_is_key (const g
GLIB_AVAILABLE_IN_2_48
gboolean g_settings_backend_is_dir (const gchar *string);
typedef gboolean (* GSettingsBackendChangesetPredicate) (const gchar *path,
GVariant *value,
gpointer user_data);
GLIB_AVAILABLE_IN_2_48
GSettingsBackendChangeset * g_settings_backend_changeset_new (void);
GLIB_AVAILABLE_IN_2_48
GSettingsBackendChangeset * g_settings_backend_changeset_new_database (GSettingsBackendChangeset *copy_of);
GLIB_AVAILABLE_IN_2_48
GSettingsBackendChangeset * g_settings_backend_changeset_new_write (const gchar *path,
GVariant *value);
GLIB_AVAILABLE_IN_2_48
GSettingsBackendChangeset * g_settings_backend_changeset_ref (GSettingsBackendChangeset *changeset);
GLIB_AVAILABLE_IN_2_48
void g_settings_backend_changeset_unref (GSettingsBackendChangeset *changeset);
GLIB_AVAILABLE_IN_2_48
gboolean g_settings_backend_changeset_is_empty (GSettingsBackendChangeset *changeset);
GLIB_AVAILABLE_IN_2_48
void g_settings_backend_changeset_set (GSettingsBackendChangeset *changeset,
const gchar *path,
GVariant *value);
GLIB_AVAILABLE_IN_2_48
gboolean g_settings_backend_changeset_get (GSettingsBackendChangeset *changeset,
const gchar *key,
GVariant **value);
GLIB_AVAILABLE_IN_2_48
gboolean g_settings_backend_changeset_is_similar_to (GSettingsBackendChangeset *changeset,
GSettingsBackendChangeset *other);
GLIB_AVAILABLE_IN_2_48
gboolean g_settings_backend_changeset_all (GSettingsBackendChangeset *changeset,
GSettingsBackendChangesetPredicate predicate,
gpointer user_data);
GLIB_AVAILABLE_IN_2_48
guint g_settings_backend_changeset_describe (GSettingsBackendChangeset *changeset,
const gchar **prefix,
const gchar * const **paths,
GVariant * const **values);
GLIB_AVAILABLE_IN_2_48
GVariant * g_settings_backend_changeset_serialise (GSettingsBackendChangeset *changeset);
GLIB_AVAILABLE_IN_2_48
GSettingsBackendChangeset * g_settings_backend_changeset_deserialise (GVariant *serialised);
GLIB_AVAILABLE_IN_2_48
void g_settings_backend_changeset_change (GSettingsBackendChangeset *changeset,
GSettingsBackendChangeset *changes);
GLIB_AVAILABLE_IN_2_48
GSettingsBackendChangeset * g_settings_backend_changeset_diff (GSettingsBackendChangeset *from,
GSettingsBackendChangeset *to);
GLIB_AVAILABLE_IN_2_48
void g_settings_backend_changeset_seal (GSettingsBackendChangeset *changeset);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GSettingsBackend, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GSettingsBackendChangeset, g_settings_backend_changeset_unref)
G_END_DECLS
#endif /* __G_SETTINGS_BACKEND_H__ */

View File

@ -0,0 +1,842 @@
/*
* Copyright © 2010 Codethink Limited
*
* 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 of the licence, 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: Allison Lortie <desrt@desrt.ca>
*/
#include "config.h"
#include "gsettingsbackend.h"
#include <string.h>
#include <stdlib.h>
/**
* SECTION:changeset
* @title: GSettingsBackendChangeset
* @Short_description: A set of changes to a g_settings_backend database
*
* #GSettingsBackendChangeset represents a set of changes that can be made to a
* g_settings_backend database. Currently supported operations are writing new
* values to keys and resetting keys and dirs.
*
* Create the changeset with g_settings_backend_changeset_new() and populate it with
* g_settings_backend_changeset_set(). Submit it to g_settings_backend with
* g_settings_backend_client_change_fast() or g_settings_backend_client_change_sync().
* g_settings_backend_changeset_new_write() is a convenience constructor for the
* common case of writing or resetting a single value.
**/
/**
* GSettingsBackendChangeset:
*
* This is a reference counted opaque structure type. It is not a
* #GObject.
*
* Use g_settings_backend_changeset_ref() and g_settings_backend_changeset_unref() to manipulate
* references.
**/
struct _GSettingsBackendChangeset
{
GHashTable *table;
GHashTable *dir_resets;
guint is_database : 1;
guint is_sealed : 1;
gint ref_count;
gchar *prefix;
const gchar **paths;
GVariant **values;
};
static void
unref_gvariant0 (gpointer data)
{
if (data)
g_variant_unref (data);
}
/**
* g_settings_backend_changeset_new:
*
* Creates a new, empty, #GSettingsBackendChangeset.
*
* Returns: the new #GSettingsBackendChangeset.
**/
GSettingsBackendChangeset *
g_settings_backend_changeset_new (void)
{
GSettingsBackendChangeset *changeset;
changeset = g_slice_new0 (GSettingsBackendChangeset);
changeset->table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, unref_gvariant0);
changeset->ref_count = 1;
return changeset;
}
/**
* g_settings_backend_changeset_new_database:
* @copy_of: (allow-none): a #GSettingsBackendChangeset to copy
*
* Creates a new #GSettingsBackendChangeset in "database" mode, possibly
* initialising it with the values of another changeset.
*
* In a certain sense it's possible to imagine that a #GSettingsBackendChangeset
* could express the contents of an entire g_settings_backend database -- the
* contents are the database are what you would have if you applied the
* changeset to an empty database. One thing that fails to map in this
* analogy are reset operations -- if we start with an empty database
* then reset operations are meaningless.
*
* A "database" mode changeset is therefore a changeset which is
* incapable of containing reset operations.
*
* It is not permitted to use a database-mode changeset for most
* operations (such as the @change argument to g_settings_backend_changeset_change()
* or the @changeset argument to #GSettingsBackendClient APIs).
*
* If @copy_of is non-%NULL then its contents will be copied into the
* created changeset. @copy_of must be a database-mode changeset.
*
* Returns: a new #GSettingsBackendChangeset in "database" mode
*
* Since: 0.16
*/
GSettingsBackendChangeset *
g_settings_backend_changeset_new_database (GSettingsBackendChangeset *copy_of)
{
GSettingsBackendChangeset *changeset;
g_return_val_if_fail (copy_of == NULL || copy_of->is_database, NULL);
changeset = g_settings_backend_changeset_new ();
changeset->is_database = TRUE;
if (copy_of)
{
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init (&iter, copy_of->table);
while (g_hash_table_iter_next (&iter, &key, &value))
g_hash_table_insert (changeset->table, g_strdup (key), g_variant_ref (value));
}
return changeset;
}
/**
* g_settings_backend_changeset_unref:
* @changeset: a #GSettingsBackendChangeset
*
* Releases a #GSettingsBackendChangeset reference.
**/
void
g_settings_backend_changeset_unref (GSettingsBackendChangeset *changeset)
{
if (g_atomic_int_dec_and_test (&changeset->ref_count))
{
g_free (changeset->prefix);
g_free (changeset->paths);
g_free (changeset->values);
g_hash_table_unref (changeset->table);
if (changeset->dir_resets)
g_hash_table_unref (changeset->dir_resets);
g_slice_free (GSettingsBackendChangeset, changeset);
}
}
/**
* g_settings_backend_changeset_ref:
* @changeset: a #GSettingsBackendChangeset
*
* Increases the reference count on @changeset
*
* Returns: @changeset
**/
GSettingsBackendChangeset *
g_settings_backend_changeset_ref (GSettingsBackendChangeset *changeset)
{
g_atomic_int_inc (&changeset->ref_count);
return changeset;
}
static void
g_settings_backend_changeset_record_dir_reset (GSettingsBackendChangeset *changeset,
const gchar *dir)
{
g_return_if_fail (g_settings_backend_is_dir (dir));
g_return_if_fail (!changeset->is_database);
g_return_if_fail (!changeset->is_sealed);
if (!changeset->dir_resets)
changeset->dir_resets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
g_hash_table_insert (changeset->table, g_strdup (dir), NULL);
g_hash_table_add (changeset->dir_resets, g_strdup (dir));
}
/**
* g_settings_backend_changeset_set:
* @changeset: a #GSettingsBackendChangeset
* @path: a path to modify
* @value: the value for the key, or %NULL to reset
*
* Adds an operation to modify @path to a #GSettingsBackendChangeset.
*
* @path may either be a key or a dir. If it is a key then @value may
* be a #GVariant, or %NULL (to set or reset the key).
*
* If @path is a dir then this must be a reset operation: @value must be
* %NULL. It is not permitted to assign a #GVariant value to a dir.
**/
void
g_settings_backend_changeset_set (GSettingsBackendChangeset *changeset,
const gchar *path,
GVariant *value)
{
g_return_if_fail (!changeset->is_sealed);
g_return_if_fail (g_settings_backend_is_path (path));
/* Check if we are performing a path reset */
if (g_str_has_suffix (path, "/"))
{
GHashTableIter iter;
gpointer key;
g_return_if_fail (value == NULL);
/* When we reset a path we must also reset all keys within that
* path.
*/
g_hash_table_iter_init (&iter, changeset->table);
while (g_hash_table_iter_next (&iter, &key, NULL))
if (g_str_has_prefix (key, path))
g_hash_table_iter_remove (&iter);
/* If this is a non-database then record the reset itself. */
if (!changeset->is_database)
g_settings_backend_changeset_record_dir_reset (changeset, path);
}
/* ...or a value reset */
else if (value == NULL)
{
/* If we're a non-database, record the reset explicitly.
* Otherwise, just reset whatever may be there already.
*/
if (!changeset->is_database)
g_hash_table_insert (changeset->table, g_strdup (path), NULL);
else
g_hash_table_remove (changeset->table, path);
}
/* ...or a normal write. */
else
g_hash_table_insert (changeset->table, g_strdup (path), g_variant_ref_sink (value));
}
/**
* g_settings_backend_changeset_get:
* @changeset: a #GSettingsBackendChangeset
* @key: the key to check
* @value: a return location for the value, or %NULL
*
* Checks if a #GSettingsBackendChangeset has an outstanding request to change
* the value of the given @key.
*
* If the change doesn't involve @key then %FALSE is returned and the
* @value is unmodified.
*
* If the change modifies @key then @value is set either to the value
* for that key, or %NULL in the case that the key is being reset by the
* request.
*
* Returns: %TRUE if the key is being modified by the change
*/
gboolean
g_settings_backend_changeset_get (GSettingsBackendChangeset *changeset,
const gchar *key,
GVariant **value)
{
gpointer tmp;
if (!g_hash_table_lookup_extended (changeset->table, key, NULL, &tmp))
{
/* Did not find an exact match, so check for dir resets */
if (changeset->dir_resets)
{
GHashTableIter iter;
gpointer dir;
g_hash_table_iter_init (&iter, changeset->dir_resets);
while (g_hash_table_iter_next (&iter, &dir, NULL))
if (g_str_has_prefix (key, dir))
{
if (value)
*value = NULL;
return TRUE;
}
}
return FALSE;
}
if (value)
*value = tmp ? g_variant_ref (tmp) : NULL;
return TRUE;
}
/**
* g_settings_backend_changeset_is_similar_to:
* @changeset: a #GSettingsBackendChangeset
* @other: another #GSettingsBackendChangeset
*
* Checks if @changeset is similar to @other.
*
* Two changes are considered similar if they write to the exact same
* set of keys. The values written are not considered.
*
* This check is used to prevent building up a queue of repeated writes
* of the same keys. This is often seen when an application writes to a
* key on every move of a slider or an application window.
*
* Strictly speaking, a write resettings all of "/a/" after a write
* containing "/a/b" could cause the later to be removed from the queue,
* but this situation is difficult to detect and is expected to be
* extremely rare.
*
* Returns: %TRUE if the changes are similar
**/
gboolean
g_settings_backend_changeset_is_similar_to (GSettingsBackendChangeset *changeset,
GSettingsBackendChangeset *other)
{
GHashTableIter iter;
gpointer key;
if (g_hash_table_size (changeset->table) != g_hash_table_size (other->table))
return FALSE;
g_hash_table_iter_init (&iter, changeset->table);
while (g_hash_table_iter_next (&iter, &key, NULL))
if (!g_hash_table_contains (other->table, key))
return FALSE;
return TRUE;
}
/**
* GSettingsBackendChangesetPredicate:
* @path: a path, as per g_settings_backend_is_path()
* @value: a #GVariant, or %NULL
* @user_data: user data pointer
*
* Callback function type for predicates over items in a
* #GSettingsBackendChangeset.
*
* Use with g_settings_backend_changeset_all().
*
* Returns: %TRUE if the predicate is met for the given @path and @value
**/
/**
* g_settings_backend_changeset_all:
* @changeset: a #GSettingsBackendChangeset
* @predicate: a #GSettingsBackendChangesetPredicate
* @user_data: user data to pass to @predicate
*
* Checks if all changes in the changeset satisfy @predicate.
*
* @predicate is called on each item in the changeset, in turn, until it
* returns %FALSE.
*
* If @predicate returns %FALSE for any item, this function returns
* %FALSE. If not (including the case of no items) then this function
* returns %TRUE.
*
* Returns: %TRUE if all items in @changeset satisfy @predicate
*/
gboolean
g_settings_backend_changeset_all (GSettingsBackendChangeset *changeset,
GSettingsBackendChangesetPredicate predicate,
gpointer user_data)
{
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init (&iter, changeset->table);
while (g_hash_table_iter_next (&iter, &key, &value))
if (!(* predicate) (key, value, user_data))
return FALSE;
return TRUE;
}
static gint
g_settings_backend_changeset_string_ptr_compare (gconstpointer a_p,
gconstpointer b_p)
{
const gchar * const *a = a_p;
const gchar * const *b = b_p;
return strcmp (*a, *b);
}
/**
* g_settings_backend_changeset_seal:
* @changeset: a #GSettingsBackendChangeset
*
* Seals @changeset.
*
* When a #GSettingsBackendChangeset is first created, it is mutable and
* non-threadsafe. Once the changeset is populated with the required
* changes, it can be shared between multiple threads, but only by
* making it immutable by "sealing" it.
*
* After the changeset is sealed, you cannot call g_settings_backend_changeset_set()
* or any other functions that would modify it. It is safe, however, to
* share it between multiple threads.
*
* All changesets are unsealed on creation, including those that are
* made by copying changesets that are sealed.
* g_settings_backend_changeset_describe() will implicitly seal a changeset.
*
* This function is idempotent.
*
* Since: 0.18
**/
void
g_settings_backend_changeset_seal (GSettingsBackendChangeset *changeset)
{
gsize prefix_length;
gint n_items;
if (changeset->is_sealed)
return;
changeset->is_sealed = TRUE;
/* This function used to be called g_settings_backend_changeset_build_description()
* because that's basically what sealing is...
*/
n_items = g_hash_table_size (changeset->table);
/* If there are no items then what is there to describe? */
if (n_items == 0)
return;
/* We do three separate passes. This might take a bit longer than
* doing it all at once but it keeps the complexity down.
*
* First, we iterate the table in order to determine the common
* prefix.
*
* Next, we iterate the table again to pull the strings out excluding
* the leading prefix.
*
* We sort the list of paths at this point because the writer
* requires a sorted list in order to ensure that dir resets come
* before writes to keys in that dir.
*
* Finally, we iterate over the sorted list and use the normal
* hashtable lookup in order to populate the values array in the same
* order.
*
* Doing it this way avoids the complication of trying to sort two
* arrays (keys and values) at the same time.
*/
/* Pass 1: determine the common prefix. */
{
GHashTableIter iter;
const gchar *first;
gboolean have_one;
gpointer key;
g_hash_table_iter_init (&iter, changeset->table);
/* We checked above that we have at least one item. */
have_one = g_hash_table_iter_next (&iter, &key, NULL);
g_assert (have_one);
prefix_length = strlen (key);
first = key;
/* Consider the remaining items to find the common prefix */
while (g_hash_table_iter_next (&iter, &key, NULL))
{
const gchar *this = key;
gint i;
for (i = 0; i < prefix_length; i++)
if (first[i] != this[i])
{
prefix_length = i;
break;
}
}
/* We must surely always have a common prefix of '/' */
g_assert (prefix_length > 0);
g_assert (first[0] == '/');
/* We may find that "/a/ab" and "/a/ac" have a common prefix of
* "/a/a" but really we want to trim that back to "/a/".
*
* If there is only one item, leave it alone.
*/
if (n_items > 1)
{
while (first[prefix_length - 1] != '/')
prefix_length--;
}
changeset->prefix = g_strndup (first, prefix_length);
}
/* Pass 2: collect the list of keys, dropping the prefix */
{
GHashTableIter iter;
gpointer key;
gint i = 0;
changeset->paths = g_new (const gchar *, n_items + 1);
g_hash_table_iter_init (&iter, changeset->table);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
const gchar *path = key;
changeset->paths[i++] = path + prefix_length;
}
changeset->paths[i] = NULL;
g_assert (i == n_items);
/* Sort the list of keys */
qsort (changeset->paths, n_items, sizeof (const gchar *), g_settings_backend_changeset_string_ptr_compare);
}
/* Pass 3: collect the list of values */
{
gint i;
changeset->values = g_new (GVariant *, n_items);
for (i = 0; i < n_items; i++)
/* We dropped the prefix when collecting the array.
* Bring it back temporarily, for the lookup.
*/
changeset->values[i] = g_hash_table_lookup (changeset->table, changeset->paths[i] - prefix_length);
}
}
/**
* g_settings_backend_changeset_describe:
* @changeset: a #GSettingsBackendChangeset
* @prefix: the prefix under which changes have been requested
* @paths: the list of paths changed, relative to @prefix
* @values: the list of values changed
*
* Describes @changeset.
*
* @prefix and @paths are presented in the same way as they are for the
* GSettingsBackend::keys-changed signal. @values is an array of the
* same length as @paths. For each key described by an element in
* @paths, @values will contain either a #GVariant (the requested new
* value of that key) or %NULL (to reset a reset).
*
* The @paths array is returned in an order such that dir will always
* come before keys contained within those dirs.
*
* If @changeset is not already sealed then this call will implicitly
* seal it. See g_settings_backend_changeset_seal().
*
* Returns: the number of changes (the length of @changes and @values).
**/
guint
g_settings_backend_changeset_describe (GSettingsBackendChangeset *changeset,
const gchar **prefix,
const gchar * const **paths,
GVariant * const **values)
{
gint n_items;
n_items = g_hash_table_size (changeset->table);
g_settings_backend_changeset_seal (changeset);
if (prefix)
*prefix = changeset->prefix;
if (paths)
*paths = changeset->paths;
if (values)
*values = changeset->values;
return n_items;
}
/**
* g_settings_backend_changeset_serialise:
* @changeset: a #GSettingsBackendChangeset
*
* Serialises a #GSettingsBackendChangeset.
*
* The returned value has no particular format and should only be passed
* to g_settings_backend_changeset_deserialise().
*
* Returns: a floating #GVariant
**/
GVariant *
g_settings_backend_changeset_serialise (GSettingsBackendChangeset *changeset)
{
GVariantBuilder builder;
GHashTableIter iter;
gpointer key, value;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{smv}"));
g_hash_table_iter_init (&iter, changeset->table);
while (g_hash_table_iter_next (&iter, &key, &value))
g_variant_builder_add (&builder, "{smv}", key, value);
return g_variant_builder_end (&builder);
}
/**
* g_settings_backend_changeset_deserialise:
* @serialised: a #GVariant from g_settings_backend_changeset_serialise()
*
* Creates a #GSettingsBackendChangeset according to a serialised description
* returned from an earlier call to g_settings_backend_changeset_serialise().
*
* @serialised has no particular format -- you should only pass a value
* that resulted from an earlier serialise operation.
*
* This call never fails, even if @serialised is not in the correct
* format. Improperly-formatted parts are simply ignored.
*
* Returns: a new #GSettingsBackendChangeset
**/
GSettingsBackendChangeset *
g_settings_backend_changeset_deserialise (GVariant *serialised)
{
GSettingsBackendChangeset *changeset;
GVariantIter iter;
const gchar *key;
GVariant *value;
changeset = g_settings_backend_changeset_new ();
g_variant_iter_init (&iter, serialised);
while (g_variant_iter_loop (&iter, "{&smv}", &key, &value))
{
/* If value is NULL: we may be resetting a key or a dir (a path).
* If value is non-NULL: we must be setting a key.
*
* ie: it is not possible to set a value to a directory.
*
* If we get an invalid case, just fall through and ignore it.
*/
if (g_settings_backend_is_key (key))
g_hash_table_insert (changeset->table, g_strdup (key), value ? g_variant_ref (value) : NULL);
else if (g_settings_backend_is_dir (key) && value == NULL)
g_settings_backend_changeset_record_dir_reset (changeset, key);
}
return changeset;
}
/**
* g_settings_backend_changeset_new_write:
* @path: a g_settings_backend path
* @value: a #GVariant, or %NULL
*
* Creates a new #GSettingsBackendChangeset with one change. This is equivalent to
* calling g_settings_backend_changeset_new() and then g_settings_backend_changeset_set() with
* @path and @value.
*
* Returns: a new #GSettingsBackendChangeset
**/
GSettingsBackendChangeset *
g_settings_backend_changeset_new_write (const gchar *path,
GVariant *value)
{
GSettingsBackendChangeset *changeset;
changeset = g_settings_backend_changeset_new ();
g_settings_backend_changeset_set (changeset, path, value);
return changeset;
}
/**
* g_settings_backend_changeset_is_empty:
* @changeset: a #GSettingsBackendChangeset
*
* Checks if @changeset is empty (ie: contains no changes).
*
* Returns: %TRUE if @changeset is empty
**/
gboolean
g_settings_backend_changeset_is_empty (GSettingsBackendChangeset *changeset)
{
return !g_hash_table_size (changeset->table);
}
/**
* g_settings_backend_changeset_change:
* @changeset: a #GSettingsBackendChangeset (to be changed)
* @changes: the changes to make to @changeset
*
* Applies @changes to @changeset.
*
* If @changeset is a normal changeset then reset requests in @changes
* will be allied to @changeset and then copied down into it. In this
* case the two changesets are effectively being merged.
*
* If @changeset is in database mode then the reset operations in
* @changes will simply be applied to @changeset.
*
* Since: 0.16
**/
void
g_settings_backend_changeset_change (GSettingsBackendChangeset *changeset,
GSettingsBackendChangeset *changes)
{
gsize prefix_len;
gint i;
g_return_if_fail (!changeset->is_sealed);
/* Handling resets is a little bit tricky...
*
* Consider the case that we have @changeset containing a key /a/b and
* @changes containing a reset request for /a/ and a set request for
* /a/c.
*
* It's clear that at the end of this all, we should have only /a/c
* but in order for that to be the case, we need to make sure that we
* process the reset of /a/ before we process the set of /a/c.
*
* The easiest way to do this is to visit the strings in sorted order.
* That removes the possibility of iterating over the hash table, but
* g_settings_backend_changeset_build_description() makes the list in the order we
* need so just call it and then iterate over the result.
*/
if (!g_settings_backend_changeset_describe (changes, NULL, NULL, NULL))
return;
prefix_len = strlen (changes->prefix);
for (i = 0; changes->paths[i]; i++)
{
const gchar *path;
GVariant *value;
/* The changes->paths are just pointers into the keys of the
* hashtable, fast-forwarded past the prefix. Rewind a bit.
*/
path = changes->paths[i] - prefix_len;
value = changes->values[i];
g_settings_backend_changeset_set (changeset, path, value);
}
}
/**
* g_settings_backend_changeset_diff:
* @from: a database mode changeset
* @to: a database mode changeset
*
* Compares to database-mode changesets and produces a changeset that
* describes their differences.
*
* If there is no difference, %NULL is returned.
*
* Applying the returned changeset to @from using
* g_settings_backend_changeset_change() will result in the two changesets being
* equal.
*
* Returns: (transfer full): the changes, or %NULL
*
* Since: 0.16
*/
GSettingsBackendChangeset *
g_settings_backend_changeset_diff (GSettingsBackendChangeset *from,
GSettingsBackendChangeset *to)
{
GSettingsBackendChangeset *changeset = NULL;
GHashTableIter iter;
gpointer key, val;
g_return_val_if_fail (from->is_database, NULL);
g_return_val_if_fail (to->is_database, NULL);
/* We make no attempt to do dir resets, but we could...
*
* For now, we just reset each key individually.
*
* We create our list of changes in two steps:
*
* - iterate the 'to' changeset and note any keys that do not have
* the same value in the 'from' changeset
*
* - iterate the 'from' changeset and note any keys not present in
* the 'to' changeset, recording resets for them
*
* This will cover all changes.
*
* Note: because 'from' and 'to' are database changesets we don't have
* to worry about seeing NULL values or dirs.
*/
g_hash_table_iter_init (&iter, to->table);
while (g_hash_table_iter_next (&iter, &key, &val))
{
GVariant *from_val = g_hash_table_lookup (from->table, key);
if (from_val == NULL || !g_variant_equal (val, from_val))
{
if (!changeset)
changeset = g_settings_backend_changeset_new ();
g_settings_backend_changeset_set (changeset, key, val);
}
}
g_hash_table_iter_init (&iter, from->table);
while (g_hash_table_iter_next (&iter, &key, &val))
if (!g_hash_table_lookup (to->table, key))
{
if (!changeset)
changeset = g_settings_backend_changeset_new ();
g_settings_backend_changeset_set (changeset, key, NULL);
}
return changeset;
}