diff --git a/dbus/dbus-gproxy.c b/dbus/dbus-gproxy.c index 572b7fb..198d058 100644 --- a/dbus/dbus-gproxy.c +++ b/dbus/dbus-gproxy.c @@ -125,6 +125,12 @@ typedef struct { GSList *proxies; /**< The list of proxies */ + /** + * Set of signals with the DBusGProxy which subscribes to the signals + * gchar *signal_name -> GHashTable* of (DBusGProxy *proxy -> guint count) + */ + GHashTable *signal_subscribed; + char name[4]; /**< name (empty string for none), nul byte, * path, nul byte, * interface, nul byte @@ -147,8 +153,14 @@ struct _DBusGProxyManager GHashTable *proxy_lists; /**< Hash used to route incoming signals * and iterate over proxies + * tristring -> DBusGProxyList */ + GHashTable *owner_match_rule; /**< Hash to keep trace of match rules of + * NameOwnerChanged. + * gchar *name -> guint refcount + */ GHashTable *owner_names; /**< Hash to keep track of mapping from + * char * -> GSList of DBusGProxyNameOwnerInfo * base name -> [name,name,...] for proxies which * are for names. */ @@ -260,6 +272,16 @@ dbus_g_proxy_manager_unref (DBusGProxyManager *manager) } + if (manager->owner_match_rule) + { + /* Since we destroyed all proxies, none can be tracking + * name owners + */ + g_assert (g_hash_table_size (manager->owner_match_rule) == 0); + g_hash_table_destroy (manager->owner_match_rule); + manager->owner_match_rule = NULL; + } + if (manager->owner_names) { /* Since we destroyed all proxies, none can be tracking @@ -455,6 +477,7 @@ g_proxy_list_new (DBusGProxy *first_proxy) priv->path, priv->interface); list->proxies = NULL; + list->signal_subscribed = NULL; return list; } @@ -466,37 +489,51 @@ g_proxy_list_free (DBusGProxyList *list) * as they ref the GProxyManager */ g_slist_free (list->proxies); + if (list->signal_subscribed != NULL) + { + g_hash_table_destroy (list->signal_subscribed); + } g_free (list); } static char* -g_proxy_get_signal_match_rule (DBusGProxy *proxy) +g_proxy_get_signal_match_rule (DBusGProxy *proxy, const gchar *signal_name) { DBusGProxyPrivate *priv = DBUS_G_PROXY_GET_PRIVATE(proxy); /* FIXME Escaping is required here */ if (priv->name) - return g_strdup_printf ("type='signal',sender='%s',path='%s',interface='%s'", + { + if (signal_name == NULL) + return g_strdup_printf ("type='signal',sender='%s',path='%s',interface='%s'", priv->name, priv->path, priv->interface); + else + return g_strdup_printf ("type='signal',sender='%s',path='%s'," + "interface='%s',member='%s'", + priv->name, priv->path, priv->interface, + signal_name); + } else - return g_strdup_printf ("type='signal',path='%s',interface='%s'", - priv->path, priv->interface); + { + if (signal_name == NULL) + return g_strdup_printf ("type='signal',path='%s',interface='%s'", + priv->path, priv->interface); + else + return g_strdup_printf ("type='signal',path='%s',interface='%s'," + "member='%s'", + priv->path, priv->interface, signal_name); + } } static char * -g_proxy_get_owner_match_rule (DBusGProxy *proxy) +g_proxy_get_owner_match_rule (const gchar *name) { - DBusGProxyPrivate *priv = DBUS_G_PROXY_GET_PRIVATE(proxy); - if (priv->name) - { - return g_strdup_printf ("type='signal',sender='" DBUS_SERVICE_DBUS - "',path='" DBUS_PATH_DBUS - "',interface='" DBUS_INTERFACE_DBUS - "',member='NameOwnerChanged'" - ",arg0='%s'", priv->name); - } - return NULL; + return g_strdup_printf ("type='signal',sender='" DBUS_SERVICE_DBUS + "',path='" DBUS_PATH_DBUS + "',interface='" DBUS_INTERFACE_DBUS + "',member='NameOwnerChanged'" + ",arg0='%s'", name); } typedef struct @@ -922,6 +959,7 @@ dbus_g_proxy_manager_register (DBusGProxyManager *manager, if (manager->proxy_lists == NULL) { g_assert (manager->owner_names == NULL); + g_assert (manager->owner_match_rule == NULL); list = NULL; manager->proxy_lists = g_hash_table_new_full (tristring_hash, @@ -932,6 +970,10 @@ dbus_g_proxy_manager_register (DBusGProxyManager *manager, g_str_equal, g_free, NULL); + manager->owner_match_rule = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); } else { @@ -954,25 +996,32 @@ dbus_g_proxy_manager_register (DBusGProxyManager *manager, if (list->proxies == NULL && priv->name) { - /* We have to add match rules to the server, - * but only if the server is a message bus, - * not if it's a peer. - */ - char *rule; - - rule = g_proxy_get_signal_match_rule (proxy); - /* We don't check for errors; it's not like anyone would handle them, and - * we don't want a round trip here. - */ - dbus_bus_add_match (manager->connection, - rule, NULL); - g_free (rule); + gpointer orig_key, value; + + g_assert (list->signal_subscribed == NULL); + list->signal_subscribed = g_hash_table_new_full (g_str_hash, + g_str_equal, g_free, (GDestroyNotify)g_hash_table_destroy); + - rule = g_proxy_get_owner_match_rule (proxy); - if (rule) - dbus_bus_add_match (manager->connection, - rule, NULL); - g_free (rule); + if (g_hash_table_lookup_extended (manager->owner_match_rule, + priv->name, &orig_key, &value)) + { + /* The owner match rule is already here. Increment the refcount */ + gint count = GPOINTER_TO_INT (value) + 1; + g_hash_table_steal (manager->owner_match_rule, orig_key); + g_hash_table_insert (manager->owner_match_rule, orig_key, + GINT_TO_POINTER (count)); + } + else + { + char *rule; + rule = g_proxy_get_owner_match_rule (priv->name); + dbus_bus_add_match (manager->connection, + rule, NULL); + g_free (rule); + g_hash_table_insert (manager->owner_match_rule, g_strdup (priv->name), + GINT_TO_POINTER (1)); + } } g_assert (g_slist_find (list->proxies, proxy) == NULL); @@ -1068,22 +1117,55 @@ dbus_g_proxy_manager_unregister (DBusGProxyManager *manager, } } + if (list->signal_subscribed != NULL) + { + GHashTableIter iter; + gpointer proxy_set; + gpointer signal_name; + + g_hash_table_iter_init (&iter, list->signal_subscribed); + while (g_hash_table_iter_next (&iter, &signal_name, &proxy_set)) + { + g_hash_table_remove (proxy_set, proxy); + if (g_hash_table_size (proxy_set) == 0) + { + char *rule; + + rule = g_proxy_get_signal_match_rule (proxy, (gchar *) signal_name); + dbus_bus_remove_match (priv->manager->connection, + rule, NULL); + g_free (rule); + } + } + } + if (list->proxies == NULL) { - char *rule; + gpointer orig_key, value; + g_hash_table_remove (manager->proxy_lists, tri); - list = NULL; - rule = g_proxy_get_signal_match_rule (proxy); - dbus_bus_remove_match (manager->connection, - rule, NULL); - g_free (rule); - rule = g_proxy_get_owner_match_rule (proxy); - if (rule) - dbus_bus_remove_match (manager->connection, - rule, NULL); - g_free (rule); + if (priv->name && g_hash_table_lookup_extended ( + manager->owner_match_rule, priv->name, &orig_key, &value)) + { + gint count = GPOINTER_TO_INT (value) - 1; + if (count == 0) + { + char *rule; + rule = g_proxy_get_owner_match_rule (priv->name); + dbus_bus_remove_match (manager->connection, + rule, NULL); + g_free (rule); + g_hash_table_remove (manager->owner_match_rule, priv->name); + } + else + { + g_hash_table_steal (manager->owner_match_rule, orig_key); + g_hash_table_insert (manager->owner_match_rule, orig_key, + GINT_TO_POINTER (count)); + } + } } if (g_hash_table_size (manager->proxy_lists) == 0) @@ -1092,6 +1174,12 @@ dbus_g_proxy_manager_unregister (DBusGProxyManager *manager, manager->proxy_lists = NULL; } + if (g_hash_table_size (manager->owner_match_rule) == 0) + { + g_hash_table_destroy (manager->owner_match_rule); + manager->owner_match_rule = NULL; + } + g_free (tri); UNLOCK_MANAGER (manager); @@ -2887,6 +2975,8 @@ dbus_g_proxy_connect_signal (DBusGProxy *proxy, GClosure *closure; GQuark q; DBusGProxyPrivate *priv; + char *tri; + DBusGProxyList *list; g_return_if_fail (DBUS_IS_G_PROXY (proxy)); g_return_if_fail (!DBUS_G_PROXY_DESTROYED (proxy)); @@ -2894,6 +2984,57 @@ dbus_g_proxy_connect_signal (DBusGProxy *proxy, g_return_if_fail (handler != NULL); priv = DBUS_G_PROXY_GET_PRIVATE(proxy); + + tri = tristring_from_proxy (proxy); + list = g_hash_table_lookup (priv->manager->proxy_lists, tri); + g_free (tri); + /* We have to add match rules to the server, + * but only if the server is a message bus, + * not if it's a peer. + */ + if (priv->name && list->signal_subscribed != NULL) + { + gchar *orig_key; + GHashTable *proxy_set; + gboolean found = g_hash_table_lookup_extended (list->signal_subscribed, + signal_name, (gpointer *) &orig_key, (gpointer *) &proxy_set); + gint handler_count; + gpointer value; + + if (!found || g_hash_table_size (proxy_set) == 0) + { + char *rule; + + rule = g_proxy_get_signal_match_rule (proxy, signal_name); + /* We don't check for errors; it's not like anyone would handle + * them, and we don't want a round trip here. + */ + dbus_bus_add_match (priv->manager->connection, + rule, NULL); + g_free (rule); + } + + if (!found) + { + proxy_set = g_hash_table_new (NULL, NULL); + orig_key = g_strdup (signal_name); + } + + if (g_hash_table_lookup_extended (proxy_set, proxy, NULL, + &value)) + { + handler_count = GPOINTER_TO_UINT (value) + 1; + } + else + { + handler_count = 1; + } + g_hash_table_insert (proxy_set, proxy, GINT_TO_POINTER (handler_count)); + + g_hash_table_steal (list->signal_subscribed, signal_name); + g_hash_table_insert (list->signal_subscribed, orig_key, proxy_set); + } + name = create_signal_name (priv->interface, signal_name); q = g_quark_try_string (name); @@ -2933,9 +3074,14 @@ dbus_g_proxy_disconnect_signal (DBusGProxy *proxy, GCallback handler, void *data) { + guint matched_count; + gint handler_count; char *name; GQuark q; DBusGProxyPrivate *priv; + char *tri; + DBusGProxyList *list; + gchar *_signal_name; g_return_if_fail (DBUS_IS_G_PROXY (proxy)); g_return_if_fail (!DBUS_G_PROXY_DESTROYED (proxy)); @@ -2946,25 +3092,72 @@ dbus_g_proxy_disconnect_signal (DBusGProxy *proxy, name = create_signal_name (priv->interface, signal_name); q = g_quark_try_string (name); + g_free (name); - if (q != 0) - { - g_signal_handlers_disconnect_matched (G_OBJECT (proxy), - G_SIGNAL_MATCH_DETAIL | - G_SIGNAL_MATCH_FUNC | - G_SIGNAL_MATCH_DATA, - signals[RECEIVED], - q, - NULL, - G_CALLBACK (handler), data); - } - else + if (q == 0) { g_warning ("Attempt to disconnect from signal '%s' which is not registered\n", - name); + signal_name); + return; } - g_free (name); + /* signal_name may be freed by g_signal_handlers_disconnect_matched() :'( + * Keep a copy while we need it + */ + _signal_name = g_strdup (signal_name); + matched_count = g_signal_handlers_disconnect_matched (G_OBJECT (proxy), + G_SIGNAL_MATCH_DETAIL | G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, + signals[RECEIVED], q, NULL, G_CALLBACK (handler), data); + + tri = tristring_from_proxy (proxy); + list = g_hash_table_lookup (priv->manager->proxy_lists, tri); + g_free (tri); + + /* Remove a match rule */ + if (list->signal_subscribed != NULL) + { + GHashTable *proxy_set = g_hash_table_lookup (list->signal_subscribed, + _signal_name); + gpointer value; + + if (proxy_set == NULL || !g_hash_table_lookup_extended (proxy_set, proxy, + NULL, &value)) + { + g_warning ("Attempt to disconnect from signal '%s' which is not" + " registered\n", _signal_name); + g_free (_signal_name); + return; + } + + handler_count = GPOINTER_TO_INT (value); + handler_count -= matched_count; + g_assert (handler_count >= 0); + if (handler_count == 0) + { + g_hash_table_remove (proxy_set, proxy); + } + else + { + g_hash_table_insert (proxy_set, proxy, + GINT_TO_POINTER (handler_count)); + } + + if (g_hash_table_size (proxy_set) == 0) + { + char *rule; + + rule = g_proxy_get_signal_match_rule (proxy, _signal_name); + /* We don't check for errors; it's not like anyone would handle them, + * and we don't want a round trip here. + */ + dbus_bus_remove_match (priv->manager->connection, + rule, NULL); + g_free (rule); + + g_hash_table_remove (list->signal_subscribed, _signal_name); + } + } + g_free (_signal_name); } /**