summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/dconf-changeset.c845
-rw-r--r--common/dconf-changeset.h75
-rw-r--r--common/dconf-enums.h42
-rw-r--r--common/dconf-error.c52
-rw-r--r--common/dconf-paths.c253
-rw-r--r--common/dconf-paths.h40
-rw-r--r--common/meson.build46
7 files changed, 1353 insertions, 0 deletions
diff --git a/common/dconf-changeset.c b/common/dconf-changeset.c
new file mode 100644
index 0000000..c80c88c
--- /dev/null
+++ b/common/dconf-changeset.c
@@ -0,0 +1,845 @@
+/*
+ * 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: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "config.h"
+
+#include "dconf-changeset.h"
+#include "dconf-paths.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+/**
+ * SECTION:changeset
+ * @title: DConfChangeset
+ * @Short_description: A set of changes to a dconf database
+ *
+ * #DConfChangeset represents a set of changes that can be made to a
+ * dconf database. Currently supported operations are writing new
+ * values to keys and resetting keys and dirs.
+ *
+ * Create the changeset with dconf_changeset_new() and populate it with
+ * dconf_changeset_set(). Submit it to dconf with
+ * dconf_client_change_fast() or dconf_client_change_sync().
+ * dconf_changeset_new_write() is a convenience constructor for the
+ * common case of writing or resetting a single value.
+ **/
+
+/**
+ * DConfChangeset:
+ *
+ * This is a reference counted opaque structure type. It is not a
+ * #GObject.
+ *
+ * Use dconf_changeset_ref() and dconf_changeset_unref() to manipulate
+ * references.
+ **/
+
+struct _DConfChangeset
+{
+ 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);
+}
+
+/**
+ * dconf_changeset_new:
+ *
+ * Creates a new, empty, #DConfChangeset.
+ *
+ * Returns: (transfer full): the new #DConfChangeset.
+ **/
+DConfChangeset *
+dconf_changeset_new (void)
+{
+ DConfChangeset *changeset;
+
+ changeset = g_slice_new0 (DConfChangeset);
+ changeset->table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, unref_gvariant0);
+ changeset->ref_count = 1;
+
+ return changeset;
+}
+
+/**
+ * dconf_changeset_new_database:
+ * @copy_of: (nullable): a #DConfChangeset to copy
+ *
+ * Creates a new #DConfChangeset in "database" mode, possibly
+ * initialising it with the values of another changeset.
+ *
+ * In a certain sense it's possible to imagine that a #DConfChangeset
+ * could express the contents of an entire dconf 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 dconf_changeset_change()
+ * or the @changeset argument to #DConfClient 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: (transfer full): a new #DConfChangeset in "database" mode
+ *
+ * Since: 0.16
+ */
+DConfChangeset *
+dconf_changeset_new_database (DConfChangeset *copy_of)
+{
+ DConfChangeset *changeset;
+
+ g_return_val_if_fail (copy_of == NULL || copy_of->is_database, NULL);
+
+ changeset = dconf_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;
+}
+
+/**
+ * dconf_changeset_unref:
+ * @changeset: a #DConfChangeset
+ *
+ * Releases a #DConfChangeset reference.
+ **/
+void
+dconf_changeset_unref (DConfChangeset *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 (DConfChangeset, changeset);
+ }
+}
+
+/**
+ * dconf_changeset_ref:
+ * @changeset: a #DConfChangeset
+ *
+ * Increases the reference count on @changeset
+ *
+ * Returns: (transfer full): @changeset
+ **/
+DConfChangeset *
+dconf_changeset_ref (DConfChangeset *changeset)
+{
+ g_atomic_int_inc (&changeset->ref_count);
+
+ return changeset;
+}
+
+static void
+dconf_changeset_record_dir_reset (DConfChangeset *changeset,
+ const gchar *dir)
+{
+ g_return_if_fail (dconf_is_dir (dir, NULL));
+ 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));
+}
+
+/**
+ * dconf_changeset_set:
+ * @changeset: a #DConfChangeset
+ * @path: a path to modify
+ * @value: (nullable): the value for the key, or %NULL to reset. If it has a
+ * floating reference it's consumed.
+ *
+ * Adds an operation to modify @path to a #DConfChangeset.
+ *
+ * @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
+dconf_changeset_set (DConfChangeset *changeset,
+ const gchar *path,
+ GVariant *value)
+{
+ g_return_if_fail (!changeset->is_sealed);
+ g_return_if_fail (dconf_is_path (path, NULL));
+
+ /* 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)
+ dconf_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));
+}
+
+/**
+ * dconf_changeset_get:
+ * @changeset: a #DConfChangeset
+ * @key: the key to check
+ * @value: (transfer full) (optional) (nullable): a return location for the value, or %NULL
+ *
+ * Checks if a #DConfChangeset 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
+dconf_changeset_get (DConfChangeset *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;
+}
+
+/**
+ * dconf_changeset_is_similar_to:
+ * @changeset: a #DConfChangeset
+ * @other: another #DConfChangeset
+ *
+ * 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
+dconf_changeset_is_similar_to (DConfChangeset *changeset,
+ DConfChangeset *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;
+}
+
+/**
+ * DConfChangesetPredicate:
+ * @path: a path, as per dconf_is_path()
+ * @value: (nullable): a #GVariant, or %NULL
+ * @user_data: user data pointer
+ *
+ * Callback function type for predicates over items in a
+ * #DConfChangeset.
+ *
+ * Use with dconf_changeset_all().
+ *
+ * Returns: %TRUE if the predicate is met for the given @path and @value
+ **/
+
+/**
+ * dconf_changeset_all:
+ * @changeset: a #DConfChangeset
+ * @predicate: a #DConfChangesetPredicate
+ * @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
+dconf_changeset_all (DConfChangeset *changeset,
+ DConfChangesetPredicate 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
+dconf_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);
+}
+
+/**
+ * dconf_changeset_seal:
+ * @changeset: a #DConfChangeset
+ *
+ * Seals @changeset.
+ *
+ * When a #DConfChangeset 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 dconf_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.
+ * dconf_changeset_describe() will implicitly seal a changeset.
+ *
+ * This function is idempotent.
+ *
+ * Since: 0.18
+ **/
+void
+dconf_changeset_seal (DConfChangeset *changeset)
+{
+ gsize prefix_length;
+ gint n_items;
+
+ if (changeset->is_sealed)
+ return;
+
+ changeset->is_sealed = TRUE;
+
+ /* This function used to be called dconf_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 *), dconf_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);
+ }
+}
+
+/**
+ * dconf_changeset_describe:
+ * @changeset: a #DConfChangeset
+ * @prefix: (transfer none) (optional) (out): the prefix under which changes have been requested
+ * @paths: (transfer none) (optional) (out): the list of paths changed, relative to @prefix
+ * @values: (transfer none) (optional) (out): the list of values changed
+ *
+ * Describes @changeset.
+ *
+ * @prefix and @paths are presented in the same way as they are for the
+ * DConfClient::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 dconf_changeset_seal().
+ *
+ * Returns: the number of changes (the length of @changes and @values).
+ **/
+guint
+dconf_changeset_describe (DConfChangeset *changeset,
+ const gchar **prefix,
+ const gchar * const **paths,
+ GVariant * const **values)
+{
+ gint n_items;
+
+ n_items = g_hash_table_size (changeset->table);
+
+ dconf_changeset_seal (changeset);
+
+ if (prefix)
+ *prefix = changeset->prefix;
+
+ if (paths)
+ *paths = changeset->paths;
+
+ if (values)
+ *values = changeset->values;
+
+ return n_items;
+}
+
+/**
+ * dconf_changeset_serialise:
+ * @changeset: a #DConfChangeset
+ *
+ * Serialises a #DConfChangeset.
+ *
+ * The returned value has no particular format and should only be passed
+ * to dconf_changeset_deserialise().
+ *
+ * Returns: (transfer full): a floating #GVariant
+ **/
+GVariant *
+dconf_changeset_serialise (DConfChangeset *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);
+}
+
+/**
+ * dconf_changeset_deserialise:
+ * @serialised: (transfer none): a #GVariant from dconf_changeset_serialise()
+ *
+ * Creates a #DConfChangeset according to a serialised description
+ * returned from an earlier call to dconf_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: (transfer full): a new #DConfChangeset
+ **/
+DConfChangeset *
+dconf_changeset_deserialise (GVariant *serialised)
+{
+ DConfChangeset *changeset;
+ GVariantIter iter;
+ const gchar *key;
+ GVariant *value;
+
+ changeset = dconf_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 (dconf_is_key (key, NULL))
+ g_hash_table_insert (changeset->table, g_strdup (key), value ? g_variant_ref (value) : NULL);
+
+ else if (dconf_is_dir (key, NULL) && value == NULL)
+ dconf_changeset_record_dir_reset (changeset, key);
+ }
+
+ return changeset;
+}
+
+/**
+ * dconf_changeset_new_write:
+ * @path: a dconf path
+ * @value: (nullable): a #GVariant, or %NULL. If it has a floating reference it's
+ * consumed.
+ *
+ * Creates a new #DConfChangeset with one change. This is equivalent to
+ * calling dconf_changeset_new() and then dconf_changeset_set() with
+ * @path and @value.
+ *
+ * Returns: a new #DConfChangeset
+ **/
+DConfChangeset *
+dconf_changeset_new_write (const gchar *path,
+ GVariant *value)
+{
+ DConfChangeset *changeset;
+
+ changeset = dconf_changeset_new ();
+ dconf_changeset_set (changeset, path, value);
+
+ return changeset;
+}
+
+/**
+ * dconf_changeset_is_empty:
+ * @changeset: a #DConfChangeset
+ *
+ * Checks if @changeset is empty (ie: contains no changes).
+ *
+ * Returns: %TRUE if @changeset is empty
+ **/
+gboolean
+dconf_changeset_is_empty (DConfChangeset *changeset)
+{
+ return !g_hash_table_size (changeset->table);
+}
+
+/**
+ * dconf_changeset_change:
+ * @changeset: a #DConfChangeset (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
+dconf_changeset_change (DConfChangeset *changeset,
+ DConfChangeset *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
+ * dconf_changeset_build_description() makes the list in the order we
+ * need so just call it and then iterate over the result.
+ */
+
+ if (!dconf_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];
+
+ dconf_changeset_set (changeset, path, value);
+ }
+}
+
+/**
+ * dconf_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
+ * dconf_changeset_change() will result in the two changesets being
+ * equal.
+ *
+ * Returns: (transfer full) (nullable): the changes, or %NULL
+ *
+ * Since: 0.16
+ */
+DConfChangeset *
+dconf_changeset_diff (DConfChangeset *from,
+ DConfChangeset *to)
+{
+ DConfChangeset *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 = dconf_changeset_new ();
+
+ dconf_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 = dconf_changeset_new ();
+
+ dconf_changeset_set (changeset, key, NULL);
+ }
+
+ return changeset;
+}
diff --git a/common/dconf-changeset.h b/common/dconf-changeset.h
new file mode 100644
index 0000000..b0ce450
--- /dev/null
+++ b/common/dconf-changeset.h
@@ -0,0 +1,75 @@
+/*
+ * 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: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_changeset_h__
+#define __dconf_changeset_h__
+
+#include <glib.h>
+
+typedef struct _DConfChangeset DConfChangeset;
+
+typedef gboolean (* DConfChangesetPredicate) (const gchar *path,
+ GVariant *value,
+ gpointer user_data);
+
+DConfChangeset * dconf_changeset_new (void);
+DConfChangeset * dconf_changeset_new_database (DConfChangeset *copy_of);
+
+DConfChangeset * dconf_changeset_new_write (const gchar *path,
+ GVariant *value);
+
+DConfChangeset * dconf_changeset_ref (DConfChangeset *changeset);
+void dconf_changeset_unref (DConfChangeset *changeset);
+
+gboolean dconf_changeset_is_empty (DConfChangeset *changeset);
+
+void dconf_changeset_set (DConfChangeset *changeset,
+ const gchar *path,
+ GVariant *value);
+
+gboolean dconf_changeset_get (DConfChangeset *changeset,
+ const gchar *key,
+ GVariant **value);
+
+gboolean dconf_changeset_is_similar_to (DConfChangeset *changeset,
+ DConfChangeset *other);
+
+gboolean dconf_changeset_all (DConfChangeset *changeset,
+ DConfChangesetPredicate predicate,
+ gpointer user_data);
+
+guint dconf_changeset_describe (DConfChangeset *changeset,
+ const gchar **prefix,
+ const gchar * const **paths,
+ GVariant * const **values);
+
+GVariant * dconf_changeset_serialise (DConfChangeset *changeset);
+DConfChangeset * dconf_changeset_deserialise (GVariant *serialised);
+
+void dconf_changeset_change (DConfChangeset *changeset,
+ DConfChangeset *changes);
+
+DConfChangeset * dconf_changeset_diff (DConfChangeset *from,
+ DConfChangeset *to);
+
+void dconf_changeset_seal (DConfChangeset *changeset);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(DConfChangeset, dconf_changeset_unref)
+
+#endif /* __dconf_changeset_h__ */
diff --git a/common/dconf-enums.h b/common/dconf-enums.h
new file mode 100644
index 0000000..2f10d1a
--- /dev/null
+++ b/common/dconf-enums.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2013 Canonical 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: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_error_h__
+#define __dconf_error_h__
+
+#include <glib.h>
+
+#define DCONF_ERROR (dconf_error_quark ())
+GQuark dconf_error_quark (void);
+
+typedef enum
+{
+ DCONF_ERROR_FAILED,
+ DCONF_ERROR_PATH,
+ DCONF_ERROR_NOT_WRITABLE
+} DConfError;
+
+typedef enum
+{
+ DCONF_READ_FLAGS_NONE = 0,
+ DCONF_READ_DEFAULT_VALUE = (1u << 0),
+ DCONF_READ_USER_VALUE = (1u << 1)
+} DConfReadFlags;
+
+#endif /* __dconf_error_h__ */
diff --git a/common/dconf-error.c b/common/dconf-error.c
new file mode 100644
index 0000000..6339397
--- /dev/null
+++ b/common/dconf-error.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright © 2013 Canonical 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: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "config.h"
+
+#include "dconf-enums.h"
+
+/**
+ * SECTION:error
+ * @title: DConfError
+ * @short_description: GError error codes
+ *
+ * These are the error codes that can be returned from dconf APIs.
+ **/
+
+/**
+ * DCONF_ERROR:
+ *
+ * The error domain of DConf.
+ *
+ * Since: 0.20
+ **/
+
+/**
+ * DConfError:
+ * @DCONF_ERROR_FAILED: generic error
+ * @DCONF_ERROR_PATH: the path given for the operation was a valid path
+ * or was not of the expected type (dir vs. key)
+ * @DCONF_ERROR_NOT_WRITABLE: the given key was not writable
+ *
+ * Possible errors from DConf functions.
+ *
+ * Since: 0.20
+ **/
+
+G_DEFINE_QUARK (dconf_error, dconf_error)
diff --git a/common/dconf-paths.c b/common/dconf-paths.c
new file mode 100644
index 0000000..047429d
--- /dev/null
+++ b/common/dconf-paths.c
@@ -0,0 +1,253 @@
+/*
+ * Copyright © 2008-2009 Ryan Lortie
+ * 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: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "config.h"
+
+#include "dconf-paths.h"
+
+#include "dconf-enums.h"
+
+/**
+ * SECTION:paths
+ * @title: dconf Paths
+ * @short_description: utility functions to validate dconf paths
+ *
+ * Various places in the dconf API speak of "paths", "keys", "dirs" and
+ * relative versions of each of these. This file contains functions to
+ * check if a given string is a valid member of each of these classes
+ * and to report errors when a string is not.
+ *
+ * See each function in this section for a precise description of what
+ * makes a string a valid member of a given class.
+ **/
+
+#define vars gchar c, l
+
+#define nonnull \
+ if (string == NULL) { \
+ g_set_error (error, DCONF_ERROR, DCONF_ERROR_PATH, \
+ "%s not specified", type); \
+ return FALSE; \
+ }
+
+
+#define absolute \
+ if ((l = *string++) != '/') \
+ { \
+ g_set_error (error, DCONF_ERROR, DCONF_ERROR_PATH, \
+ "dconf %s must begin with a slash", type); \
+ return FALSE; \
+ }
+
+#define relative \
+ if (*string == '/') \
+ { \
+ g_set_error (error, DCONF_ERROR, DCONF_ERROR_PATH, \
+ "dconf %s must not begin with a slash", type); \
+ return FALSE; \
+ } \
+ l = '/'
+
+#define no_double_slash \
+ while ((c = *string++)) \
+ { \
+ if (c == '/' && l == '/') \
+ { \
+ g_set_error (error, DCONF_ERROR, DCONF_ERROR_PATH, \
+ "dconf %s must not contain two " \
+ "consecutive slashes", type); \
+ return FALSE; \
+ } \
+ l = c; \
+ } \
+
+#define path \
+ return TRUE
+
+#define key \
+ if (l == '/') \
+ { \
+ g_set_error (error, DCONF_ERROR, DCONF_ERROR_PATH, \
+ "dconf %s must not end with a slash", type); \
+ return FALSE; \
+ } \
+ return TRUE
+
+#define dir \
+ if (l != '/') \
+ { \
+ g_set_error (error, DCONF_ERROR, DCONF_ERROR_PATH, \
+ "dconf %s must end with a slash", type); \
+ return FALSE; \
+ } \
+ return TRUE
+
+
+
+/**
+ * dconf_is_path:
+ * @string: a string
+ * @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
+ *
+ * Checks if @string is a valid dconf path. dconf keys must start with
+ * '/' and not contain '//'.
+ *
+ * A dconf path may be either a key or a dir. See dconf_is_key() and
+ * dconf_is_dir() for examples of each.
+ *
+ * Returns: %TRUE if @string is a path
+ **/
+gboolean
+dconf_is_path (const gchar *string,
+ GError **error)
+{
+#define type "path"
+ vars; nonnull; absolute; no_double_slash; path;
+#undef type
+}
+
+/**
+ * dconf_is_key:
+ * @string: a string
+ * @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
+ *
+ * Checks if @string is a valid dconf key. dconf keys must start with
+ * '/', not contain '//' and not end with '/'.
+ *
+ * A dconf key is the potential location of a single value within the
+ * database.
+ *
+ * "/a", "/a/b" and "/a/b/c" are examples of keys. "", "/", "a", "a/b",
+ * "//a/b", "/a//b", and "/a/" are examples of strings that are not
+ * keys.
+ *
+ * Returns: %TRUE if @string is a key
+ **/
+gboolean
+dconf_is_key (const gchar *string,
+ GError **error)
+{
+#define type "key"
+ vars; nonnull; absolute; no_double_slash; key;
+#undef type
+}
+
+/**
+ * dconf_is_dir:
+ * @string: a string
+ * @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
+ *
+ * Checks if @string is a valid dconf dir. dconf dirs must start and
+ * end with '/' and not contain '//'.
+ *
+ * A dconf dir refers to a subtree of the database that can contain
+ * other dirs or keys. If @string is a dir, then it will be a prefix of
+ * any key or dir contained within it.
+ *
+ * "/", "/a/" and "/a/b/" are examples of dirs. "", "a/", "a/b/",
+ * "//a/b/", "/a//b/" and "/a" are examples of strings that are not
+ * dirs.
+ *
+ * Returns: %TRUE if @string is a dir
+ **/
+gboolean
+dconf_is_dir (const gchar *string,
+ GError **error)
+{
+#define type "dir"
+ vars; nonnull; absolute; no_double_slash; dir;
+#undef type
+}
+
+/**
+ * dconf_is_rel_path:
+ * @string: a string
+ * @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
+ *
+ * Checks if @string is a valid dconf relative path. A relative path is
+ * a string that, when concatenated to a dir, forms a valid dconf path.
+ * This means that a rel must not start with a '/' or contain '//'.
+ *
+ * A dconf rel may be either a relative key or a relative dir. See
+ * dconf_is_rel_key() and dconf_is_rel_dir() for examples of each.
+ *
+ * Returns: %TRUE if @string is a relative path
+ **/
+gboolean
+dconf_is_rel_path (const gchar *string,
+ GError **error)
+{
+#define type "relative path"
+ vars; nonnull; relative; no_double_slash; path;
+#undef type
+}
+
+
+/**
+ * dconf_is_rel_key:
+ * @string: a string
+ * @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
+ *
+ * Checks if @string is a valid dconf relative key. A relative key is a
+ * string that, when concatenated to a dir, forms a valid dconf key.
+ * This means that a relative key must not start or end with a '/' or
+ * contain '//'.
+ *
+ * "a", "a/b" and "a/b/c" are examples of relative keys. "", "/", "/a",
+ * "/a/b", "//a/b", "/a//b", and "a/" are examples of strings that are
+ * not relative keys.
+ *
+ * Returns: %TRUE if @string is a relative key
+ **/
+gboolean
+dconf_is_rel_key (const gchar *string,
+ GError **error)
+{
+#define type "relative key"
+ vars; nonnull; relative; no_double_slash; key;
+#undef type
+}
+
+/**
+ * dconf_is_rel_dir:
+ * @string: a string
+ * @error: a pointer to a #GError, or %NULL, set when %FALSE is returned
+ *
+ * Checks if @string is a valid dconf relative dir. A relative dir is a
+ * string that, when appended to a dir, forms a valid dconf dir. This
+ * means that a relative dir must not start with a '/' or contain '//'
+ * and must end with a '/' except in the case that it is the empty
+ * string (in which case the path specified by appending the rel to a
+ * directory is the original directory).
+ *
+ * "", "a/" and "a/b/" are examples of relative dirs. "/", "/a/",
+ * "/a/b/", "//a/b/", "a//b/" and "a" are examples of strings that are
+ * not relative dirs.
+ *
+ * Returns: %TRUE if @string is a relative dir
+ **/
+gboolean
+dconf_is_rel_dir (const gchar *string,
+ GError **error)
+{
+#define type "relative dir"
+ vars; nonnull; relative; no_double_slash; dir;
+#undef type
+}
diff --git a/common/dconf-paths.h b/common/dconf-paths.h
new file mode 100644
index 0000000..9449fa5
--- /dev/null
+++ b/common/dconf-paths.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2008-2009 Ryan Lortie
+ * 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: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __dconf_paths_h__
+#define __dconf_paths_h__
+
+#include <glib.h>
+
+gboolean dconf_is_path (const gchar *string,
+ GError **error);
+gboolean dconf_is_key (const gchar *string,
+ GError **error);
+gboolean dconf_is_dir (const gchar *string,
+ GError **error);
+
+gboolean dconf_is_rel_path (const gchar *string,
+ GError **error);
+gboolean dconf_is_rel_key (const gchar *string,
+ GError **error);
+gboolean dconf_is_rel_dir (const gchar *string,
+ GError **error);
+
+#endif /* __dconf_paths_h__ */
diff --git a/common/meson.build b/common/meson.build
new file mode 100644
index 0000000..58e0fa8
--- /dev/null
+++ b/common/meson.build
@@ -0,0 +1,46 @@
+common_inc = include_directories('.')
+
+headers = files(
+ 'dconf-changeset.h',
+ 'dconf-enums.h',
+ 'dconf-paths.h',
+)
+
+install_headers(
+ headers,
+ subdir: join_paths('dconf', 'common'),
+)
+
+sources = files(
+ 'dconf-changeset.c',
+ 'dconf-error.c',
+ 'dconf-paths.c',
+)
+
+libdconf_common = static_library(
+ 'dconf-common',
+ sources: sources,
+ include_directories: top_inc,
+ dependencies: glib_dep,
+ c_args: dconf_c_args,
+ pic: true,
+)
+
+libdconf_common_dep = declare_dependency(
+ dependencies: glib_dep,
+ link_whole: libdconf_common,
+)
+
+libdconf_common_hidden = static_library(
+ 'dconf-common-hidden',
+ sources: sources,
+ include_directories: top_inc,
+ dependencies: glib_dep,
+ c_args: dconf_c_args + cc.get_supported_arguments('-fvisibility=hidden'),
+ pic: true,
+)
+
+libdconf_common_hidden_dep = declare_dependency(
+ dependencies: glib_dep,
+ link_with: libdconf_common_hidden,
+)