diff options
Diffstat (limited to 'common/dconf-changeset.c')
-rw-r--r-- | common/dconf-changeset.c | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/common/dconf-changeset.c b/common/dconf-changeset.c new file mode 100644 index 0000000..43dbe42 --- /dev/null +++ b/common/dconf-changeset.c @@ -0,0 +1,412 @@ +/* + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ryan Lortie <desrt@desrt.ca> + */ + +#include "dconf-changeset.h" +#include "dconf-paths.h" + +#include <string.h> +#include <stdlib.h> + +struct _DConfChangeset +{ + GHashTable *table; + gint ref_count; + + gchar *root; + const gchar **paths; + GVariant **values; +}; + +/** + * dconf_changeset_new: + * + * Creates a new, empty, #DConfChangeset. + * + * Returns: the new #DConfChangeset. + **/ +DConfChangeset * +dconf_changeset_new (void) +{ + DConfChangeset *change; + + change = g_slice_new0 (DConfChangeset); + change->table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref); + + return change; +} + +/** + * dconf_changeset_unref: + * @change: a #DConfChangeset + * + * Releases a #DConfChangeset reference. + **/ +void +dconf_changeset_unref (DConfChangeset *change) +{ + if (g_atomic_int_dec_and_test (&change->ref_count)) + { + g_free (change->root); + g_free (change->paths); + g_free (change->values); + + g_hash_table_unref (change->table); + + g_slice_free (DConfChangeset, change); + } +} + +DConfChangeset * +dconf_changeset_ref (DConfChangeset *change) +{ + g_atomic_int_inc (&change->ref_count); + + return change; +} + +/** + * dconf_changeset_set: + * @change: a #DConfChangeset + * @key: a key to modify + * @value: the value for the key, or %NULL to reset + * + * Adds an operation to modify @key to a #DConfChangeset. + * + * @value, if non-%NULL specifies the new requested value of @key. If + * @value is %NULL, the key is reset. + **/ +void +dconf_changeset_set (DConfChangeset *change, + const gchar *key, + GVariant *value) +{ + g_return_if_fail (change->root == NULL); + + g_hash_table_insert (change->table, g_strdup (key), g_variant_ref_sink (value)); +} + +/** + * dconf_changeset_get: + * @change: a #DConfChangeset + * @key: the key to check + * @value: 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 *change, + const gchar *key, + GVariant **value) +{ + gpointer tmp; + + if (!g_hash_table_lookup_extended (change->table, key, NULL, &tmp)) + return FALSE; + + *value = tmp ? g_variant_ref (tmp) : NULL; + return TRUE; +} + +/** + * dconf_changeset_is_similar_to: + * @change: a #DConfChangeset + * @other: another #DConfChangeset + * + * Checks if @change 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. + * + * Returns: %TRUE if the changes are similar + **/ +gboolean +dconf_changeset_is_similar_to (DConfChangeset *change, + DConfChangeset *other) +{ + GHashTableIter iter; + gpointer key; + + if (g_hash_table_size (change->table) != g_hash_table_size (other->table)) + return FALSE; + + g_hash_table_iter_init (&iter, change->table); + while (g_hash_table_iter_next (&iter, &key, NULL)) + if (!g_hash_table_contains (other->table, key)) + return FALSE; + + return TRUE; +} + +/** + * dconf_changeset_all: + * @change: a #DConfChangeset + * @predicate: a #DConfChangePredicate + * @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 @preciate 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 @change satisfy @predicate + */ +gboolean +dconf_changeset_all (DConfChangeset *change, + DConfChangesetPredicate predicate, + gpointer user_data) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, change->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 = a_p; + + return strcmp (*a, *b); +} + +static void +dconf_changeset_build_description (DConfChangeset *change) +{ + gsize prefix_length; + gint n_items; + + n_items = g_hash_table_size (change->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 rebuilder + * requires a sorted list. + * + * 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; + gpointer key; + + g_hash_table_iter_init (&iter, change->table); + + /* We checked above that we have at least one item. */ + if (!g_hash_table_iter_next (&iter, &key, NULL)) + g_assert_not_reached (); + + 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--; + } + + change->root = g_strndup (first, prefix_length); + } + + /* Pass 2: collect the list of keys, dropping the prefix */ + { + GHashTableIter iter; + gpointer key; + gint i = 0; + + change->paths = g_new (const gchar *, n_items + 1); + + g_hash_table_iter_init (&iter, change->table); + while (g_hash_table_iter_next (&iter, &key, NULL)) + { + const gchar *path = key; + + change->paths[i++] = path + prefix_length; + } + change->paths[i] = NULL; + g_assert (i == n_items); + + /* Sort the list of keys */ + qsort (change->paths, n_items, sizeof (const gchar *), dconf_changeset_string_ptr_compare); + } + + /* Pass 3: collect the list of values */ + { + gint i; + + change->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. + */ + change->values[i] = g_hash_table_lookup (change->table, change->paths[i] - prefix_length); + } + + { + gint i; + + g_print ("Changes were described: %s\n", change->root); + for (i = 0; i < n_items; i++) + g_print (" %s: %s\n", change->paths[i], change->values[i] ? g_variant_print (change->values[i], FALSE) : "(none)"); + g_print ("\n"); + } +} + +guint +dconf_changeset_describe (DConfChangeset *change, + const gchar **root, + const gchar * const **paths, + GVariant * const **values) +{ + gint n_items; + + n_items = g_hash_table_size (change->table); + + if (n_items && !change->root) + dconf_changeset_build_description (change); + + if (root) + *root = change->root; + + if (paths) + *paths = change->paths; + + if (values) + *values = change->values; + + return n_items; +} + +GVariant * +dconf_changeset_serialise (DConfChangeset *change) +{ + GVariantBuilder builder; + GHashTableIter iter; + gpointer key, value; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{smv}")); + + g_hash_table_iter_init (&iter, change->table); + while (g_hash_table_iter_next (&iter, &key, &value)) + g_variant_builder_add (&builder, "{smv}", key, value); + + return g_variant_builder_end (&builder); +} + +DConfChangeset * +dconf_changeset_deserialise (GVariant *serialised) +{ + DConfChangeset *change; + GVariantIter iter; + const gchar *key; + GVariant *value; + + change = 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 (value == NULL) + { + if (dconf_is_path (key, NULL)) + g_hash_table_insert (change->table, g_strdup (key), NULL); + } + else + { + if (dconf_is_key (key, NULL)) + g_hash_table_insert (change->table, g_strdup (key), g_variant_ref (value)); + } + } + + return change; +} |