[dconf/wip/reorg: 3/7] Add DConfChangeset utility class to common/



commit c2f09990517c6569cf356c369754184491123074
Author: Ryan Lortie <desrt desrt ca>
Date:   Sun Jul 1 13:28:16 2012 -0400

    Add DConfChangeset utility class to common/

 common/Makefile.am       |    2 +
 common/dconf-changeset.c |  412 ++++++++++++++++++++++++++++++++++++++++++++++
 common/dconf-changeset.h |   61 +++++++
 3 files changed, 475 insertions(+), 0 deletions(-)
---
diff --git a/common/Makefile.am b/common/Makefile.am
index 250bb91..5c98e55 100644
--- a/common/Makefile.am
+++ b/common/Makefile.am
@@ -6,6 +6,8 @@ libdconf_common_a_CFLAGS = -Wall
 INCLUDES = $(glib_CFLAGS)
 
 libdconf_common_a_SOURCES = \
+	dconf-changeset.h	\
+	dconf-changeset.c	\
 	dconf-paths.c		\
 	dconf-shmdir.h		\
 	dconf-shmdir.c
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;
+}
diff --git a/common/dconf-changeset.h b/common/dconf-changeset.h
new file mode 100644
index 0000000..930c09b
--- /dev/null
+++ b/common/dconf-changeset.h
@@ -0,0 +1,61 @@
+/*
+ * 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>
+ */
+
+#ifndef __dconf_changeset_h__
+#define __dconf_changeset_h__
+
+#include <glib.h>
+
+typedef struct _DConfChangeset                              DConfChangeset;
+
+typedef gboolean     (* DConfChangesetPredicate)                        (const gchar             *key,
+                                                                         GVariant                *value,
+                                                                         gpointer                 user_data);
+
+DConfChangeset *        dconf_changeset_new                             (void);
+
+DConfChangeset *        dconf_changeset_ref                             (DConfChangeset          *changeset);
+void                    dconf_changeset_unref                           (DConfChangeset          *changeset);
+
+void                    dconf_changeset_set                             (DConfChangeset          *changeset,
+                                                                         const gchar             *key,
+                                                                         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         **change_root,
+                                                                         const gchar * const **changes,
+                                                                         GVariant * const    **values);
+
+GVariant *              dconf_changeset_serialise                       (DConfChangeset          *changeset);
+DConfChangeset *        dconf_changeset_deserialise                     (GVariant                *serialised);
+
+#endif /* __dconf_changeset_h__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]