[dconf/dbus1] dbus-1: add support for watching keys



commit 5ee7a71d40a98b46233b218d22d07fe850a02aef
Author: Ryan Lortie <desrt desrt ca>
Date:   Sun Jan 2 14:05:38 2011 -0500

    dbus-1: add support for watching keys
    
    Add code to the test case to ensure that it's working.

 dbus-1/dconf-dbus-1.c |  216 ++++++++++++++++++++++++++++++++-----------------
 dbus-1/dconf-dbus-1.h |   17 +++-
 tests/dbus1.c         |   56 ++++---------
 3 files changed, 168 insertions(+), 121 deletions(-)
---
diff --git a/dbus-1/dconf-dbus-1.c b/dbus-1/dconf-dbus-1.c
index c3a75bc..7aafc05 100644
--- a/dbus-1/dconf-dbus-1.c
+++ b/dbus-1/dconf-dbus-1.c
@@ -29,6 +29,7 @@ struct _DConfDBusClient
 {
   DBusConnection *session_bus;
   DBusConnection *system_bus;
+  GSList *watches;
   guint session_filter;
   guint system_filter;
   gint ref_count;
@@ -162,48 +163,6 @@ dconf_dbus_client_add_value_to_iter (DBusMessageIter *iter,
     }
 }
 
-#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,
@@ -460,6 +419,43 @@ dconf_settings_backend_scan_outstanding (DConfDBusClient  *backend,
   return found;
 }
 
