mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-09-28 10:07:13 +02:00
843 lines
25 KiB
C
843 lines
25 KiB
C
![]() |
/*
|
||
|
* 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;
|
||
|
}
|