diff options
Diffstat (limited to 'src/bus-watch-namespace.c')
-rw-r--r-- | src/bus-watch-namespace.c | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/src/bus-watch-namespace.c b/src/bus-watch-namespace.c new file mode 100644 index 0000000..f20905c --- /dev/null +++ b/src/bus-watch-namespace.c @@ -0,0 +1,349 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program 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 + * License, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#include <gio/gio.h> +#include <string.h> +#include "bus-watch-namespace.h" + +typedef struct +{ + guint id; + gchar *name_space; + GBusNameAppearedCallback appeared_handler; + GBusNameVanishedCallback vanished_handler; + gpointer user_data; + GDestroyNotify user_data_destroy; + + GDBusConnection *connection; + GCancellable *cancellable; + GHashTable *names; + guint subscription_id; +} NamespaceWatcher; + +typedef struct +{ + NamespaceWatcher *watcher; + gchar *name; +} GetNameOwnerData; + +static guint namespace_watcher_next_id; +static GHashTable *namespace_watcher_watchers; + +static void +namespace_watcher_stop (gpointer data) +{ + NamespaceWatcher *watcher = data; + + g_cancellable_cancel (watcher->cancellable); + g_object_unref (watcher->cancellable); + + if (watcher->subscription_id) + g_dbus_connection_signal_unsubscribe (watcher->connection, watcher->subscription_id); + + if (watcher->vanished_handler) + { + GHashTableIter it; + const gchar *name; + + g_hash_table_iter_init (&it, watcher->names); + while (g_hash_table_iter_next (&it, (gpointer *) &name, NULL)) + watcher->vanished_handler (watcher->connection, name, watcher->user_data); + } + + if (watcher->user_data_destroy) + watcher->user_data_destroy (watcher->user_data); + + if (watcher->connection) + { + g_signal_handlers_disconnect_by_func (watcher->connection, namespace_watcher_stop, watcher); + g_object_unref (watcher->connection); + } + + g_hash_table_unref (watcher->names); + + g_hash_table_remove (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id)); + if (g_hash_table_size (namespace_watcher_watchers) == 0) + g_clear_pointer (&namespace_watcher_watchers, g_hash_table_destroy); + + g_free (watcher->name_space); + + g_free (watcher); +} + +static void +namespace_watcher_name_appeared (NamespaceWatcher *watcher, + const gchar *name, + const gchar *owner) +{ + /* There's a race between NameOwnerChanged signals arriving and the + * ListNames/GetNameOwner sequence returning, so this function might + * be called more than once for the same name. To ensure that + * appeared_handler is only called once for each name, it is only + * called when inserting the name into watcher->names (each name is + * only inserted once there). + */ + if (g_hash_table_contains (watcher->names, name)) + return; + + g_hash_table_add (watcher->names, g_strdup (name)); + + if (watcher->appeared_handler) + watcher->appeared_handler (watcher->connection, name, owner, watcher->user_data); +} + +static void +namespace_watcher_name_vanished (NamespaceWatcher *watcher, + const gchar *name) +{ + if (g_hash_table_remove (watcher->names, name) && watcher->vanished_handler) + watcher->vanished_handler (watcher->connection, name, watcher->user_data); +} + +static gboolean +dbus_name_has_namespace (const gchar *name, + const gchar *name_space) +{ + gint len_name; + gint len_namespace; + + len_name = strlen (name); + len_namespace = strlen (name_space); + + if (len_name < len_namespace) + return FALSE; + + if (memcmp (name_space, name, len_namespace) != 0) + return FALSE; + + return len_namespace == len_name || name[len_namespace] == '.'; +} + +static void +name_owner_changed (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + NamespaceWatcher *watcher = user_data; + const gchar *name; + const gchar *old_owner; + const gchar *new_owner; + + g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner); + + if (old_owner[0] != '\0') + namespace_watcher_name_vanished (watcher, name); + + if (new_owner[0] != '\0') + namespace_watcher_name_appeared (watcher, name, new_owner); +} + +static void +got_name_owner (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GetNameOwnerData *data = user_data; + GError *error = NULL; + GVariant *reply; + const gchar *owner; + + reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + goto out; + } + + if (reply == NULL) + { + if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER)) + g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.GetNameOwner: %s", error->message); + g_error_free (error); + goto out; + } + + g_variant_get (reply, "(&s)", &owner); + namespace_watcher_name_appeared (data->watcher, data->name, owner); + + g_variant_unref (reply); + +out: + g_free (data->name); + g_slice_free (GetNameOwnerData, data); +} + +static void +names_listed (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + NamespaceWatcher *watcher; + GError *error = NULL; + GVariant *reply; + GVariantIter *iter; + const gchar *name; + + reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + return; + } + + watcher = user_data; + + if (reply == NULL) + { + g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.ListNames: %s", error->message); + g_error_free (error); + return; + } + + g_variant_get (reply, "(as)", &iter); + while (g_variant_iter_next (iter, "&s", &name)) + { + if (dbus_name_has_namespace (name, watcher->name_space)) + { + GetNameOwnerData *data = g_slice_new (GetNameOwnerData); + data->watcher = watcher; + data->name = g_strdup (name); + g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/", + "org.freedesktop.DBus", "GetNameOwner", + g_variant_new ("(s)", name), G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable, + got_name_owner, data); + } + } + + g_variant_iter_free (iter); + g_variant_unref (reply); +} + +static void +connection_closed (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data) +{ + NamespaceWatcher *watcher = user_data; + + namespace_watcher_stop (watcher); +} + +static void +got_bus (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection *connection; + NamespaceWatcher *watcher; + GError *error = NULL; + + connection = g_bus_get_finish (result, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + return; + } + + watcher = user_data; + + if (connection == NULL) + { + namespace_watcher_stop (watcher); + return; + } + + watcher->connection = connection; + g_signal_connect (watcher->connection, "closed", G_CALLBACK (connection_closed), watcher); + + watcher->subscription_id = + g_dbus_connection_signal_subscribe (watcher->connection, "org.freedesktop.DBus", + "org.freedesktop.DBus", "NameOwnerChanged", "/org/freedesktop/DBus", + watcher->name_space, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, + name_owner_changed, watcher, NULL); + + g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/", + "org.freedesktop.DBus", "ListNames", NULL, G_VARIANT_TYPE ("(as)"), + G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable, + names_listed, watcher); +} + +guint +bus_watch_namespace (GBusType bus_type, + const gchar *name_space, + GBusNameAppearedCallback appeared_handler, + GBusNameVanishedCallback vanished_handler, + gpointer user_data, + GDestroyNotify user_data_destroy) +{ + NamespaceWatcher *watcher; + + /* same rules for interfaces and well-known names */ + g_return_val_if_fail (name_space != NULL && g_dbus_is_interface_name (name_space), 0); + g_return_val_if_fail (appeared_handler || vanished_handler, 0); + + watcher = g_new0 (NamespaceWatcher, 1); + watcher->id = namespace_watcher_next_id++; + watcher->name_space = g_strdup (name_space); + watcher->appeared_handler = appeared_handler; + watcher->vanished_handler = vanished_handler; + watcher->user_data = user_data; + watcher->user_data_destroy = user_data_destroy; + watcher->cancellable = g_cancellable_new (); + watcher->names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + if (namespace_watcher_watchers == NULL) + namespace_watcher_watchers = g_hash_table_new (g_direct_hash, g_direct_equal); + g_hash_table_insert (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id), watcher); + + g_bus_get (bus_type, watcher->cancellable, got_bus, watcher); + + return watcher->id; +} + +void +bus_unwatch_namespace (guint id) +{ + /* namespace_watcher_stop() might have already removed the watcher + * with @id in the case of a connection error. Thus, this function + * doesn't warn when @id is absent from the hash table. + */ + + if (namespace_watcher_watchers) + { + NamespaceWatcher *watcher; + + watcher = g_hash_table_lookup (namespace_watcher_watchers, GUINT_TO_POINTER (id)); + if (watcher) + { + /* make sure vanished() is not called as a result of this function */ + g_hash_table_remove_all (watcher->names); + + namespace_watcher_stop (watcher); + } + } +} |