+/* Watches are reference counted because they can be held both by the
+ * list of watches and by the pending watch registration.  In the normal
+ * case, the registration completes before the watch is unsubscribed
+ * from but it might be the case that the watch is unsubscribed from
+ * before the AddMatch completes.  For that reason, either thing could
+ * be responsible for freeing the watch structure; we solve that
+ * ambiguity using a reference count.
+ *
+ * We just initially set it to 2, since these are the only two users.
+ * That way we can skip having the ref() function.
+ */
+typedef struct
+{
+  DConfDBusClient *dcdbc;
+  gchar           *name;
+  DConfDBusNotify  notify;
+  gpointer         user_data;
+  guint64          initial_state;
+  gint             ref_count;
+} Watch;
+
+
+
+static void
+dconf_dbus_emit_change (DConfDBusClient *dcdbc,
+                        const gchar     *key)
+{
+  GSList *iter;
+
+  for (iter = dcdbc->watches; iter; iter = iter->next)
+    {
+      Watch *watch = iter->data;
+
+      if (g_str_has_prefix (key, watch->name))
+        watch->notify (dcdbc, key, watch->user_data);
+    }
+}
 
 GVariant *
 dconf_dbus_client_read (DConfDBusClient *dcdbc,
@@ -484,46 +480,49 @@ dconf_dbus_client_write (DConfDBusClient *dcdbc,
     return FALSE;
 
   dconf_settings_backend_queue (dcdbc, &dcem, key, value, NULL);
-  /* XXX emit */
+  dconf_dbus_emit_change (dcdbc, key);
 
   return TRUE;
 }
 
-typedef struct
-{
-  DConfDBusClient *dcdbc;
-  guint64 state;
-  gchar name[1];
-} OutstandingWatch;
-
-static OutstandingWatch *
-outstanding_watch_new (DConfDBusClient *dcdbc,
-                       const gchar     *name)
+static Watch *
+watch_new (DConfDBusClient *dcdbc,
+           const gchar     *name,
+           DConfDBusNotify  notify,
+           gpointer         user_data)
 {
-  OutstandingWatch *watch;
-  gsize length;
+  Watch *watch;
 
-  length = strlen (name);
-  watch = g_malloc (G_STRUCT_OFFSET (OutstandingWatch, name) + length + 1);
+  watch = g_slice_new (Watch);
   watch->dcdbc = dconf_dbus_client_ref (dcdbc);
-  watch->state = dconf_engine_get_state (dcdbc->engine);
-  strcpy (watch->name, name);
+  watch->user_data = user_data;
+  watch->name = g_strdup (name);
+  watch->notify = notify;
+
+  watch->initial_state = dconf_engine_get_state (dcdbc->engine);
+  watch->ref_count = 2;
+
+  dcdbc->watches = g_slist_prepend (dcdbc->watches, watch);
 
   return watch;
 }
 
 static void
-outstanding_watch_free (OutstandingWatch *watch)
+watch_unref (Watch *watch)
 {
-  dconf_dbus_client_unref (watch->dcdbc);
-  g_free (watch);
+  if (--watch->ref_count == 0)
+    {
+      dconf_dbus_client_unref (watch->dcdbc);
+      g_free (watch->name);
+      g_slice_free (Watch, watch);
+    }
 }
 
 static void
 add_match_done (DBusPendingCall *pending,
                 gpointer         user_data)
 {
-  OutstandingWatch *watch = user_data;
+  Watch *watch = user_data;
   GError *error = NULL;
   GVariant *reply;
 
@@ -531,10 +530,16 @@ add_match_done (DBusPendingCall *pending,
 
   if (reply == NULL)
     {
+      /* This is extremely unlikely to happen and it happens
+       * asynchronous to the user's call.  Since the user doesn't know
+       * that it happened, we pretend that it didn't (ie: we leave the
+       * watch structure in the list).
+       */
+
       g_critical ("DBus AddMatch for dconf path '%s': %s",
                   watch->name, error->message);
-      outstanding_watch_free (watch);
       g_error_free (error);
+      watch_unref (watch);
 
       return;
     }
@@ -542,27 +547,29 @@ add_match_done (DBusPendingCall *pending,
   else
     g_variant_unref (reply); /* it is just an empty tuple */
 
-  /* In the normal case we can just free everything and be done.
+  /* In the normal case we're 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 */
+  if (dconf_engine_get_state (watch->dcdbc->engine) != watch->initial_state)
+    watch->notify (watch->dcdbc, watch->name, watch->user_data);
 
-  outstanding_watch_free (watch);
+  watch_unref (watch);
 }
 
 void
 dconf_dbus_client_subscribe (DConfDBusClient *dcdbc,
-                             const gchar     *name)
+                             const gchar     *name,
+                             DConfDBusNotify  notify,
+                             gpointer         user_data)
 {
-  OutstandingWatch *watch;
   DConfEngineMessage dcem;
+  Watch *watch;
  
-  watch = outstanding_watch_new (dcdbc, name);
+  watch = watch_new (dcdbc, name, notify, user_data);
   dconf_engine_watch (dcdbc->engine, name, &dcem);
   dconf_settings_backend_send (dcdbc, &dcem, add_match_done, watch);
   dconf_engine_message_destroy (&dcem);
@@ -570,13 +577,30 @@ dconf_dbus_client_subscribe (DConfDBusClient *dcdbc,
 
 void
 dconf_dbus_client_unsubscribe (DConfDBusClient *dcdbc,
-                               const gchar     *name)
+                               DConfDBusNotify  notify,
+                               gpointer         user_data)
 {
   DConfEngineMessage dcem;
+  GSList **ptr;
 
-  dconf_engine_unwatch (dcdbc->engine, name, &dcem);
-  dconf_settings_backend_send (dcdbc, &dcem, NULL, NULL);
-  dconf_engine_message_destroy (&dcem);
+  for (ptr = &dcdbc->watches; *ptr; ptr = &(*ptr)->next)
+    {
+      Watch *watch = (*ptr)->data;
+
+      if (watch->notify == notify && watch->user_data == user_data)
+        {
+          *ptr = g_slist_remove (*ptr, *ptr);
+
+          dconf_engine_unwatch (dcdbc->engine, watch->name, &dcem);
+          dconf_settings_backend_send (dcdbc, &dcem, NULL, NULL);
+          dconf_engine_message_destroy (&dcem);
+          watch_unref (watch);
+
+          return;
+        }
+    }
+
+  g_warning ("No matching watch found to unsubscribe");
 }
 
 gboolean
@@ -591,6 +615,41 @@ dconf_settings_backend_service_func (DConfEngineMessage *dcem)
   g_assert_not_reached ();
 }
 
+static DBusHandlerResult
+dconf_dbus_client_filter (DBusConnection *connection,
+                          DBusMessage    *message,
+                          void           *user_data)
+{
+  DConfDBusClient *dcdbc = user_data;
+
+  if (dbus_message_is_signal (message, "ca.desrt.dconf.Writer", "Notify") &&
+      dbus_message_has_signature (message, "sass"))
+    {
+      DBusMessageIter iter, sub;
+      const gchar *path, *tag;
+
+      dbus_message_iter_init (message, &iter);
+      dbus_message_iter_get_basic (&iter, &path);
+      dbus_message_iter_recurse (&iter, &sub);
+      dbus_message_iter_get_basic (&iter, &tag);
+
+      /* XXX todo: anti-expose */
+
+      while (dbus_message_iter_get_arg_type (&sub) == 's')
+        {
+          const gchar *item;
+          gchar *full;
+
+          dbus_message_iter_get_basic (&iter, &item);
+          full = g_strconcat (path, item, NULL);
+          dconf_dbus_emit_change (dcdbc, full);
+          g_free (full);
+        }
+    }
+
+  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
 DConfDBusClient *
 dconf_dbus_client_new (const gchar    *profile,
                        DBusConnection *system,
@@ -606,6 +665,9 @@ dconf_dbus_client_new (const gchar    *profile,
   dcdbc->session_bus = dbus_connection_ref (session);
   dcdbc->ref_count = 1;
 
+  dbus_connection_add_filter (system, dconf_dbus_client_filter, dcdbc, NULL);
+  dbus_connection_add_filter (session, dconf_dbus_client_filter, dcdbc, NULL);
+
   return dcdbc;
 }
 
@@ -614,6 +676,10 @@ dconf_dbus_client_unref (DConfDBusClient *dcdbc)
 {
   if (--dcdbc->ref_count == 0)
     {
+      dbus_connection_remove_filter (dcdbc->session_bus,
+                                     dconf_dbus_client_filter, dcdbc);
+      dbus_connection_remove_filter (dcdbc->system_bus,
+                                     dconf_dbus_client_filter, dcdbc);
       dbus_connection_unref (dcdbc->session_bus);
       dbus_connection_unref (dcdbc->system_bus);
 
diff --git a/dbus-1/dconf-dbus-1.h b/dbus-1/dconf-dbus-1.h
index d137fc8..649f24d 100644
--- a/dbus-1/dconf-dbus-1.h
+++ b/dbus-1/dconf-dbus-1.h
@@ -25,9 +25,13 @@
 
 typedef struct _DConfDBusClient DConfDBusClient;
 
-DConfDBusClient *       dconf_dbus_client_new                           (const gchar    *profile,
-                                                                         DBusConnection *system,
-                                                                         DBusConnection *session);
+typedef void         (* DConfDBusNotify)                                (DConfDBusClient *dcdbc,
+                                                                         const gchar     *key,
+                                                                         gpointer         user_data);
+
+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);
 
@@ -37,9 +41,12 @@ gboolean                dconf_dbus_client_write                         (DConfDB
                                                                          const gchar     *key,
                                                                          GVariant        *value);
 void                    dconf_dbus_client_subscribe                     (DConfDBusClient *dcdbc,
-                                                                         const gchar     *name);
+                                                                         const gchar     *name,
+                                                                         DConfDBusNotify  notify,
+                                                                         gpointer         user_data);
 void                    dconf_dbus_client_unsubscribe                   (DConfDBusClient *dcdbc,
-                                                                         const gchar     *name);
+                                                                         DConfDBusNotify  notify,
+                                                                         gpointer         user_data);
 gboolean                dconf_dbus_client_has_pending                   (DConfDBusClient *dcdbc);
 
 #endif /* _dconf_dbus_1_h_ */
diff --git a/tests/dbus1.c b/tests/dbus1.c
index 497d26a..ea54182 100644
--- a/tests/dbus1.c
+++ b/tests/dbus1.c
@@ -54,7 +54,7 @@ do_write_tree (GTree *tree)
 static void
 do_sync (void)
 {
-  g_assert_not_reached ();
+/*  g_assert_not_reached (); */
 }
 
 #define RANDOM_ELEMENT(array) \
@@ -156,53 +156,22 @@ apply_change_tree (GHashTable *table,
 static GHashTable *implicit;
 static GHashTable *explicit;
 
-#if 0
-/* interpose */
-void
-g_settings_backend_changed (GSettingsBackend *backend_,
-                            const gchar      *key,
-                            gpointer          origin_tag)
+static void
+watch_func (DConfDBusClient *client,
+            const gchar     *key,
+            gpointer         user_data)
 {
   GVariant *value;
 
   /* ensure that we see no dupes from the bus */
-  g_assert (origin_tag == do_write);
-  g_assert (backend == backend_);
+/*  g_assert (origin_tag == do_write); */
+  g_assert (client == 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)
 {
@@ -218,7 +187,7 @@ setup (void)
   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, "/");
+  dconf_dbus_client_subscribe (backend, "/", watch_func, NULL);
 
   implicit = g_hash_table_new_full (g_str_hash, g_str_equal,
                                     g_free, free_variant);
@@ -271,8 +240,7 @@ verify_consistency (void)
   else
     g_print ("(%d)", g_hash_table_size (explicit));
 
-  /*g_assert (g_hash_table_size (explicit) == g_hash_table_size
-   * (implicit)); */
+  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))
     {
@@ -280,6 +248,12 @@ verify_consistency (void)
         {
           GVariant *other;
 
+          ghash_time -= g_get_monotonic_time ();
+          other = g_hash_table_lookup (implicit, key);
+          ghash_time += g_get_monotonic_time ();
+          g_assert (g_variant_equal (value, other));
+
+
           dconf_time -= g_get_monotonic_time ();
           other = do_read (key);
           dconf_time += g_get_monotonic_time ();



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