[dconf/dbus1] Add support for libdbus-1



commit 0bc13626b1dd2ebb1b9897fb188a119aee19597e
Author: Ryan Lortie <desrt desrt ca>
Date:   Mon Dec 20 10:14:19 2010 -0500

    Add support for libdbus-1

 Makefile.am           |    2 +-
 configure.ac          |    6 +-
 dbus-1/.gitignore     |    3 +
 dbus-1/Makefile.am    |   25 ++
 dbus-1/dconf-dbus-1.c |  630 +++++++++++++++++++++++++++++++++++++++++++++++++
 dbus-1/dconf-dbus-1.h |   45 ++++
 tests/.gitignore      |    1 +
 tests/Makefile.am     |    5 +-
 tests/dbus1.c         |  377 +++++++++++++++++++++++++++++
 9 files changed, 1089 insertions(+), 5 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 219d5a1..148cb94 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
 ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
 
-SUBDIRS = gvdb service gsettings tests client bin engine common docs
+SUBDIRS = gvdb service gsettings tests client bin engine common docs dbus-1
 
 if ENABLE_EDITOR
 SUBDIRS += editor
diff --git a/configure.ac b/configure.ac
index 309e2da..61a045d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -21,8 +21,9 @@ GOBJECT_INTROSPECTION_CHECK([0.9.5])
 GTK_DOC_CHECK([1.15])
 
 # Dependencies
