diff options
author | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2009-08-17 13:36:49 +0100 |
---|---|---|
committer | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2009-08-17 16:10:59 +0100 |
commit | eced62a10faadc3e0c4dbf41a040a852c023e156 (patch) | |
tree | 53504984cc15a903588dac2d42faafa334fb9ab7 /telepathy-glib/dbus-daemon.c | |
parent | 16ae7db13a12c3da955748d1a56a945c19eb7147 (diff) | |
download | telepathy-glib-eced62a10faadc3e0c4dbf41a040a852c023e156.tar.gz |
Separate dbus.c into dbus.c and dbus-daemon.c
No code changes, except renaming starter_bus_conn to
_tp_dbus_starter_bus_conn and making it visible within telepathy-glib
(both of the split modules need it, now).
Diffstat (limited to 'telepathy-glib/dbus-daemon.c')
-rw-r--r-- | telepathy-glib/dbus-daemon.c | 1098 |
1 files changed, 1098 insertions, 0 deletions
diff --git a/telepathy-glib/dbus-daemon.c b/telepathy-glib/dbus-daemon.c new file mode 100644 index 000000000..75525d6a8 --- /dev/null +++ b/telepathy-glib/dbus-daemon.c @@ -0,0 +1,1098 @@ +/* + * dbus-daemon.c - Source for TpDBusDaemon + * + * Copyright (C) 2005-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright (C) 2005-2009 Nokia Corporation + * + * 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.1 of the License, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <telepathy-glib/dbus.h> +#include <telepathy-glib/dbus-internal.h> + +#include <dbus/dbus.h> +#include <dbus/dbus-glib-lowlevel.h> + +#include <telepathy-glib/errors.h> +#include <telepathy-glib/interfaces.h> +#include <telepathy-glib/proxy-subclass.h> +#include <telepathy-glib/util.h> + +#include "telepathy-glib/_gen/signals-marshal.h" + +#include "telepathy-glib/_gen/tp-cli-dbus-daemon-body.h" + +#define DEBUG_FLAG TP_DEBUG_PROXY +#include "debug-internal.h" + +/** + * TpDBusDaemonClass: + * + * The class of #TpDBusDaemon. + * + * Since: 0.7.1 + */ +struct _TpDBusDaemonClass +{ + /*<private>*/ + TpProxyClass parent_class; + gpointer priv; +}; + +/** + * TpDBusDaemon: + * + * A subclass of #TpProxy that represents the D-Bus daemon. It mainly provides + * functionality to manage well-known names on the bus. + * + * Since: 0.7.1 + */ +struct _TpDBusDaemon +{ + /*<private>*/ + TpProxy parent; + + TpDBusDaemonPrivate *priv; +}; + +struct _TpDBusDaemonPrivate +{ + /* dup'd name => _NameOwnerWatch */ + GHashTable *name_owner_watches; + /* reffed */ + DBusConnection *libdbus; +}; + +G_DEFINE_TYPE (TpDBusDaemon, tp_dbus_daemon, TP_TYPE_PROXY); + +static gpointer starter_bus_daemon = NULL; + +/** + * tp_dbus_daemon_dup: + * @error: Used to indicate error if %NULL is returned + * + * Returns a proxy for signals and method calls on the D-Bus daemon on which + * this process was activated (if it was launched by D-Bus service + * activation), or the session bus (otherwise). + * + * If it is not possible to connect to the appropriate bus, raise an error + * and return %NULL. + * + * The returned #TpDBusDaemon is cached; the same #TpDBusDaemon object will + * be returned by this function repeatedly, as long as at least one reference + * exists. + * + * Returns: a reference to a proxy for signals and method calls on the bus + * daemon, or %NULL + * + * Since: 0.7.26 + */ +TpDBusDaemon * +tp_dbus_daemon_dup (GError **error) +{ + DBusGConnection *conn; + + if (starter_bus_daemon != NULL) + return g_object_ref (starter_bus_daemon); + + conn = _tp_dbus_starter_bus_conn (error); + + if (conn == NULL) + return NULL; + + starter_bus_daemon = tp_dbus_daemon_new (conn); + g_assert (starter_bus_daemon != NULL); + g_object_add_weak_pointer (starter_bus_daemon, &starter_bus_daemon); + + return starter_bus_daemon; +} + +/** + * tp_dbus_daemon_new: + * @connection: a connection to D-Bus + * + * Returns a proxy for signals and method calls on a particular bus + * connection. + * + * Use tp_dbus_daemon_dup() instead if you just want a connection to the + * starter or session bus (which is almost always the right thing for + * Telepathy). + * + * Returns: a new proxy for signals and method calls on the bus daemon + * to which @connection is connected + * + * Since: 0.7.1 + */ +TpDBusDaemon * +tp_dbus_daemon_new (DBusGConnection *connection) +{ + g_return_val_if_fail (connection != NULL, NULL); + + return TP_DBUS_DAEMON (g_object_new (TP_TYPE_DBUS_DAEMON, + "dbus-connection", connection, + "bus-name", DBUS_SERVICE_DBUS, + "object-path", DBUS_PATH_DBUS, + NULL)); +} + +typedef struct +{ + TpDBusDaemonNameOwnerChangedCb callback; + gpointer user_data; + GDestroyNotify destroy; + gchar *last_owner; +} _NameOwnerWatch; + +typedef struct +{ + TpDBusDaemonNameOwnerChangedCb callback; + gpointer user_data; + GDestroyNotify destroy; +} _NameOwnerSubWatch; + +static void +_tp_dbus_daemon_name_owner_changed_multiple (TpDBusDaemon *self, + const gchar *name, + const gchar *new_owner, + gpointer user_data) +{ + GArray *array = user_data; + guint i; + + for (i = 0; i < array->len; i++) + { + _NameOwnerSubWatch *watch = &g_array_index (array, _NameOwnerSubWatch, + i); + + watch->callback (self, name, new_owner, watch->user_data); + } +} + +static void +_tp_dbus_daemon_name_owner_changed_multiple_free (gpointer data) +{ + GArray *array = data; + guint i; + + for (i = 0; i < array->len; i++) + { + _NameOwnerSubWatch *watch = &g_array_index (array, _NameOwnerSubWatch, + i); + + if (watch->destroy) + watch->destroy (watch->user_data); + } + + g_array_free (array, TRUE); +} + +static void +_tp_dbus_daemon_name_owner_changed (TpDBusDaemon *self, + const gchar *name, + const gchar *new_owner) +{ + _NameOwnerWatch *watch = g_hash_table_lookup (self->priv->name_owner_watches, + name); + + DEBUG ("%s -> %s", name, new_owner); + + if (watch == NULL) + return; + + /* This is partly to handle the case where an owner change happens + * while GetNameOwner is in flight, partly to be able to optimize by only + * calling GetNameOwner if we didn't already know, and partly because of a + * dbus-glib bug that means we get every signal twice + * (it thinks org.freedesktop.DBus is both a well-known name and a unique + * name). */ + if (!tp_strdiff (watch->last_owner, new_owner)) + return; + + g_free (watch->last_owner); + watch->last_owner = g_strdup (new_owner); + + watch->callback (self, name, new_owner, watch->user_data); +} + +static dbus_int32_t daemons_slot = -1; + +typedef struct { + DBusConnection *libdbus; + DBusMessage *message; +} NOCIdleContext; + +static NOCIdleContext * +noc_idle_context_new (DBusConnection *libdbus, + DBusMessage *message) +{ + NOCIdleContext *context = g_slice_new (NOCIdleContext); + + context->libdbus = dbus_connection_ref (libdbus); + context->message = dbus_message_ref (message); + return context; +} + +static void +noc_idle_context_free (gpointer data) +{ + NOCIdleContext *context = data; + + dbus_connection_unref (context->libdbus); + dbus_message_unref (context->message); + g_slice_free (NOCIdleContext, context); +} + +static gboolean +noc_idle_context_invoke (gpointer data) +{ + NOCIdleContext *context = data; + const gchar *name; + const gchar *old_owner; + const gchar *new_owner; + DBusError dbus_error = DBUS_ERROR_INIT; + GSList **daemons; + + if (daemons_slot == -1) + return FALSE; + + if (!dbus_message_get_args (context->message, &dbus_error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID)) + { + DEBUG ("Couldn't unpack NameOwnerChanged(s, s, s): %s: %s", + dbus_error.name, dbus_error.message); + dbus_error_free (&dbus_error); + return FALSE; + } + + daemons = dbus_connection_get_data (context->libdbus, daemons_slot); + + DEBUG ("NameOwnerChanged(%s, %s -> %s)", name, old_owner, new_owner); + + /* should always be non-NULL, barring bugs */ + if (G_LIKELY (daemons != NULL)) + { + GSList *iter; + + for (iter = *daemons; iter != NULL; iter = iter->next) + _tp_dbus_daemon_name_owner_changed (iter->data, name, new_owner); + } + + return FALSE; +} + +static DBusHandlerResult +_tp_dbus_daemon_name_owner_changed_filter (DBusConnection *libdbus, + DBusMessage *message, + void *unused G_GNUC_UNUSED) +{ + /* We have to do the real work in an idle, so we don't break re-entrant + * calls (the dbus-glib event source isn't re-entrant) */ + if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, + "NameOwnerChanged") && + dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + g_idle_add_full (G_PRIORITY_HIGH, noc_idle_context_invoke, + noc_idle_context_new (libdbus, message), + noc_idle_context_free); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +typedef struct { + TpDBusDaemon *self; + gchar *name; + DBusMessage *reply; + gsize refs; +} GetNameOwnerContext; + +static GetNameOwnerContext * +get_name_owner_context_new (TpDBusDaemon *self, + const gchar *name) +{ + GetNameOwnerContext *context = g_slice_new (GetNameOwnerContext); + + context->self = g_object_ref (self); + context->name = g_strdup (name); + context->reply = NULL; + DEBUG ("New, 1 ref"); + context->refs = 1; + return context; +} + +static void +get_name_owner_context_unref (gpointer data) +{ + GetNameOwnerContext *context = data; + + DEBUG ("%lu -> %lu", (gulong) context->refs, (gulong) (context->refs-1)); + + if (--context->refs == 0) + { + g_object_unref (context->self); + g_free (context->name); + + if (context->reply != NULL) + dbus_message_unref (context->reply); + + g_slice_free (GetNameOwnerContext, context); + } +} + +static gboolean +_tp_dbus_daemon_get_name_owner_idle (gpointer data) +{ + GetNameOwnerContext *context = data; + const gchar *owner = ""; + + if (context->reply == NULL) + { + DEBUG ("Connection disconnected or no reply to GetNameOwner(%s)", + context->name); + } + else if (dbus_message_get_type (context->reply) == + DBUS_MESSAGE_TYPE_METHOD_RETURN) + { + if (!dbus_message_get_args (context->reply, NULL, + DBUS_TYPE_STRING, &owner, + DBUS_TYPE_INVALID)) + { + DEBUG ("Malformed reply from GetNameOwner(%s), assuming no owner", + context->name); + } + } + else + { + if (DEBUGGING) + { + DBusError error = DBUS_ERROR_INIT; + + if (dbus_set_error_from_message (&error, context->reply)) + { + DEBUG ("GetNameOwner(%s) raised %s: %s", context->name, + error.name, error.message); + dbus_error_free (&error); + } + else + { + DEBUG ("Unexpected message type from GetNameOwner(%s)", + context->name); + } + } + } + + _tp_dbus_daemon_name_owner_changed (context->self, context->name, owner); + + return FALSE; +} + +/** + * TpDBusDaemonNameOwnerChangedCb: + * @bus_daemon: The D-Bus daemon + * @name: The name whose ownership has changed or been discovered + * @new_owner: The unique name that now owns @name + * @user_data: Arbitrary user-supplied data as passed to + * tp_dbus_daemon_watch_name_owner() + * + * The signature of the callback called by tp_dbus_daemon_watch_name_owner(). + * + * Since: 0.7.1 + */ + +static inline gchar * +_tp_dbus_daemon_get_noc_rule (const gchar *name) +{ + return g_strdup_printf ("type='signal'," + "sender='" DBUS_SERVICE_DBUS "'," + "path='" DBUS_PATH_DBUS "'," + "interface='"DBUS_INTERFACE_DBUS "'," + "member='NameOwnerChanged'," + "arg0='%s'", name); +} + +static void +_tp_dbus_daemon_get_name_owner_notify (DBusPendingCall *pc, + gpointer data) +{ + GetNameOwnerContext *context = data; + + /* we recycle this function for the case where the connection is already + * disconnected: in that case we use pc = NULL */ + if (pc != NULL) + context->reply = dbus_pending_call_steal_reply (pc); + + /* We have to do the real work in an idle, so we don't break re-entrant + * calls (the dbus-glib event source isn't re-entrant) */ + DEBUG ("%lu -> %lu", (gulong) context->refs, (gulong) (context->refs + 1)); + context->refs++; + g_idle_add_full (G_PRIORITY_HIGH, _tp_dbus_daemon_get_name_owner_idle, + context, get_name_owner_context_unref); + + if (pc != NULL) + dbus_pending_call_unref (pc); +} + +/** + * tp_dbus_daemon_watch_name_owner: + * @self: The D-Bus daemon + * @name: The name whose ownership is to be watched + * @callback: Callback to call when the ownership is discovered or changes + * @user_data: Arbitrary data to pass to @callback + * @destroy: Called to destroy @user_data when the name owner watch is + * cancelled due to tp_dbus_daemon_cancel_name_owner_watch() + * + * Arrange for @callback to be called with the owner of @name as soon as + * possible (which might even be before this function returns!), then + * again every time the ownership of @name changes. + * + * If multiple watches are registered for the same @name, they will be called + * in the order they were registered. + * + * Since: 0.7.1 + */ +void +tp_dbus_daemon_watch_name_owner (TpDBusDaemon *self, + const gchar *name, + TpDBusDaemonNameOwnerChangedCb callback, + gpointer user_data, + GDestroyNotify destroy) +{ + _NameOwnerWatch *watch = g_hash_table_lookup (self->priv->name_owner_watches, + name); + + g_return_if_fail (TP_IS_DBUS_DAEMON (self)); + g_return_if_fail (tp_dbus_check_valid_bus_name (name, + TP_DBUS_NAME_TYPE_ANY, NULL)); + g_return_if_fail (name != NULL); + g_return_if_fail (callback != NULL); + + if (watch == NULL) + { + gchar *match_rule; + DBusMessage *message; + DBusPendingCall *pc = NULL; + GetNameOwnerContext *context = get_name_owner_context_new (self, name); + + /* Allocate a single watch (common case) */ + watch = g_slice_new (_NameOwnerWatch); + watch->callback = callback; + watch->user_data = user_data; + watch->destroy = destroy; + watch->last_owner = NULL; + + g_hash_table_insert (self->priv->name_owner_watches, g_strdup (name), + watch); + + /* We want to be notified about name owner changes for this one. + * Assume the match addition will succeed; there's no good way to cope + * with failure here... */ + match_rule = _tp_dbus_daemon_get_noc_rule (name); + DEBUG ("Adding match rule %s", match_rule); + dbus_bus_add_match (self->priv->libdbus, match_rule, NULL); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner"); + + if (message == NULL) + g_error ("Out of memory"); + + /* We already checked that @name was in (a small subset of) UTF-8, + * so OOM is the only thing that can go wrong. The use of &name here + * is because libdbus is strange. */ + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + g_error ("Out of memory"); + + if (!dbus_connection_send_with_reply (self->priv->libdbus, + message, &pc, -1)) + g_error ("Out of memory"); + /* pc is unreffed by _tp_dbus_daemon_get_name_owner_notify */ + + if (pc == NULL || dbus_pending_call_get_completed (pc)) + { + /* pc can be NULL when the connection is already disconnected */ + _tp_dbus_daemon_get_name_owner_notify (pc, context); + get_name_owner_context_unref (context); + } + else if (!dbus_pending_call_set_notify (pc, + _tp_dbus_daemon_get_name_owner_notify, + context, get_name_owner_context_unref)) + { + g_error ("Out of memory"); + } + } + else + { + _NameOwnerSubWatch tmp = { callback, user_data, destroy }; + + if (watch->callback == _tp_dbus_daemon_name_owner_changed_multiple) + { + /* The watch is already a "multiplexer", just append to it */ + GArray *array = watch->user_data; + + g_array_append_val (array, tmp); + } + else + { + /* Replace the old contents of the watch with one that dispatches + * the signal to (potentially) more than one watcher */ + GArray *array = g_array_sized_new (FALSE, FALSE, + sizeof (_NameOwnerSubWatch), 2); + + /* The new watcher */ + g_array_append_val (array, tmp); + /* The old watcher */ + tmp.callback = watch->callback; + tmp.user_data = watch->user_data; + tmp.destroy = watch->destroy; + g_array_prepend_val (array, tmp); + + watch->callback = _tp_dbus_daemon_name_owner_changed_multiple; + watch->user_data = array; + watch->destroy = _tp_dbus_daemon_name_owner_changed_multiple_free; + } + + if (watch->last_owner != NULL) + { + /* FIXME: should avoid reentrancy? */ + callback (self, name, watch->last_owner, user_data); + } + } +} + +static void +_tp_dbus_daemon_stop_watching (TpDBusDaemon *self, + const gchar *name, + _NameOwnerWatch *watch) +{ + gchar *match_rule; + + if (watch->destroy) + watch->destroy (watch->user_data); + + g_free (watch->last_owner); + g_slice_free (_NameOwnerWatch, watch); + + match_rule = _tp_dbus_daemon_get_noc_rule (name); + DEBUG ("Removing match rule %s", match_rule); + dbus_bus_remove_match (self->priv->libdbus, match_rule, NULL); + g_free (match_rule); +} + +/** + * tp_dbus_daemon_cancel_name_owner_watch: + * @self: the D-Bus daemon + * @name: the name that was being watched + * @callback: the callback that was called + * @user_data: the user data that was provided + * + * If there was a previous call to tp_dbus_daemon_watch_name_owner() + * with exactly the given @name, @callback and @user_data, remove it. + * + * If more than one watch matching the details provided was active, remove + * only the most recently added one. + * + * Returns: %TRUE if there was such a watch, %FALSE otherwise + * + * Since: 0.7.1 + */ +gboolean +tp_dbus_daemon_cancel_name_owner_watch (TpDBusDaemon *self, + const gchar *name, + TpDBusDaemonNameOwnerChangedCb callback, + gconstpointer user_data) +{ + _NameOwnerWatch *watch = g_hash_table_lookup (self->priv->name_owner_watches, + name); + + g_return_val_if_fail (TP_IS_DBUS_DAEMON (self), FALSE); + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (callback != NULL, FALSE); + + if (watch == NULL) + { + /* No watch at all */ + return FALSE; + } + else if (watch->callback == callback && watch->user_data == user_data) + { + /* Simple case: there is one name-owner watch and it's what we wanted */ + _tp_dbus_daemon_stop_watching (self, name, watch); + g_hash_table_remove (self->priv->name_owner_watches, name); + return TRUE; + } + else if (watch->callback == _tp_dbus_daemon_name_owner_changed_multiple) + { + /* Complicated case: this watch is a "multiplexer", we need to check + * its contents */ + GArray *array = watch->user_data; + guint i; + + for (i = 1; i <= array->len; i++) + { + _NameOwnerSubWatch *entry = &g_array_index (array, + _NameOwnerSubWatch, array->len - i); + + if (entry->callback == callback && entry->user_data == user_data) + { + if (entry->destroy != NULL) + entry->destroy (entry->user_data); + + g_array_remove_index (array, array->len - i); + + if (array->len == 0) + { + _tp_dbus_daemon_stop_watching (self, name, watch); + g_hash_table_remove (self->priv->name_owner_watches, name); + } + + return TRUE; + } + } + } + + /* We haven't found it */ + return FALSE; +} + +/* for internal use (TpChannel, TpConnection _new convenience functions) */ +gboolean +_tp_dbus_daemon_get_name_owner (TpDBusDaemon *self, + gint timeout_ms, + const gchar *well_known_name, + gchar **unique_name, + GError **error) +{ + DBusGConnection *gconn; + DBusConnection *dbc; + DBusMessage *message; + DBusMessage *reply; + DBusError dbus_error; + const char *name_in_reply; + const GError *invalidated; + + g_return_val_if_fail (TP_IS_DBUS_DAEMON (self), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + invalidated = tp_proxy_get_invalidated (self); + + if (invalidated != NULL) + { + if (error != NULL) + *error = g_error_copy (invalidated); + + return FALSE; + } + + gconn = tp_proxy_get_dbus_connection (self); + dbc = dbus_g_connection_get_connection (gconn); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, "GetNameOwner"); + + if (message == NULL) + g_error ("Out of memory"); + + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &well_known_name, + DBUS_TYPE_INVALID)) + g_error ("Out of memory"); + + dbus_error_init (&dbus_error); + reply = dbus_connection_send_with_reply_and_block (dbc, message, + timeout_ms, &dbus_error); + + dbus_message_unref (message); + + if (reply == NULL) + { + if (!tp_strdiff (dbus_error.name, DBUS_ERROR_NO_MEMORY)) + g_error ("Out of memory"); + + /* FIXME: ideally we'd use dbus-glib's error mapping for this */ + g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_NAME_OWNER_LOST, + "%s: %s", dbus_error.name, dbus_error.message); + + dbus_error_free (&dbus_error); + return FALSE; + } + + if (!dbus_message_get_args (reply, &dbus_error, + DBUS_TYPE_STRING, &name_in_reply, + DBUS_TYPE_INVALID)) + { + g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_NAME_OWNER_LOST, + "%s: %s", dbus_error.name, dbus_error.message); + + dbus_error_free (&dbus_error); + dbus_message_unref (reply); + return FALSE; + } + + if (unique_name != NULL) + *unique_name = g_strdup (name_in_reply); + + dbus_message_unref (reply); + + return TRUE; +} + +/** + * tp_dbus_daemon_request_name: + * @self: a TpDBusDaemon + * @well_known_name: a well-known name to acquire + * @idempotent: whether to consider it to be a success if this process + * already owns the name + * @error: used to raise an error if %FALSE is returned + * + * Claim the given well-known name without queueing, allowing replacement + * or replacing an existing name-owner. This makes a synchronous call to the + * bus daemon. + * + * Returns: %TRUE if @well_known_name was claimed, or %FALSE and sets @error if + * an error occurred. + * + * Since: 0.7.30 + */ +gboolean +tp_dbus_daemon_request_name (TpDBusDaemon *self, + const gchar *well_known_name, + gboolean idempotent, + GError **error) +{ + TpProxy *as_proxy = (TpProxy *) self; + DBusGConnection *gconn; + DBusConnection *dbc; + DBusError dbus_error; + int result; + const GError *invalidated; + + g_return_val_if_fail (TP_IS_DBUS_DAEMON (self), FALSE); + g_return_val_if_fail (tp_dbus_check_valid_bus_name (well_known_name, + TP_DBUS_NAME_TYPE_WELL_KNOWN, error), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + invalidated = tp_proxy_get_invalidated (self); + + if (invalidated != NULL) + { + if (error != NULL) + *error = g_error_copy (invalidated); + + return FALSE; + } + + gconn = as_proxy->dbus_connection; + dbc = dbus_g_connection_get_connection (gconn); + + dbus_error_init (&dbus_error); + result = dbus_bus_request_name (dbc, well_known_name, + DBUS_NAME_FLAG_DO_NOT_QUEUE, &dbus_error); + + switch (result) + { + case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: + return TRUE; + + case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: + if (idempotent) + { + return TRUE; + } + else + { + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Name '%s' already in use by this process", well_known_name); + return FALSE; + } + + case DBUS_REQUEST_NAME_REPLY_EXISTS: + case DBUS_REQUEST_NAME_REPLY_IN_QUEUE: + /* the latter shouldn't actually happen since we said DO_NOT_QUEUE */ + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Name '%s' already in use by another process", well_known_name); + return FALSE; + + case -1: + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "%s: %s", dbus_error.name, dbus_error.message); + dbus_error_free (&dbus_error); + return FALSE; + + default: + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "RequestName('%s') returned %d and I don't know what that means", + well_known_name, result); + return FALSE; + } +} + +/** + * tp_dbus_daemon_release_name: + * @self: a TpDBusDaemon + * @well_known_name: a well-known name owned by this process to release + * @error: used to raise an error if %FALSE is returned + * + * Release the given well-known name. This makes a synchronous call to the bus + * daemon. + * + * Returns: %TRUE if @well_known_name was released, or %FALSE and sets @error + * if an error occurred. + * + * Since: 0.7.30 + */ +gboolean +tp_dbus_daemon_release_name (TpDBusDaemon *self, + const gchar *well_known_name, + GError **error) +{ + TpProxy *as_proxy = (TpProxy *) self; + DBusGConnection *gconn; + DBusConnection *dbc; + DBusError dbus_error; + int result; + const GError *invalidated; + + g_return_val_if_fail (TP_IS_DBUS_DAEMON (self), FALSE); + g_return_val_if_fail (tp_dbus_check_valid_bus_name (well_known_name, + TP_DBUS_NAME_TYPE_WELL_KNOWN, error), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + invalidated = tp_proxy_get_invalidated (self); + + if (invalidated != NULL) + { + if (error != NULL) + *error = g_error_copy (invalidated); + + return FALSE; + } + + gconn = as_proxy->dbus_connection; + dbc = dbus_g_connection_get_connection (gconn); + dbus_error_init (&dbus_error); + result = dbus_bus_release_name (dbc, well_known_name, &dbus_error); + + switch (result) + { + case DBUS_RELEASE_NAME_REPLY_RELEASED: + return TRUE; + + case DBUS_RELEASE_NAME_REPLY_NOT_OWNER: + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_YOURS, + "Name '%s' owned by another process", well_known_name); + return FALSE; + + case DBUS_RELEASE_NAME_REPLY_NON_EXISTENT: + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Name '%s' not owned", well_known_name); + return FALSE; + + case -1: + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "%s: %s", dbus_error.name, dbus_error.message); + dbus_error_free (&dbus_error); + return FALSE; + + default: + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "ReleaseName('%s') returned %d and I don't know what that means", + well_known_name, result); + return FALSE; + } +} + +/** + * tp_dbus_daemon_get_unique_name: + * + * <!-- Returns: is enough --> + * + * Returns: the unique name of this connection to the bus, which is valid for + * as long as this #TpDBusDaemon is + * Since: 0.7.UNRELEASED + */ +const gchar * +tp_dbus_daemon_get_unique_name (TpDBusDaemon *self) +{ + g_return_val_if_fail (TP_IS_DBUS_DAEMON (self), NULL); + + return dbus_bus_get_unique_name (self->priv->libdbus); +} + +static void +free_daemon_list (gpointer p) +{ + GSList **slistp = p; + + g_slist_free (*slistp); + g_slice_free (GSList *, slistp); +} + +static GObject * +tp_dbus_daemon_constructor (GType type, + guint n_params, + GObjectConstructParam *params) +{ + GObjectClass *object_class = + (GObjectClass *) tp_dbus_daemon_parent_class; + TpDBusDaemon *self = TP_DBUS_DAEMON (object_class->constructor (type, + n_params, params)); + TpProxy *as_proxy = (TpProxy *) self; + GSList **daemons; + + g_assert (!tp_strdiff (as_proxy->bus_name, DBUS_SERVICE_DBUS)); + g_assert (!tp_strdiff (as_proxy->object_path, DBUS_PATH_DBUS)); + + self->priv->libdbus = dbus_connection_ref ( + dbus_g_connection_get_connection ( + tp_proxy_get_dbus_connection (self))); + + /* one ref per TpDBusDaemon, released in finalize */ + if (!dbus_connection_allocate_data_slot (&daemons_slot)) + g_error ("Out of memory"); + + daemons = dbus_connection_get_data (self->priv->libdbus, daemons_slot); + + if (daemons == NULL) + { + daemons = g_slice_new (GSList *); + + *daemons = NULL; + dbus_connection_set_data (self->priv->libdbus, daemons_slot, daemons, + free_daemon_list); + + /* we add this filter at most once per DBusConnection */ + if (!dbus_connection_add_filter (self->priv->libdbus, + _tp_dbus_daemon_name_owner_changed_filter, NULL, NULL)) + g_error ("Out of memory"); + } + + *daemons = g_slist_prepend (*daemons, self); + + return (GObject *) self; +} + +static void +tp_dbus_daemon_init (TpDBusDaemon *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_DBUS_DAEMON, + TpDBusDaemonPrivate); + + self->priv->name_owner_watches = g_hash_table_new_full (g_str_hash, + g_str_equal, g_free, NULL); +} + +static void +tp_dbus_daemon_dispose (GObject *object) +{ + TpDBusDaemon *self = TP_DBUS_DAEMON (object); + GSList **daemons; + + if (self->priv->name_owner_watches != NULL) + { + GHashTable *tmp = self->priv->name_owner_watches; + GHashTableIter iter; + gpointer k, v; + + self->priv->name_owner_watches = NULL; + g_hash_table_iter_init (&iter, tmp); + + while (g_hash_table_iter_next (&iter, &k, &v)) + { + _tp_dbus_daemon_stop_watching (self, k, v); + g_hash_table_iter_remove (&iter); + } + + g_hash_table_destroy (tmp); + } + + if (self->priv->libdbus != NULL) + { + /* remove myself from the list to be notified on NoC */ + daemons = dbus_connection_get_data (self->priv->libdbus, daemons_slot); + + /* should always be non-NULL, barring bugs */ + if (G_LIKELY (daemons != NULL)) + { + *daemons = g_slist_remove (*daemons, self); + } + + dbus_connection_unref (self->priv->libdbus); + self->priv->libdbus = NULL; + } + + G_OBJECT_CLASS (tp_dbus_daemon_parent_class)->dispose (object); +} + +static void +tp_dbus_daemon_finalize (GObject *object) +{ + GObjectFinalizeFunc chain_up = G_OBJECT_CLASS (tp_dbus_daemon_parent_class)->finalize; + + /* one ref per TpDBusDaemon, from constructor */ + dbus_connection_free_data_slot (&daemons_slot); + + if (chain_up != NULL) + chain_up (object); +} + +/** + * tp_dbus_daemon_init_known_interfaces: + * + * Ensure that the known interfaces for TpDBusDaemon have been set up. + * This is done automatically when necessary, but for correct + * overriding of library interfaces by local extensions, you should + * call this function before calling + * tp_proxy_or_subclass_hook_on_interface_add() with first argument + * %TP_TYPE_DBUS_DAEMON. + * + * Since: 0.7.32 + */ +void +tp_dbus_daemon_init_known_interfaces (void) +{ + static gsize once = 0; + + if (g_once_init_enter (&once)) + { + tp_proxy_init_known_interfaces (); + tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_DBUS_DAEMON, + tp_cli_dbus_daemon_add_signals); + + g_once_init_leave (&once, 1); + } +} + +static void +tp_dbus_daemon_class_init (TpDBusDaemonClass *klass) +{ + TpProxyClass *proxy_class = (TpProxyClass *) klass; + GObjectClass *object_class = (GObjectClass *) klass; + + tp_dbus_daemon_init_known_interfaces (); + + g_type_class_add_private (klass, sizeof (TpDBusDaemonPrivate)); + + object_class->constructor = tp_dbus_daemon_constructor; + object_class->dispose = tp_dbus_daemon_dispose; + object_class->finalize = tp_dbus_daemon_finalize; + + proxy_class->interface = TP_IFACE_QUARK_DBUS_DAEMON; +} + +/* Auto-generated implementation of _tp_register_dbus_glib_marshallers */ +#include "_gen/register-dbus-glib-marshallers-body.h" |