diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/dconf-changeset.c | 845 | ||||
-rw-r--r-- | common/dconf-changeset.h | 75 | ||||
-rw-r--r-- | common/dconf-enums.h | 42 | ||||
-rw-r--r-- | common/dconf-error.c | 52 | ||||
-rw-r--r-- | common/dconf-paths.c | 253 | ||||
-rw-r--r-- | common/dconf-paths.h | 40 | ||||
-rw-r--r-- | common/meson.build | 46 |
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, +) |