-PKG_CHECK_MODULES(gio, gio-2.0 >= 2.25.16)
-
+PKG_CHECK_MODULES(glib, glib-2.0 >= 2.25.16)
+PKG_CHECK_MODULES(gio, gio-2.0)
+PKG_CHECK_MODULES(dbus, dbus-1)
 
 AC_ARG_ENABLE(editor,
               AC_HELP_STRING([--disable-editor],
@@ -55,6 +56,7 @@ AC_CONFIG_FILES([
   client/dconf.pc
   client/Makefile
   service/Makefile
+  dbus-1/Makefile
   bin/Makefile
   editor/Makefile
   tests/Makefile
diff --git a/dbus-1/.gitignore b/dbus-1/.gitignore
new file mode 100644
index 0000000..3ea6490
--- /dev/null
+++ b/dbus-1/.gitignore
@@ -0,0 +1,3 @@
+libdconf-dbus-1.so
+libdconf-dbus-1.so.0
+libdconf-dbus-1.so.0.0.0
diff --git a/dbus-1/Makefile.am b/dbus-1/Makefile.am
new file mode 100644
index 0000000..c9bd7ba
--- /dev/null
+++ b/dbus-1/Makefile.am
@@ -0,0 +1,25 @@
+AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings -fPIC -DPIC
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/gvdb -I$(top_srcdir)/engine $(dbus_CFLAGS) $(glib_CFLAGS)
+
+shlibdir = $(libdir)
+shlib_PROGRAMS = libdconf-dbus-1.so.0.0.0
+
+libdconf_dbus_1_so_0_0_0_LDADD = $(glib_LIBS) $(dbus_LIBS)
+libdconf_dbus_1_so_0_0_0_LDFLAGS = -module -avoid-version -shared
+libdconf_dbus_1_so_0_0_0_SOURCES = \
+	../engine/dconf-engine.c	\
+	../common/dconf-shmdir.c	\
+	../gvdb/gvdb-reader.c		\
+	dconf-dbus-1.c
+
+libdconf-dbus-1.so.0 libdconf-dbus-1.so: libdconf-dbus-1.so.0.0.0
+	$(AM_V_GEN) ln -fs libdconf-dbus-1.so.0.0.0 $@
+
+install-data-hook:
+	ln -fs libdconf-dbus-1.so.0.0.0 $(DESTDIR)$(shlibdir)/libdconf-dbus-1.so.0
+	ln -fs libdconf-dbus-1.so.0.0.0 $(DESTDIR)$(shlibdir)/libdconf-dbus-1.so
+
+uninstall-hook:
+	rm -f $(DESTDIR)$(shlibdir)/libdconf-dbus-1.so.0
+	rm -f $(DESTDIR)$(shlibdir)/libdconf-dbus-1.so
+
diff --git a/dbus-1/dconf-dbus-1.c b/dbus-1/dconf-dbus-1.c
new file mode 100644
index 0000000..c3a75bc
--- /dev/null
+++ b/dbus-1/dconf-dbus-1.c
@@ -0,0 +1,630 @@
+/**
+ * Copyright © 2010 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the licence, or (at
+ * your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ **/
+
+#include "dconf-dbus-1.h"
+
+#include <dconf-engine.h>
+
+#include <string.h>
+
+typedef struct _Outstanding Outstanding;
+
+struct _DConfDBusClient
+{
+  DBusConnection *session_bus;
+  DBusConnection *system_bus;
+  guint session_filter;
+  guint system_filter;
+  gint ref_count;
+
+  Outstanding *outstanding;
+  gchar *anti_expose_tag;
+
+  DConfEngine *engine;
+};
+
+static void
+dconf_dbus_client_add_value_to_iter (DBusMessageIter *iter,
+                                     GVariant        *value)
+{
+  GVariantClass class;
+
+  class = g_variant_classify (value);
+
+  switch (class)
+    {
+    case G_VARIANT_CLASS_BOOLEAN:
+      {
+        dbus_bool_t boolean;
+
+        boolean = g_variant_get_boolean (value);
+        dbus_message_iter_append_basic (iter, 'b', &boolean);
+      }
+      break;
+
+    case G_VARIANT_CLASS_BYTE:
+    case G_VARIANT_CLASS_INT16:
+    case G_VARIANT_CLASS_UINT16:
+    case G_VARIANT_CLASS_INT32:
+    case G_VARIANT_CLASS_UINT32:
+    case G_VARIANT_CLASS_INT64:
+    case G_VARIANT_CLASS_UINT64:
+    case G_VARIANT_CLASS_DOUBLE:
+      dbus_message_iter_append_basic (iter, class,
+                                      g_variant_get_data (value));
+      break;
+
+    case G_VARIANT_CLASS_STRING:
+    case G_VARIANT_CLASS_OBJECT_PATH:
+    case G_VARIANT_CLASS_SIGNATURE:
+      {
+        const gchar *str;
+
+        str = g_variant_get_string (value, NULL);
+        dbus_message_iter_append_basic (iter, class, &str);
+      }
+      break;
+
+    case G_VARIANT_CLASS_ARRAY:
+      {
+        const gchar *contained;
+        DBusMessageIter sub;
+        gint i, n;
+
+        contained = g_variant_get_type_string (value) + 1;
+        n = g_variant_n_children (value);
+        dbus_message_iter_open_container (iter, 'a', contained, &sub);
+        for (i = 0; i < n; i++)
+          {
+            GVariant *child;
+
+            child = g_variant_get_child_value (value, i);
+            dconf_dbus_client_add_value_to_iter (&sub, child);
+            g_variant_unref (child);
+          }
+          
+        dbus_message_iter_close_container (iter, &sub);
+      }
+      break;
+
+    case G_VARIANT_CLASS_TUPLE:
+      {
+        DBusMessageIter sub;
+        gint i, n;
+
+        n = g_variant_n_children (value);
+        dbus_message_iter_open_container (iter, 'r', NULL, &sub);
+        for (i = 0; i < n; i++)
+          {
+            GVariant *child;
+
+            child = g_variant_get_child_value (value, i);
+            dconf_dbus_client_add_value_to_iter (&sub, child);
+            g_variant_unref (child);
+          }
+          
+        dbus_message_iter_close_container (iter, &sub);
+      }
+      break;
+
+    case G_VARIANT_CLASS_DICT_ENTRY:
+      {
+        DBusMessageIter sub;
+        gint i;
+
+        dbus_message_iter_open_container (iter, 'e', NULL, &sub);
+        for (i = 0; i < 2; i++)
+          {
+            GVariant *child;
+
+            child = g_variant_get_child_value (value, i);
+            dconf_dbus_client_add_value_to_iter (&sub, child);
+            g_variant_unref (child);
+          }
+          
+        dbus_message_iter_close_container (iter, &sub);
+      }
+      break;
+
+    case G_VARIANT_CLASS_VARIANT:
+      {
+        DBusMessageIter sub;
+        GVariant *child;
+
+        child = g_variant_get_variant (value);
+        dbus_message_iter_open_container (iter, 'v',
+                                          g_variant_get_type_string (child),
+                                          &sub);
+        dconf_dbus_client_add_value_to_iter (&sub, child);
+        dbus_message_iter_close_container (iter, &sub);
+        g_variant_unref (child);
+      }
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+#if 0
+static void
+dconf_settings_backend_signal (DBusConnection *connection,
+                               const gchar    *sender_name,
+                               const gchar    *object_path,
+                               const gchar    *interface_name,
+                               const gchar    *signal_name,
+                               GVariant       *parameters,
+                               gpointer        user_data)
+{
+  DConfDBusClient *dcdbc = user_data;
+  const gchar *anti_expose;
+  const gchar **rels;
+  const gchar *path;
+  gchar bus_type;
+
+  if (connection == dcdbc->session_bus)
+    {
+      anti_expose = dcdbc->anti_expose_tag;
+      bus_type = 'e';
+    }
+
+  else if (connection == dcdbc->system_bus)
+    {
+      anti_expose = NULL;
+      bus_type = 'y';
+    }
+
+  else
+    g_assert_not_reached ();
+
+  if (dconf_engine_decode_notify (dcdbc->engine, anti_expose,
+                                  &path, &rels, bus_type,
+                                  sender_name, interface_name,
+                                  signal_name, parameters))
+    {
+      /** XXX EMIT CHANGES XXX **/
+      g_free (rels);
+    }
+}
+#endif
+
+static void
+dconf_settings_backend_send (DConfDBusClient               *dcdbc,
+                             DConfEngineMessage            *dcem,
+                             DBusPendingCallNotifyFunction  callback,
+                             gpointer                       user_data)
+{
+  DBusConnection *connection;
+  gint i;
+
+  for (i = 0; i < dcem->n_messages; i++)
+    {
+      switch (dcem->bus_types[i])
+        {
+        case 'e':
+          connection = dcdbc->session_bus;
+          break;
+
+        case 'y':
+          connection = dcdbc->system_bus;
+          break;
+
+        default:
+          g_assert_not_reached ();
+        }
+
+      if (connection == NULL && callback != NULL)
+        callback (NULL, user_data);
+
+      if (connection != NULL)
+        {
+          DBusPendingCall *pending;
+          DBusMessageIter diter;
+          DBusMessage *message;
+          GVariantIter giter;
+          GVariant *child;
+
+          message = dbus_message_new_method_call (dcem->bus_name,
+                                                  dcem->object_path,
+                                                  dcem->interface_name,
+                                                  dcem->method_name);
+
+          dbus_message_iter_init_append (message, &diter);
+          g_variant_iter_init (&giter, dcem->parameters[i]);
+
+          while ((child = g_variant_iter_next_value (&giter)))
+            {
+              dconf_dbus_client_add_value_to_iter (&diter, child);
+              g_variant_unref (child);
+            }
+
+          dbus_connection_send_with_reply (connection, message,
+                                           &pending, 120000);
+          dbus_pending_call_set_notify (pending, callback, user_data, NULL);
+          dbus_message_unref (message);
+        }
+    }
+}
+
+static GVariant *
+dconf_settings_backend_send_finish (DBusPendingCall *pending)
+{
+  DBusMessage *message;
+
+  if (pending == NULL)
+    return NULL;
+
+  message = dbus_pending_call_steal_reply (pending);
+  dbus_pending_call_unref (pending);
+
+  /* XXX remove args */
+
+  dbus_message_unref (message);
+
+  return g_variant_new ("()");
+}
+
+struct _Outstanding
+{
+  Outstanding *next;
+
+  DConfDBusClient *dcdbc;
+  DConfEngineMessage    dcem;
+
+  gchar    *set_key;
+  GVariant *set_value;
+  GTree    *tree;
+};
+
+static void
+dconf_settings_backend_outstanding_returned (DBusPendingCall *pending,
+                                             gpointer         user_data)
+{
+  Outstanding *outstanding = user_data;
+  DConfDBusClient *dcdbc;
+  GVariant *reply;
+
+  dcdbc = outstanding->dcdbc;
+
+  /* One way or another we no longer need this hooked into the list.
+   */
+  {
+    Outstanding **tmp;
+
+    for (tmp = &dcdbc->outstanding; tmp; tmp = &(*tmp)->next)
+      if (*tmp == outstanding)
+        {
+          *tmp = outstanding->next;
+          break;
+        }
+  }
+
+  reply = dconf_settings_backend_send_finish (pending);
+
+  if (reply)
+    {
+      /* Success.
+       *
+       * We want to ensure that we don't emit an extra change
+       * notification signal when we see the signal that the service is
+       * about to send, so store the tag so we know to ignore it when
+       * the signal comes.
+       *
+       * No thread safety issue here since this variable is only
+       * accessed from the worker thread.
+       */
+      g_free (dcdbc->anti_expose_tag);
+      g_variant_get_child (reply, 0, "s", &dcdbc->anti_expose_tag);
+      g_variant_unref (reply);
+    }
+  else
+    {
+      /* An error of some kind.
+       *
+       * We already removed the outstanding entry from the list, so the
+       * unmodified database is now visible to the client.  Change
+       * notify so that they see it.
+       */
+      if (outstanding->set_key)
+          /* XXX emit */;
+      else
+          /* XXX emit */;
+    }
+
+  dconf_engine_message_destroy (&outstanding->dcem);
+  dconf_dbus_client_unref (outstanding->dcdbc);
+  g_free (outstanding->set_key);
+
+  if (outstanding->set_value)
+    g_variant_unref (outstanding->set_value);
+
+  if (outstanding->tree)
+    g_tree_unref (outstanding->tree);
+
+  g_slice_free (Outstanding, outstanding);
+}
+
+static void
+dconf_settings_backend_queue (DConfDBusClient *dcdbc,
+                              DConfEngineMessage   *dcem,
+                              const gchar          *set_key,
+                              GVariant             *set_value,
+                              GTree                *tree)
+{
+  Outstanding *outstanding;
+
+  outstanding = g_slice_new (Outstanding);
+  outstanding->dcdbc = dconf_dbus_client_ref (dcdbc);
+  outstanding->dcem = *dcem;
+
+  outstanding->set_key = g_strdup (set_key);
+  outstanding->set_value = set_value ? g_variant_ref_sink (set_value) : NULL;
+  outstanding->tree = tree ? g_tree_ref (tree) : NULL;
+
+  outstanding->next = dcdbc->outstanding;
+  dcdbc->outstanding = outstanding;
+
+  dconf_settings_backend_send (outstanding->dcdbc,
+                               &outstanding->dcem,
+                               dconf_settings_backend_outstanding_returned,
+                               outstanding);
+}
+
+static gboolean
+dconf_settings_backend_scan_outstanding_tree (GTree       *tree,
+                                              const gchar *key,
+                                              gsize        key_length,
+                                              gpointer    *value)
+{
+  gchar *mykey;
+
+  mykey = g_alloca (key_length + 1);
+  memcpy (mykey, key, key_length + 1);
+
+  while (!g_tree_lookup_extended (tree, mykey, NULL, value) &&
+         --key_length)
+    {
+      while (mykey[key_length - 1] != '/')
+        key_length--;
+
+      mykey[key_length] = '\0';
+    }
+
+  return key_length != 0;
+}
+
+static gboolean
+dconf_settings_backend_scan_outstanding (DConfDBusClient  *backend,
+                                         const gchar           *key,
+                                         GVariant             **value)
+{
+  gboolean found = FALSE;
+  Outstanding *node;
+  gsize length;
+
+  length = strlen (key);
+
+  if G_LIKELY (backend->outstanding == NULL)
+    return FALSE;
+
+  for (node = backend->outstanding; node; node = node->next)
+    {
+      if (node->set_key)
+        {
+          if (strcmp (key, node->set_key) == 0)
+            {
+              if (node->set_value != NULL)
+                *value = g_variant_ref (node->set_value);
+              else
+                *value = NULL;
+
+              found = TRUE;
+              break;
+            }
+        }
+
+      else
+        {
+          gpointer result;
+
+          if (dconf_settings_backend_scan_outstanding_tree (node->tree, key,
+                                                            length, &result))
+            {
+              if (result)
+                *value = g_variant_ref (result);
+              else
+                *value = NULL;
+
+              found = TRUE;
+              break;
+            }
+        }
+    }
+
+  return found;
+}
+
+
+GVariant *
+dconf_dbus_client_read (DConfDBusClient *dcdbc,
+                        const gchar     *key)
+{
+  GVariant *value;
+
+  if (dconf_settings_backend_scan_outstanding (dcdbc, key, &value))
+    return value;
+
+  return dconf_engine_read (dcdbc->engine, key);
+}
+
+gboolean
+dconf_dbus_client_write (DConfDBusClient *dcdbc,
+                         const gchar     *key,
+                         GVariant        *value)
+{
+  DConfEngineMessage dcem;
+
+  if (!dconf_engine_write (dcdbc->engine, key, value, &dcem, NULL))
+    return FALSE;
+
+  dconf_settings_backend_queue (dcdbc, &dcem, key, value, NULL);
+  /* XXX emit */
+
+  return TRUE;
+}
+
+typedef struct
+{
+  DConfDBusClient *dcdbc;
+  guint64 state;
+  gchar name[1];
+} OutstandingWatch;
+
+static OutstandingWatch *
+outstanding_watch_new (DConfDBusClient *dcdbc,
+                       const gchar     *name)
+{
+  OutstandingWatch *watch;
+  gsize length;
+
+  length = strlen (name);
+  watch = g_malloc (G_STRUCT_OFFSET (OutstandingWatch, name) + length + 1);
+  watch->dcdbc = dconf_dbus_client_ref (dcdbc);
+  watch->state = dconf_engine_get_state (dcdbc->engine);
+  strcpy (watch->name, name);
+
+  return watch;
+}
+
+static void
+outstanding_watch_free (OutstandingWatch *watch)
+{
+  dconf_dbus_client_unref (watch->dcdbc);
+  g_free (watch);
+}
+
+static void
+add_match_done (DBusPendingCall *pending,
+                gpointer         user_data)
+{
+  OutstandingWatch *watch = user_data;
+  GError *error = NULL;
+  GVariant *reply;
+
+  reply = dconf_settings_backend_send_finish (pending);
+
+  if (reply == NULL)
+    {
+      g_critical ("DBus AddMatch for dconf path '%s': %s",
+                  watch->name, error->message);
+      outstanding_watch_free (watch);
+      g_error_free (error);
+
+      return;
+    }
+
+  else
+    g_variant_unref (reply); /* it is just an empty tuple */
+
+  /* In the normal case we can just free everything and be done.
+   *
+   * There is a fleeting chance, however, that the database has changed
+   * in the meantime.  In that case we can only assume that the subject
+   * of this watch changed in that time period and emit a signal to that
+   * effect.
+   */
+  if (dconf_engine_get_state (watch->dcdbc->engine) != watch->state)
+      /* XXX emit watch->name */
+
+  outstanding_watch_free (watch);
+}
+
+void
+dconf_dbus_client_subscribe (DConfDBusClient *dcdbc,
+                             const gchar     *name)
+{
+  OutstandingWatch *watch;
+  DConfEngineMessage dcem;
+ 
+  watch = outstanding_watch_new (dcdbc, name);
+  dconf_engine_watch (dcdbc->engine, name, &dcem);
+  dconf_settings_backend_send (dcdbc, &dcem, add_match_done, watch);
+  dconf_engine_message_destroy (&dcem);
+}
+
+void
+dconf_dbus_client_unsubscribe (DConfDBusClient *dcdbc,
+                               const gchar     *name)
+{
+  DConfEngineMessage dcem;
+
+  dconf_engine_unwatch (dcdbc->engine, name, &dcem);
+  dconf_settings_backend_send (dcdbc, &dcem, NULL, NULL);
+  dconf_engine_message_destroy (&dcem);
+}
+
+gboolean
+dconf_dbus_client_has_pending (DConfDBusClient *dcdbc)
+{
+  return dcdbc->outstanding != NULL;
+}
+
+static GVariant *
+dconf_settings_backend_service_func (DConfEngineMessage *dcem)
+{
+  g_assert_not_reached ();
+}
+
+DConfDBusClient *
+dconf_dbus_client_new (const gchar    *profile,
+                       DBusConnection *system,
+                       DBusConnection *session)
+{
+  DConfDBusClient *dcdbc;
+
+  dconf_engine_set_service_func (dconf_settings_backend_service_func);
+
+  dcdbc = g_slice_new (DConfDBusClient);
+  dcdbc->engine = dconf_engine_new (profile);
+  dcdbc->system_bus = dbus_connection_ref (system);
+  dcdbc->session_bus = dbus_connection_ref (session);
+  dcdbc->ref_count = 1;
+
+  return dcdbc;
+}
+
+void
+dconf_dbus_client_unref (DConfDBusClient *dcdbc)
+{
+  if (--dcdbc->ref_count == 0)
+    {
+      dbus_connection_unref (dcdbc->session_bus);
+      dbus_connection_unref (dcdbc->system_bus);
+
+      g_slice_free (DConfDBusClient, dcdbc);
+    }
+}
+
+DConfDBusClient *
+dconf_dbus_client_ref (DConfDBusClient *dcdbc)
+{
+  dcdbc->ref_count++;
+
+  return dcdbc;
+}
diff --git a/dbus-1/dconf-dbus-1.h b/dbus-1/dconf-dbus-1.h
new file mode 100644
index 0000000..d137fc8
--- /dev/null
+++ b/dbus-1/dconf-dbus-1.h
@@ -0,0 +1,45 @@
+/**
+ * Copyright © 2010 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the licence, or (at
+ * your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ **/
+
+#ifndef _dconf_dbus_1_h_
+#define _dconf_dbus_1_h_
+
+#include <dbus/dbus.h>
+#include <glib.h>
+
+typedef struct _DConfDBusClient DConfDBusClient;
+
+DConfDBusClient *       dconf_dbus_client_new                           (const gchar    *profile,
+                                                                         DBusConnection *system,
+                                                                         DBusConnection *session);
+void                    dconf_dbus_client_unref                         (DConfDBusClient *dcdbc);
+DConfDBusClient *       dconf_dbus_client_ref                           (DConfDBusClient *dcdbc);
+
+GVariant *              dconf_dbus_client_read                          (DConfDBusClient *dcdbc,
+                                                                         const gchar     *key);
+gboolean                dconf_dbus_client_write                         (DConfDBusClient *dcdbc,
+                                                                         const gchar     *key,
+                                                                         GVariant        *value);
+void                    dconf_dbus_client_subscribe                     (DConfDBusClient *dcdbc,
+                                                                         const gchar     *name);
+void                    dconf_dbus_client_unsubscribe                   (DConfDBusClient *dcdbc,
+                                                                         const gchar     *name);
+gboolean                dconf_dbus_client_has_pending                   (DConfDBusClient *dcdbc);
+
+#endif /* _dconf_dbus_1_h_ */
diff --git a/tests/.gitignore b/tests/.gitignore
index 791498d..e4f23e5 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -1,2 +1,3 @@
 paths
 gsettings
+dbus1
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 99f3760..6f14f30 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,8 +1,9 @@
 AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings
-INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/engine -I$(top_srcdir)/client $(gio_CFLAGS)
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/engine -I$(top_srcdir)/client $(gio_CFLAGS) -I$(top_srcdir)/dbus-1 $(dbus_CFLAGS)
 
-noinst_PROGRAMS = paths gsettings
+noinst_PROGRAMS = paths gsettings dbus1
 
+dbus1_LDFLAGS = -L../dbus-1 -ldconf-dbus-1 $(glib_LIBS)
 gsettings_LDADD = $(gio_LIBS)
 paths_LDADD = $(gio_LIBS)
 paths_SOURCES = \
diff --git a/tests/dbus1.c b/tests/dbus1.c
new file mode 100644
index 0000000..497d26a
--- /dev/null
+++ b/tests/dbus1.c
@@ -0,0 +1,377 @@
+/**
+ * Copyright © 2010 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the licence, or (at
+ * your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ **/
+
+#include <dconf-dbus-1.h>
+
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+
+static DConfDBusClient *backend;
+
+static void
+free_variant (gpointer data)
+{
+  if (data != NULL)
+    g_variant_unref (data);
+}
+
+static GVariant *
+do_read (const gchar *key)
+{
+  return dconf_dbus_client_read (backend, key);
+}
+
+static gboolean
+do_write (const gchar *key,
+          GVariant    *value)
+{
+  return dconf_dbus_client_write (backend, key, value);
+}
+
+static gboolean
+do_write_tree (GTree *tree)
+{
+  g_assert_not_reached ();
+}
+
+static void
+do_sync (void)
+{
+  g_assert_not_reached ();
+}
+
+#define RANDOM_ELEMENT(array) \
+  array[g_test_rand_int_range(0, G_N_ELEMENTS(array))]
+
+static gchar *
+random_key (void)
+{
+  const gchar * const words[] = {
+    "alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf",
+    "hotel", "india", "juliet", "kilo", "lima", "mike", "november",
+    "oscar", "papa", "quebec", "romeo", "sierra", "tango", "uniform",
+    "victor", "whiskey", "xray", "yankee", "zulu"
+  };
+  const gchar *parts[8];
+  gint n, i;
+
+  n = g_test_rand_int_range (2, 8);
+  parts[0] = "";
+  for (i = 1; i < n; i++)
+    parts[i] = RANDOM_ELEMENT (words);
+  parts[n] = NULL;
+
+  return g_strjoinv ("/", (gchar **) parts);
+}
+
+static GVariant *
+random_value (void)
+{
+  switch (g_test_rand_int_range (0, 3))
+    {
+    case 0:
+      return g_variant_new_int32 (g_test_rand_int ());
+
+    case 1:
+      return g_variant_new_boolean (g_test_rand_bit ());
+
+    case 2:
+      {
+        gint length = g_test_rand_int_range (0, 24);
+        gchar buffer[24];
+        gint i;
+
+        for (i = 0; i < length; i++)
+          buffer[i] = 'a' + g_test_rand_int_range (0, 26);
+        buffer[i] = '\0';
+
+        return g_variant_new_string (buffer);
+      }
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static GTree *
+random_tree (void)
+{
+  GTree *tree;
+  gint n;
+
+  tree = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
+                          g_free, free_variant);
+  n = g_test_rand_int_range (1, 20);
+
+  while (n--)
+    g_tree_insert (tree, random_key (), g_variant_ref_sink (random_value ()));
+
+  return tree;
+}
+
+static void
+apply_change (GHashTable  *table,
+              const gchar *key,
+              GVariant    *value)
+{
+  if (value)
+    g_hash_table_insert (table, g_strdup (key), g_variant_ref_sink (value));
+  else
+    g_hash_table_insert (table, g_strdup (key), NULL);
+}
+
+static gboolean
+apply_one_change (gpointer key,
+                  gpointer value,
+                  gpointer user_data)
+{
+  apply_change (user_data, key, value);
+  return FALSE;
+}
+
+static void
+apply_change_tree (GHashTable *table,
+                   GTree      *tree)
+{
+  g_tree_foreach (tree, apply_one_change, table);
+}
+
+static GHashTable *implicit;
+static GHashTable *explicit;
+
+#if 0
+/* interpose */
+void
+g_settings_backend_changed (GSettingsBackend *backend_,
+                            const gchar      *key,
+                            gpointer          origin_tag)
+{
+  GVariant *value;
+
+  /* ensure that we see no dupes from the bus */
+  g_assert (origin_tag == do_write);
+  g_assert (backend == backend_);
+
+  value = do_read (key);
+  apply_change (implicit, key, value);
+  g_variant_unref (value);
+}
+
+/* interpose */
+void
+g_settings_backend_keys_changed (GSettingsBackend    *backend_,
+                                 const gchar         *path,
+                                 const gchar * const *items,
+                                 gpointer             origin_tag)
+{
+  gint i;
+
+  /* ensure that we see no dupes from the bus */
+  g_assert (origin_tag == do_write);
+  g_assert (backend == backend_);
+
+  for (i = 0; items[i]; i++)
+    {
+      GVariant *value;
+      gchar *key;
+
+      key = g_strconcat (path, items[i], NULL);
+      value = do_read (key);
+
+      apply_change (implicit, key, value);
+
+      g_variant_unref (value);
+      g_free (key);
+    }
+}
+#endif
+
+static void
+setup (void)
+{
+  gchar *file;
+
+  file = g_build_filename (g_get_user_config_dir (),
+                           "dconf/test", NULL);
+  unlink (file);
+  g_free (file);
+
+  g_setenv ("DCONF_PROFILE", "test", false);
+
+  backend = dconf_dbus_client_new ("test",
+                                   dbus_bus_get (DBUS_BUS_SYSTEM, 0),
+                                   dbus_bus_get (DBUS_BUS_SESSION, 0));
+  dconf_dbus_client_subscribe (backend, "/");
+
+  implicit = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                    g_free, free_variant);
+  explicit = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                    g_free, free_variant);
+
+  sleep(1);
+}
+
+static void
+make_random_change (void)
+{
+  if (1)
+    {
+      GVariant *value;
+      gchar *key;
+
+      key = random_key ();
+      value = random_value ();
+      apply_change (explicit, key, value);
+      do_write (key, value);
+
+      g_free (key);
+    }
+  else
+    {
+      GTree *tree;
+
+      tree = random_tree ();
+      apply_change_tree (explicit, tree);
+      do_write_tree (tree);
+
+      g_tree_unref (tree);
+    }
+}
+
+guint64 dconf_time;
+guint64 ghash_time;
+guint64 lookups;
+gboolean dots;
+
+static void
+verify_consistency (void)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  if (dots)
+    g_print (".");
+  else
+    g_print ("(%d)", g_hash_table_size (explicit));
+
+  /*g_assert (g_hash_table_size (explicit) == g_hash_table_size
+   * (implicit)); */
+  g_hash_table_iter_init (&iter, explicit);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      if (value)
+        {
+          GVariant *other;
+
+          dconf_time -= g_get_monotonic_time ();
+          other = do_read (key);
+          dconf_time += g_get_monotonic_time ();
+          g_assert (g_variant_equal (value, other));
+          g_variant_unref (other);
+        }
+      else
+        {
+          g_assert (g_hash_table_lookup (implicit, key) == NULL);
+          g_assert (do_read (key) == NULL);
+        }
+
+      lookups++;
+    }
+}
+
+#if 0
+static void
+dump_table (void)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  g_print ("{");
+  g_hash_table_iter_init (&iter, explicit);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    if (value)
+      {
+        gchar *printed;
+
+        if (value)
+          printed = g_variant_print (value, FALSE);
+        else
+          printed = g_strdup ("None");
+
+        g_print ("'%s': %s, ", (gchar *) key, printed);
+        g_free (printed);
+      }
+  g_print ("}");
+}
+#endif
+
+static void
+test (void)
+{
+  int i;
+
+  g_print ("Testing dconf...");
+  for (i = 0; i < 1000; i++)
+    {
+      g_print (" %d", i);
+      make_random_change ();
+      verify_consistency ();
+    }
+
+  g_print ("\n");
+  g_print ("GSettings lookup time:  %f µs/lookup\n",
+           ((double) dconf_time / lookups));
+  g_print ("GHashTable lookup time: %f µs/lookup\n",
+           ((double) ghash_time / lookups));
+
+  dconf_time = 0;
+  ghash_time = 0;
+  lookups = 0;
+
+  g_print ("\nWaiting for dconf-service to catch up...");
+  do_sync ();
+  g_print (" done.\n");
+
+  g_print ("Measuring dconf read performance...");
+  dots = TRUE;
+  for (i = 0; i < 1000; i++)
+    verify_consistency ();
+  g_print ("\n");
+
+  g_print ("dconf lookup time:      %f µs/lookup\n",
+           ((double) dconf_time / lookups));
+  g_print ("GHashTable lookup time: %f µs/lookup\n",
+           ((double) ghash_time / lookups));
+
+  g_hash_table_unref (explicit);
+  g_hash_table_unref (implicit);
+}
+
+int
+main (int argc, char **argv)
+{
+  g_test_init (&argc, &argv, NULL);
+
+  setup ();
+
+  test ();
+
+  return g_test_run ();
+}



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