/* vi: set et sw=4 ts=8 cino=t0,(0: */ /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 8 -*- */ /* * This file is part of mission-control * * Copyright © 2007-2011 Nokia Corporation. * Copyright © 2009-2011 Collabora Ltd. * * Contact: Naba Kumar * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 * */ /** * SECTION:mcd-dispatcher * @title: McdDispatcher * @short_description: Dispatcher class to dispatch channels to handlers * @see_also: * @stability: Unstable * @include: mcd-dispatcher.h * * FIXME */ #include "config.h" #include #include #include #include #include "mission-control-plugins/mission-control-plugins.h" #include "client-registry.h" #include "mcd-account-priv.h" #include "mcd-client-priv.h" #include "mcd-connection.h" #include "mcd-connection-priv.h" #include "mcd-channel.h" #include "mcd-master.h" #include "mcd-channel-priv.h" #include "mcd-dispatcher-priv.h" #include "mcd-dispatch-operation-priv.h" #include "mcd-handler-map-priv.h" #include "mcd-misc.h" #include "plugin-loader.h" #include #include #include #include #include #include "sp_timestamp.h" #define MCD_DISPATCHER_PRIV(dispatcher) (MCD_DISPATCHER (dispatcher)->priv) static void dispatcher_iface_init (gpointer, gpointer); static void messages_iface_init (gpointer, gpointer); G_DEFINE_TYPE_WITH_CODE (McdDispatcher, mcd_dispatcher, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_DISPATCHER, dispatcher_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_DISPATCHER_INTERFACE_MESSAGES1, messages_iface_init); G_IMPLEMENT_INTERFACE ( TP_TYPE_SVC_CHANNEL_DISPATCHER_INTERFACE_OPERATION_LIST1, NULL)) typedef struct { McdDispatcher *dispatcher; McdChannel *channel; gint handler_locks; gboolean handled; } McdChannelRecover; typedef struct { McdDispatcher *dispatcher; gchar *account_path; GHashTable *properties; gint64 user_action_time; gchar *preferred_handler; GHashTable *request_metadata; gboolean ensure; } McdChannelRequestACL; struct _McdDispatcherPrivate { /* Dispatching contexts */ GList *contexts; /* Channel dispatch operations */ GList *operations; TpClientFactory *factory; /* hash table containing clients * char *bus_name -> McdClientProxy */ McdClientRegistry *clients; McdHandlerMap *handler_map; McdMaster *master; /* connection => itself, borrowed */ GHashTable *connections; /* Initially FALSE, meaning we suppress OperationList.DispatchOperations * change notification signals because nobody has retrieved that property * yet. Set to TRUE the first time someone reads the DispatchOperations * property. */ gboolean operation_list_active; gboolean is_disposed; }; struct cancel_call_data { DBusGProxy *handler_proxy; DBusGProxyCall *call; McdDispatcher *dispatcher; }; enum { PROP_0, PROP_FACTORY, PROP_MCD_MASTER, PROP_INTERFACES, PROP_SUPPORTS_REQUEST_HINTS, PROP_DISPATCH_OPERATIONS, }; static void on_operation_finished (McdDispatchOperation *operation, const gchar *dbus_error, const gchar *message, McdDispatcher *self); static void on_master_abort (McdMaster *master, McdDispatcherPrivate *priv) { tp_clear_object (&priv->master); } static GStrv mcd_dispatcher_dup_internal_handlers (void) { const gchar * const internal_handlers[] = { CDO_INTERNAL_HANDLER, NULL }; return g_strdupv ((GStrv) internal_handlers); } static GStrv mcd_dispatcher_dup_possible_handlers (McdDispatcher *self, McdRequest *request, TpChannel *channel, const gchar *must_have_unique_name) { GList *handlers; guint n_handlers; guint i; GStrv ret; const GList *iter; GVariant *request_properties = NULL; if (request != NULL) request_properties = mcd_request_dup_properties (request); handlers = _mcd_client_registry_list_possible_handlers ( self->priv->clients, request != NULL ? _mcd_request_get_preferred_handler (request) : NULL, request_properties, channel, must_have_unique_name); n_handlers = g_list_length (handlers); tp_clear_pointer (&request_properties, g_variant_unref); if (handlers == NULL) return NULL; ret = g_new0 (gchar *, n_handlers + 1); for (iter = handlers, i = 0; iter != NULL; iter = iter->next, i++) { ret[i] = g_strdup (tp_proxy_get_bus_name (iter->data)); } ret[n_handlers] = NULL; g_list_free (handlers); return ret; } static void on_operation_finished (McdDispatchOperation *operation, const gchar *dbus_error, const gchar *message, McdDispatcher *self) { GList *its_link; g_signal_handlers_disconnect_by_func (operation, on_operation_finished, self); /* don't emit the signal if the CDO never appeared on D-Bus */ if (self->priv->operation_list_active && _mcd_dispatch_operation_needs_approval (operation)) { tp_svc_channel_dispatcher_interface_operation_list1_emit_dispatch_operation_finished ( self, _mcd_dispatch_operation_get_path (operation)); } its_link = g_list_find (self->priv->operations, operation); if (its_link != NULL) { self->priv->operations = g_list_delete_link (self->priv->operations, its_link); g_object_unref (operation); } } static void _mcd_dispatcher_enter_state_machine (McdDispatcher *dispatcher, McdChannel *channel, const gchar * const *possible_handlers, gboolean requested, gboolean only_observe) { McdDispatchOperation *operation; McdDispatcherPrivate *priv; McdAccount *account; g_return_if_fail (MCD_IS_DISPATCHER (dispatcher)); g_return_if_fail (MCD_IS_CHANNEL (channel)); g_return_if_fail (requested || !only_observe); account = mcd_channel_get_account (channel); if (G_UNLIKELY (!account)) { g_warning ("%s called with no account", G_STRFUNC); return; } priv = dispatcher->priv; DEBUG ("new dispatch operation for %s channel %p: %s", requested ? "requested" : "unrequested", channel, mcd_channel_get_object_path (channel)); operation = _mcd_dispatch_operation_new (priv->clients, priv->handler_map, !requested, only_observe, channel, (const gchar * const *) possible_handlers); if (!requested) { if (priv->operation_list_active) { tp_svc_channel_dispatcher_interface_operation_list1_emit_new_dispatch_operation ( dispatcher, _mcd_dispatch_operation_get_path (operation), _mcd_dispatch_operation_get_properties (operation)); } priv->operations = g_list_prepend (priv->operations, g_object_ref (operation)); g_signal_connect (operation, "finished", G_CALLBACK (on_operation_finished), dispatcher); } if (_mcd_dispatch_operation_get_cancelled (operation)) { GError error = { TP_ERROR, TP_ERROR_CANCELLED, "Channel request cancelled" }; McdChannel *cancelled; cancelled = _mcd_dispatch_operation_dup_channel (operation); if (cancelled != NULL) { if (mcd_channel_get_error (cancelled) == NULL) mcd_channel_take_error (cancelled, g_error_copy (&error)); _mcd_channel_undispatchable (cancelled); g_object_unref (cancelled); } } else if (_mcd_dispatch_operation_peek_channel (operation) == NULL) { DEBUG ("No channels left"); } else { _mcd_dispatch_operation_run_clients (operation); } g_object_unref (operation); } static void _mcd_dispatcher_set_property (GObject * obj, guint prop_id, const GValue * val, GParamSpec * pspec) { McdDispatcherPrivate *priv = MCD_DISPATCHER_PRIV (obj); McdMaster *master; switch (prop_id) { case PROP_FACTORY: g_assert (priv->factory == NULL); /* construct-only */ priv->factory = TP_CLIENT_FACTORY (g_value_dup_object (val)); break; case PROP_MCD_MASTER: master = g_value_get_object (val); g_object_ref (G_OBJECT (master)); if (priv->master) { g_signal_handlers_disconnect_by_func (G_OBJECT (master), G_CALLBACK (on_master_abort), NULL); g_object_unref (priv->master); } priv->master = master; g_signal_connect (G_OBJECT (master), "abort", G_CALLBACK (on_master_abort), priv); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } static const char * const interfaces[] = { TP_IFACE_CHANNEL_DISPATCHER_INTERFACE_OPERATION_LIST1, TP_IFACE_CHANNEL_DISPATCHER_INTERFACE_MESSAGES1, NULL }; static void _mcd_dispatcher_get_property (GObject * obj, guint prop_id, GValue * val, GParamSpec * pspec) { McdDispatcherPrivate *priv = MCD_DISPATCHER_PRIV (obj); switch (prop_id) { case PROP_FACTORY: g_value_set_object (val, priv->factory); break; case PROP_MCD_MASTER: g_value_set_object (val, priv->master); break; case PROP_INTERFACES: g_value_set_static_boxed (val, interfaces); break; case PROP_SUPPORTS_REQUEST_HINTS: g_value_set_boolean (val, TRUE); break; case PROP_DISPATCH_OPERATIONS: { GList *iter; GPtrArray *operations = g_ptr_array_new (); /* Side-effect: from now on, emit change notification signals for * this property */ priv->operation_list_active = TRUE; for (iter = priv->operations; iter != NULL; iter = iter->next) { McdDispatchOperation *op = iter->data; if (_mcd_dispatch_operation_needs_approval (op) && !_mcd_dispatch_operation_is_finished (op)) { GValueArray *va = g_value_array_new (2); g_value_array_append (va, NULL); g_value_array_append (va, NULL); g_value_init (va->values + 0, DBUS_TYPE_G_OBJECT_PATH); g_value_init (va->values + 1, TP_HASH_TYPE_STRING_VARIANT_MAP); g_value_set_boxed (va->values + 0, _mcd_dispatch_operation_get_path (op)); g_value_set_boxed (va->values + 1, _mcd_dispatch_operation_get_properties (op)); g_ptr_array_add (operations, va); } } g_value_take_boxed (val, operations); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } static void mcd_dispatcher_client_handling_channel_cb (McdClientProxy *client, const gchar *object_path, McdDispatcher *self) { const gchar *bus_name = tp_proxy_get_bus_name (client); const gchar *unique_name = _mcd_client_proxy_get_unique_name (client); if (unique_name == NULL || unique_name[0] == '\0') { /* if it said it was handling channels but it doesn't seem to exist * (or worse, doesn't know whether it exists) then we don't believe * it */ DEBUG ("%s doesn't seem to exist, assuming it's not handling %s", bus_name, object_path); return; } DEBUG ("%s (%s) is handling %s", bus_name, unique_name, object_path); _mcd_handler_map_set_path_handled (self->priv->handler_map, object_path, unique_name, bus_name); } static void mcd_dispatcher_update_client_caps (McdDispatcher *self, McdClientProxy *client); static void mcd_dispatcher_client_capabilities_changed_cb (McdClientProxy *client, McdDispatcher *self) { mcd_dispatcher_update_client_caps (self, client); } static void mcd_dispatcher_client_gone_cb (McdClientProxy *client, McdDispatcher *self); static void mcd_dispatcher_client_needs_recovery_cb (McdClientProxy *client, McdDispatcher *self); static void mcd_dispatcher_discard_client (McdDispatcher *self, McdClientProxy *client) { g_signal_handlers_disconnect_by_func (client, mcd_dispatcher_client_capabilities_changed_cb, self); g_signal_handlers_disconnect_by_func (client, mcd_dispatcher_client_handling_channel_cb, self); g_signal_handlers_disconnect_by_func (client, mcd_dispatcher_client_gone_cb, self); g_signal_handlers_disconnect_by_func (client, mcd_dispatcher_client_needs_recovery_cb, self); } static void mcd_dispatcher_client_gone_cb (McdClientProxy *client, McdDispatcher *self) { mcd_dispatcher_discard_client (self, client); } /* * @channel: a non-null TpChannel which has already been dispatched * @request: (allow-none): if not NULL, a request that resulted in * @channel * * Return a Handler to which @channel could be re-dispatched, * for instance as a result of a re-request or a PresentChannel call. * * If @channel was dispatched to a Handler, return that Handler. * Otherwise, if it was Claimed by a process, and that process * has a Handler to which the channel could have been dispatched, * return that Handler. Otherwise return NULL. */ static McdClientProxy * _mcd_dispatcher_lookup_handler (McdDispatcher *self, TpChannel *channel, McdRequest *request) { McdClientProxy *handler = NULL; const gchar *object_path; const gchar *unique_name; const gchar *well_known_name; object_path = tp_proxy_get_object_path (channel); unique_name = _mcd_handler_map_get_handler (self->priv->handler_map, object_path, &well_known_name); if (unique_name == NULL) { DEBUG ("No process is handling channel %s", object_path); return NULL; } if (well_known_name != NULL) { /* We know which Handler well-known name was responsible: use it if it * still exists */ DEBUG ("Channel %s is handler by %s", object_path, well_known_name); handler = _mcd_client_registry_lookup (self->priv->clients, well_known_name); } if (handler == NULL) { GList *possible_handlers; GVariant *request_properties = NULL; /* Failing that, maybe the Handler it was dispatched to was temporary; * try to pick another Handler that can deal with it, on the same * unique name (i.e. in the same process). * It can also happen in the case an Observer/Approver Claimed the * channel; in that case we did not get its handler well known name. */ if (request != NULL) request_properties = mcd_request_dup_properties (request); possible_handlers = _mcd_client_registry_list_possible_handlers ( self->priv->clients, request != NULL ? _mcd_request_get_preferred_handler (request) : NULL, request_properties, channel, unique_name); tp_clear_pointer (&request_properties, g_variant_unref); if (possible_handlers != NULL) { DEBUG ("Pick first possible handler for channel %s", object_path); handler = possible_handlers->data; } else { /* The process is still running (otherwise it wouldn't be in the * handler map), but none of its well-known names is still * interested in channels of that sort. Oh well, not our problem. */ DEBUG ("process %s no longer interested in channel %s", unique_name, object_path); } g_list_free (possible_handlers); } return handler; } static void mcd_dispatcher_client_needs_recovery_cb (McdClientProxy *client, McdDispatcher *self) { const GList *channels = _mcd_handler_map_get_handled_channels (self->priv->handler_map); const GList *observer_filters; const GList *list; DEBUG ("called"); observer_filters = _mcd_client_proxy_get_observer_filters (client); for (list = channels; list; list = list->next) { TpChannel *channel = list->data; GVariant *properties; properties = tp_channel_dup_immutable_properties (channel); if (_mcd_client_match_filters (properties, observer_filters, FALSE)) { const gchar *account_path = _mcd_handler_map_get_channel_account (self->priv->handler_map, tp_proxy_get_object_path (channel)); _mcd_client_recover_observer (client, channel, account_path); } g_variant_unref (properties); } /* we also need to think about channels that are still being dispatched, * but have got far enough that this client wouldn't otherwise see them */ for (list = self->priv->operations; list != NULL; list = list->next) { McdDispatchOperation *op = list->data; if (_mcd_dispatch_operation_has_invoked_observers (op)) { McdChannel *mcd_channel = _mcd_dispatch_operation_peek_channel (op); if (mcd_channel != NULL) { GVariant *properties = mcd_channel_dup_immutable_properties (mcd_channel); if (_mcd_client_match_filters (properties, observer_filters, FALSE)) { _mcd_client_recover_observer (client, mcd_channel_get_tp_channel (mcd_channel), _mcd_dispatch_operation_get_account_path (op)); } g_variant_unref (properties); } } } } static void mcd_dispatcher_client_added_cb (McdClientRegistry *clients, McdClientProxy *client, McdDispatcher *self) { g_signal_connect (client, "gone", G_CALLBACK (mcd_dispatcher_client_gone_cb), self); g_signal_connect (client, "is-handling-channel", G_CALLBACK (mcd_dispatcher_client_handling_channel_cb), self); g_signal_connect (client, "handler-capabilities-changed", G_CALLBACK (mcd_dispatcher_client_capabilities_changed_cb), self); g_signal_connect (client, "need-recovery", G_CALLBACK (mcd_dispatcher_client_needs_recovery_cb), self); } static void mcd_dispatcher_client_registry_ready_cb (McdClientRegistry *clients, McdDispatcher *self) { GHashTableIter iter; gpointer p; GPtrArray *vas; DEBUG ("All initial clients have been inspected"); vas = _mcd_client_registry_dup_client_caps (clients); g_hash_table_iter_init (&iter, self->priv->connections); while (g_hash_table_iter_next (&iter, &p, NULL)) { _mcd_connection_start_dispatching (p, vas); } g_ptr_array_foreach (vas, (GFunc) g_value_array_free, NULL); g_ptr_array_unref (vas); } static void drop_each_operation (gpointer operation, gpointer dispatcher) { g_signal_handlers_disconnect_by_func (operation, on_operation_finished, dispatcher); g_object_unref (operation); } static void _mcd_dispatcher_dispose (GObject * object) { McdDispatcherPrivate *priv = MCD_DISPATCHER_PRIV (object); if (priv->is_disposed) { return; } priv->is_disposed = TRUE; if (priv->operations != NULL) { g_list_foreach (priv->operations, drop_each_operation, object); tp_clear_pointer (&priv->operations, g_list_free); } tp_clear_object (&priv->handler_map); if (priv->clients != NULL) { gpointer client_p; GHashTableIter iter; _mcd_client_registry_init_hash_iter (priv->clients, &iter); while (g_hash_table_iter_next (&iter, NULL, &client_p)) { mcd_dispatcher_discard_client ((McdDispatcher *) object, client_p); } g_signal_handlers_disconnect_by_func (priv->clients, mcd_dispatcher_client_added_cb, object); g_signal_handlers_disconnect_by_func (priv->clients, mcd_dispatcher_client_registry_ready_cb, object); tp_clear_object (&priv->clients); } tp_clear_pointer (&priv->connections, g_hash_table_unref); tp_clear_object (&priv->master); tp_clear_object (&priv->factory); G_OBJECT_CLASS (mcd_dispatcher_parent_class)->dispose (object); } static void mcd_dispatcher_update_client_caps (McdDispatcher *self, McdClientProxy *client) { GPtrArray *vas; GHashTableIter iter; gpointer k; /* If we haven't finished inspecting initial clients yet, we'll push all * the client caps into all connections when we do, so do nothing. * * If we don't have any connections, on the other hand, then there's * nothing to do. */ if (!_mcd_client_registry_is_ready (self->priv->clients) || g_hash_table_size (self->priv->connections) == 0) { return; } vas = g_ptr_array_sized_new (1); g_ptr_array_add (vas, _mcd_client_proxy_dup_handler_capabilities (client)); g_hash_table_iter_init (&iter, self->priv->connections); while (g_hash_table_iter_next (&iter, &k, NULL)) { _mcd_connection_update_client_caps (k, vas); } g_ptr_array_foreach (vas, (GFunc) g_value_array_free, NULL); g_ptr_array_unref (vas); } static void mcd_dispatcher_constructed (GObject *object) { McdDispatcherPrivate *priv = MCD_DISPATCHER_PRIV (object); GError *error = NULL; priv->handler_map = _mcd_handler_map_new (priv->factory); priv->clients = _mcd_client_registry_new (priv->factory); g_signal_connect (priv->clients, "client-added", G_CALLBACK (mcd_dispatcher_client_added_cb), object); g_signal_connect (priv->clients, "ready", G_CALLBACK (mcd_dispatcher_client_registry_ready_cb), object); tp_dbus_connection_register_object ( tp_client_factory_get_dbus_connection (priv->factory), TP_CHANNEL_DISPATCHER_OBJECT_PATH, object); if (!tp_dbus_connection_request_name ( tp_client_factory_get_dbus_connection (priv->factory), TP_CHANNEL_DISPATCHER_BUS_NAME, TRUE /* idempotent */, &error)) { /* FIXME: put in proper error handling when MC gains the ability to * be the AM or the CD but not both */ g_warning ("Failed registering '%s' service: %s", TP_CHANNEL_DISPATCHER_BUS_NAME, error->message); g_error_free (error); exit (1); } } static void mcd_dispatcher_class_init (McdDispatcherClass * klass) { static TpDBusPropertiesMixinPropImpl cd_props[] = { { "Interfaces", "interfaces", NULL }, { NULL } }; static TpDBusPropertiesMixinPropImpl op_list_props[] = { { "DispatchOperations", "dispatch-operations", NULL }, { NULL } }; GObjectClass *object_class = G_OBJECT_CLASS (klass); g_type_class_add_private (object_class, sizeof (McdDispatcherPrivate)); object_class->constructed = mcd_dispatcher_constructed; object_class->set_property = _mcd_dispatcher_set_property; object_class->get_property = _mcd_dispatcher_get_property; object_class->dispose = _mcd_dispatcher_dispose; /* Properties */ g_object_class_install_property (object_class, PROP_FACTORY, g_param_spec_object ("factory", "Factory", "Client factory", TP_TYPE_CLIENT_FACTORY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_MCD_MASTER, g_param_spec_object ("mcd-master", "McdMaster", "McdMaster", MCD_TYPE_MASTER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_INTERFACES, g_param_spec_boxed ("interfaces", "Interfaces", "Interfaces", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_DISPATCH_OPERATIONS, g_param_spec_boxed ("dispatch-operations", "ChannelDispatchOperation details", "A dbus-glib a(oa{sv})", TP_ARRAY_TYPE_DISPATCH_OPERATION_DETAILS_LIST, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); tp_dbus_properties_mixin_class_init (object_class, 0); tp_dbus_properties_mixin_implement_interface (object_class, TP_IFACE_QUARK_CHANNEL_DISPATCHER, tp_dbus_properties_mixin_getter_gobject_properties, NULL, cd_props); tp_dbus_properties_mixin_implement_interface (object_class, TP_IFACE_QUARK_CHANNEL_DISPATCHER_INTERFACE_OPERATION_LIST1, tp_dbus_properties_mixin_getter_gobject_properties, NULL, op_list_props); } static void mcd_dispatcher_init (McdDispatcher * dispatcher) { McdDispatcherPrivate *priv; priv = G_TYPE_INSTANCE_GET_PRIVATE (dispatcher, MCD_TYPE_DISPATCHER, McdDispatcherPrivate); dispatcher->priv = priv; priv->operation_list_active = FALSE; priv->connections = g_hash_table_new (NULL, NULL); /* idempotent, not guaranteed to have been called yet */ _mcd_plugin_loader_init (); } McdDispatcher * mcd_dispatcher_new (TpClientFactory *factory, McdMaster *master) { McdDispatcher *obj; obj = MCD_DISPATCHER (g_object_new (MCD_TYPE_DISPATCHER, "factory", factory, "mcd-master", master, NULL)); return obj; } /* * _mcd_dispatcher_add_channel: * @dispatcher: the #McdDispatcher. * @channel: (transfer none): a #McdChannel which must own a #TpChannel * @requested: whether the channels were requested by MC. * * Add @channel to the dispatching state machine. */ void _mcd_dispatcher_add_channel (McdDispatcher *dispatcher, McdChannel *channel, gboolean requested, gboolean only_observe) { TpChannel *tp_channel = NULL; GStrv possible_handlers; McdRequest *request = NULL; gboolean internal_request = FALSE; g_return_if_fail (MCD_IS_DISPATCHER (dispatcher)); g_return_if_fail (MCD_IS_CHANNEL (channel)); DEBUG ("%s channel %p: %s", requested ? "requested" : "unrequested", channel, mcd_channel_get_object_path (channel)); if (only_observe) { g_return_if_fail (requested); /* these channels were requested "behind our back", so only call * ObserveChannel on them */ _mcd_dispatcher_enter_state_machine (dispatcher, channel, NULL, TRUE, TRUE); return; } /* The channel must have the TpChannel part of McdChannel's double life. * It might also have the McdRequest part. */ tp_channel = mcd_channel_get_tp_channel (channel); g_assert (tp_channel != NULL); request = _mcd_channel_get_request (channel); internal_request = _mcd_request_is_internal (request); /* See if there are any handlers that can take all these channels */ if (internal_request) possible_handlers = mcd_dispatcher_dup_internal_handlers (); else possible_handlers = mcd_dispatcher_dup_possible_handlers (dispatcher, request, tp_channel, NULL); if (possible_handlers == NULL) { DEBUG ("Channel cannot be handled - making a CDO " "anyway, to get Observers run"); } else { DEBUG ("%s handler(s) found, dispatching channel", internal_request ? "internal" : "possible"); } _mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_DISPATCHING); _mcd_dispatcher_enter_state_machine (dispatcher, channel, (const gchar * const *) possible_handlers, requested, FALSE); g_strfreev (possible_handlers); } static void mcd_dispatcher_finish_reinvocation (McdChannel *request) { _mcd_channel_set_status (request, MCD_CHANNEL_STATUS_DISPATCHED); /* no need to keep it around any more */ mcd_mission_abort (MCD_MISSION (request)); } static void reinvoke_handle_channel_cb (TpClient *client, const GError *error, gpointer user_data G_GNUC_UNUSED, GObject *weak_object) { McdChannel *request = MCD_CHANNEL (weak_object); if (error != NULL) { /* The handler is already dealing with this channel, but refuses to * re-handle it. Oh well, we tried. */ DEBUG ("handler %s refused re-notification about channel %p:%s: " "%s:%d: %s", tp_proxy_get_bus_name (client), request, mcd_channel_get_object_path (request), g_quark_to_string (error->domain), error->code, error->message); } else { DEBUG ("handler %s successfully notified about channel %p:%s", tp_proxy_get_bus_name (client), request, mcd_channel_get_object_path (request)); } /* either way, consider the request to have been dispatched */ mcd_dispatcher_finish_reinvocation (request); } /* * _mcd_dispatcher_reinvoke_handler: * @dispatcher: The #McdDispatcher. * @request: a #McdChannel that has both a #TpChannel and a #McdRequest * * Re-invoke the channel handler for @request. */ static void _mcd_dispatcher_reinvoke_handler (McdDispatcher *dispatcher, McdChannel *request) { McdClientProxy *handler = NULL; McdRequest *real_request = _mcd_channel_get_request (request); TpChannel *tp_channel = mcd_channel_get_tp_channel (request); GHashTable *handler_info; GHashTable *request_properties; g_assert (real_request != NULL); g_assert (tp_channel != NULL); request_properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_unref); g_hash_table_insert (request_properties, g_strdup (_mcd_request_get_object_path (real_request)), _mcd_request_dup_immutable_properties (real_request)); handler_info = tp_asv_new (NULL, NULL); /* hand over ownership of request_properties */ tp_asv_take_boxed (handler_info, "request-properties", TP_HASH_TYPE_OBJECT_IMMUTABLE_PROPERTIES_MAP, request_properties); request_properties = NULL; handler = _mcd_dispatcher_lookup_handler (dispatcher, tp_channel, real_request); if (handler == NULL) { mcd_dispatcher_finish_reinvocation (request); goto finally; } /* This is deliberately not the same call as for normal dispatching, * and it doesn't go through a dispatch operation - the error handling * is completely different, because the channel is already being * handled perfectly well. */ _mcd_client_proxy_handle_channel (handler, -1, request, 0, /* the request's user action time will be used automatically */ handler_info, reinvoke_handle_channel_cb, NULL, NULL, (GObject *) request); finally: g_hash_table_unref (handler_info); } static McdDispatchOperation * find_operation_from_channel (McdDispatcher *dispatcher, McdChannel *channel) { GList *list; g_return_val_if_fail (MCD_IS_CHANNEL (channel), NULL); for (list = dispatcher->priv->operations; list != NULL; list = list->next) { McdDispatchOperation *op = list->data; if (_mcd_dispatch_operation_has_channel (op, channel)) return op; } return NULL; } void _mcd_dispatcher_add_channel_request (McdDispatcher *dispatcher, McdChannel *channel, McdChannel *request) { McdChannelStatus status; McdRequest *origin = _mcd_channel_get_request (request); gboolean internal = _mcd_request_is_internal (origin); status = mcd_channel_get_status (channel); /* if the channel is already dispatched, just reinvoke the handler; if it * is not, @request must mirror the status of @channel */ if (status == MCD_CHANNEL_STATUS_DISPATCHED) { DEBUG ("reinvoking handler on channel %p", request); /* copy the object path and the immutable properties from the * existing channel */ _mcd_channel_copy_details (request, channel); if (internal) _mcd_request_handle_internally (origin, request, FALSE); else _mcd_dispatcher_reinvoke_handler (dispatcher, request); } else { DEBUG ("non-reinvoked handling of channel %p", request); _mcd_channel_set_request_proxy (request, channel); if (internal) { _mcd_request_handle_internally (origin, request, FALSE); } else if (status == MCD_CHANNEL_STATUS_DISPATCHING) { McdDispatchOperation *op = find_operation_from_channel (dispatcher, channel); const gchar *preferred_handler = _mcd_channel_get_request_preferred_handler (request); g_return_if_fail (op != NULL); DEBUG ("channel %p is in CDO %p", channel, op); _mcd_dispatch_operation_approve (op, preferred_handler); } DEBUG ("channel %p is proxying %p", request, channel); } } void _mcd_dispatcher_recover_channel (McdDispatcher *dispatcher, McdChannel *channel, const gchar *account_path) { McdDispatcherPrivate *priv; const gchar *path; const gchar *unique_name; const gchar *well_known_name = NULL; gboolean requested; TpChannel *tp_channel; /* we must check if the channel is already being handled by some client; to * do this, we can examine the active handlers' "HandledChannel" property. * By now, we should already have done this, because startup has completed. */ g_return_if_fail (MCD_IS_DISPATCHER (dispatcher)); priv = dispatcher->priv; g_return_if_fail (_mcd_client_registry_is_ready ( dispatcher->priv->clients)); path = mcd_channel_get_object_path (channel); tp_channel = mcd_channel_get_tp_channel (channel); g_return_if_fail (tp_channel != NULL); unique_name = _mcd_handler_map_get_handler (priv->handler_map, path, &well_known_name); if (unique_name != NULL) { DEBUG ("Channel %s is already handled by process %s", path, unique_name); _mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_DISPATCHED); _mcd_handler_map_set_channel_handled (priv->handler_map, tp_channel, unique_name, well_known_name, account_path); } else { DEBUG ("%s is unhandled, redispatching", path); requested = mcd_channel_is_requested (channel); _mcd_dispatcher_add_channel (dispatcher, channel, requested, FALSE); } } static gboolean check_preferred_handler (const gchar *preferred_handler, GError **error) { g_assert (error != NULL); if (preferred_handler[0] == '\0') return TRUE; if (!tp_dbus_check_valid_bus_name (preferred_handler, TP_DBUS_NAME_TYPE_WELL_KNOWN, error)) { /* The error is TP_DBUS_ERROR_INVALID_BUS_NAME, which has no D-Bus * representation; re-map to InvalidArgument. */ (*error)->domain = TP_ERROR; (*error)->code = TP_ERROR_INVALID_ARGUMENT; return FALSE; } if (!g_str_has_prefix (preferred_handler, TP_CLIENT_BUS_NAME_BASE)) { g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Not a Telepathy Client: %s", preferred_handler); return FALSE; } return TRUE; } static void dispatcher_request_channel (McdDispatcher *self, const gchar *account_path, GHashTable *requested_properties, gint64 user_action_time, const gchar *preferred_handler, GHashTable *request_metadata, GDBusMethodInvocation *context, gboolean ensure) { McdAccountManager *am; McdAccount *account; McdChannel *channel = NULL; McdRequest *request = NULL; GError *error = NULL; const gchar *path; GHashTable *immutables; g_return_if_fail (account_path != NULL); g_return_if_fail (requested_properties != NULL); g_return_if_fail (preferred_handler != NULL); g_object_get (self->priv->master, "account-manager", &am, NULL); g_assert (am != NULL); account = mcd_account_manager_lookup_account_by_path (am, account_path); if (account == NULL) { g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "No such account: %s", account_path); goto despair; } if (!check_preferred_handler (preferred_handler, &error)) goto despair; channel = _mcd_account_create_request (self->priv->clients, account, requested_properties, user_action_time, preferred_handler, request_metadata, ensure, &request, &error); if (channel == NULL) { /* FIXME: ideally this would be emitted as a Failed signal after * Proceed is called, but for the particular failure case here (low * memory) perhaps we don't want to */ goto despair; } g_assert (request != NULL); path = _mcd_request_get_object_path (request); g_assert (path != NULL); /* This is OK because the signatures of CreateChannel and EnsureChannel * are the same */ immutables = _mcd_request_dup_immutable_properties (request); tp_svc_channel_dispatcher_return_from_create_channel (context, path, immutables); g_hash_table_unref (immutables); _mcd_request_predict_handler (request); /* We've done all we need to with this channel: the ChannelRequests code * keeps it alive as long as is necessary. The finally clause will * free it */ goto finally; despair: g_dbus_method_invocation_return_gerror (context, error); g_error_free (error); finally: tp_clear_object (&channel); tp_clear_object (&request); g_object_unref (am); } static void dispatcher_create_channel (TpSvcChannelDispatcher *iface, const gchar *account_path, GHashTable *requested_properties, gint64 user_action_time, const gchar *preferred_handler, GHashTable *hints, GDBusMethodInvocation *context) { dispatcher_request_channel (MCD_DISPATCHER (iface), account_path, requested_properties, user_action_time, preferred_handler, hints, context, FALSE); } static void dispatcher_ensure_channel (TpSvcChannelDispatcher *iface, const gchar *account_path, GHashTable *requested_properties, gint64 user_action_time, const gchar *preferred_handler, GHashTable *hints, GDBusMethodInvocation *context) { dispatcher_request_channel (MCD_DISPATCHER (iface), account_path, requested_properties, user_action_time, preferred_handler, hints, context, TRUE); } static void mcd_dispatcher_lost_connection (gpointer data, GObject *corpse) { McdDispatcher *self = MCD_DISPATCHER (data); /* not safe to dereference corpse any more, so just print its address - * that's enough to pair up with add_connection calls */ DEBUG ("%p: %p", self, corpse); g_hash_table_remove (self->priv->connections, corpse); g_object_unref (self); } /* FIXME: this only needs to exist because McdConnection calls it in order * to preload caps before Connect */ GPtrArray * _mcd_dispatcher_dup_client_caps (McdDispatcher *self) { g_return_val_if_fail (MCD_IS_DISPATCHER (self), NULL); /* if we're not ready, return NULL to tell the connection not to preload */ if (!_mcd_client_registry_is_ready (self->priv->clients)) { return NULL; } return _mcd_client_registry_dup_client_caps (self->priv->clients); } void _mcd_dispatcher_add_connection (McdDispatcher *self, McdConnection *connection) { g_return_if_fail (MCD_IS_DISPATCHER (self)); DEBUG ("%p: %p (%s)", self, connection, mcd_connection_get_object_path (connection)); g_hash_table_insert (self->priv->connections, connection, connection); g_object_weak_ref ((GObject *) connection, mcd_dispatcher_lost_connection, g_object_ref (self)); if (_mcd_client_registry_is_ready (self->priv->clients)) { GPtrArray *vas = _mcd_client_registry_dup_client_caps (self->priv->clients); _mcd_connection_start_dispatching (connection, vas); g_ptr_array_foreach (vas, (GFunc) g_value_array_free, NULL); g_ptr_array_unref (vas); } /* else _mcd_connection_start_dispatching will be called when we're ready * for it */ } /* ChannelDispatcher.Messages */ typedef struct { McdDispatcher *dispatcher; gchar *account_path; gchar *target_id; GPtrArray *payload; guint flags; guint tries; gboolean close_after; GDBusMethodInvocation *dbus_context; } MessageContext; static MessageContext * message_context_new (McdDispatcher *dispatcher, const gchar *account_path, const gchar *target_id, const GPtrArray *payload, guint flags) { guint i; const guint size = payload->len; MessageContext *context = g_slice_new0 (MessageContext); GPtrArray *msg_copy = g_ptr_array_sized_new (size); g_ptr_array_set_free_func (msg_copy, (GDestroyNotify) g_hash_table_unref); for (i = 0; i < size; i++) { GHashTable *part = g_ptr_array_index (payload, i); g_ptr_array_add (msg_copy, _mcd_deepcopy_asv (part)); } context->dispatcher = g_object_ref (dispatcher); context->account_path = g_strdup (account_path); context->target_id = g_strdup (target_id); context->payload = msg_copy; context->flags = flags; context->dbus_context = NULL; return context; } static void message_context_return_error (MessageContext *context, const GError *error) { if (context->dbus_context == NULL) return; g_dbus_method_invocation_return_gerror (context->dbus_context, error); context->dbus_context = NULL; } static void message_context_set_return_context (MessageContext *context, GDBusMethodInvocation *dbus_context) { context->dbus_context = dbus_context; } static void message_context_free (gpointer ctx) { MessageContext *context = ctx; tp_clear_pointer (&context->payload, g_ptr_array_unref); tp_clear_pointer (&context->account_path, g_free); tp_clear_pointer (&context->target_id, g_free); if (context->dbus_context != NULL) { GError *error; error = g_error_new_literal (TP_ERROR, TP_ERROR_TERMINATED, "Channel request failed"); g_dbus_method_invocation_return_gerror (context->dbus_context, error); g_error_free (error); } tp_clear_object (&context->dispatcher); g_slice_free (MessageContext, context); } static void send_message_submitted (TpChannel *proxy, const gchar *token, const GError *error, gpointer data, GObject *weak) { MessageContext *message = data; GDBusMethodInvocation *context = message->dbus_context; McdChannel *channel = MCD_CHANNEL (weak); McdRequest *request = _mcd_channel_get_request (channel); gboolean close_after = message->close_after; /* this frees the dbus context, so clear it from our cache afterwards */ if (error == NULL) { tp_svc_channel_dispatcher_interface_messages1_return_from_send_message (context, token); message_context_set_return_context (message, NULL); } else { DEBUG ("error: %s", error->message); message_context_return_error (message, error); } _mcd_request_unblock_account (message->account_path); _mcd_request_clear_internal_handler (request); if (close_after) _mcd_channel_close (channel); } static void messages_send_message_start (GDBusMethodInvocation *context, MessageContext *message); static void send_message_got_channel (McdRequest *request, McdChannel *channel, gpointer data, gboolean close_after) { MessageContext *message = data; DEBUG ("received internal request/channel"); /* successful channel creation */ if (channel != NULL) { message->close_after = close_after; DEBUG ("calling send on channel interface"); tp_cli_channel_type_text_call_send_message (mcd_channel_get_tp_channel (channel), -1, message->payload, message->flags, send_message_submitted, message, NULL, G_OBJECT (channel)); } else /* doom and despair: no channel */ { if (message->tries++ == 0) { messages_send_message_start (message->dbus_context, message); /* we created a new lock above, we can now release the old one: */ _mcd_request_unblock_account (message->account_path); } else { GError *error = g_error_new_literal (TP_ERROR, TP_ERROR_CANCELLED, "Channel closed by owner"); _mcd_request_unblock_account (message->account_path); message_context_return_error (message, error); _mcd_request_clear_internal_handler (request); g_error_free (error); } } } static void messages_send_message_start (GDBusMethodInvocation *dbus_context, MessageContext *message) { McdAccountManager *am; McdAccount *account; McdChannel *channel = NULL; McdRequest *request = NULL; GError *error = NULL; GHashTable *props = NULL; GValue c_type = G_VALUE_INIT; GValue h_type = G_VALUE_INIT; GValue target = G_VALUE_INIT; McdDispatcher *self = message->dispatcher; DEBUG ("messages_send_message_acl_success [attempt #%u]", message->tries); /* the message request can now take posession of the dbus method context */ message_context_set_return_context (message, dbus_context); if (tp_str_empty (message->account_path)) { g_set_error_literal (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Account path not specified"); goto failure; } g_object_get (self->priv->master, "account-manager", &am, NULL); g_assert (am != NULL); account = mcd_account_manager_lookup_account_by_path (am, message->account_path); if (account == NULL) { g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "No such account: %s", message->account_path); goto failure; } props = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_value_unset); g_value_init (&c_type, G_TYPE_STRING); g_value_init (&h_type, G_TYPE_UINT); g_value_init (&target, G_TYPE_STRING); g_value_set_static_string (&c_type, TP_IFACE_CHANNEL_TYPE_TEXT); g_value_set_uint (&h_type, TP_ENTITY_TYPE_CONTACT); g_value_set_string (&target, message->target_id); g_hash_table_insert (props, TP_PROP_CHANNEL_CHANNEL_TYPE, &c_type); g_hash_table_insert (props, TP_PROP_CHANNEL_TARGET_ENTITY_TYPE, &h_type); g_hash_table_insert (props, TP_PROP_CHANNEL_TARGET_ID, &target); /* compare dispatcher_request_channel: we _are_ the handler for * * this channel so we don't need to check_preferred_handler here * * Also: this deep-copies the props hash, so we can throw ours away */ channel = _mcd_account_create_request (self->priv->clients, account, props, time (NULL), NULL, NULL, TRUE, &request, &error); g_hash_table_unref (props); if (channel == NULL || request == NULL) { g_set_error (&error, TP_ERROR, TP_ERROR_RESOURCE_UNAVAILABLE, "Could not create channel request"); goto failure; } _mcd_request_set_internal_handler (request, send_message_got_channel, message_context_free, message); /* we don't need to predict the handler either, same reason as above * * we do, however, want to call proceed on the request, as it is ours */ _mcd_request_proceed (request, NULL); goto finished; failure: message_context_return_error (message, error); message_context_free (message); g_error_free (error); finished: /* these are reffed and held open by the request infrastructure */ tp_clear_object (&channel); tp_clear_object (&request); } static void messages_send_message (TpSvcChannelDispatcherInterfaceMessages1 *iface, const gchar *account_path, const gchar *target_id, const GPtrArray *payload, guint flags, GDBusMethodInvocation *context) { McdDispatcher *self= MCD_DISPATCHER (iface); MessageContext *message = message_context_new (self, account_path, target_id, payload, flags); messages_send_message_start (context, message); } static void messages_iface_init (gpointer iface, gpointer data G_GNUC_UNUSED) { #define IMPLEMENT(x) \ tp_svc_channel_dispatcher_interface_messages1_implement_##x (iface, messages_##x) IMPLEMENT (send_message); #undef IMPLEMENT } typedef struct { McdDispatcher *self; gint64 user_action_time; GDBusMethodInvocation *context; /* List of owned ChannelToDelegate */ GList *channels; /* array of owned channel path */ GPtrArray *delegated; /* owned channel path -> owned GValueArray representing a * TP_STRUCT_TYPE_NOT_DELEGATED_ERROR */ GHashTable *not_delegated; } DelegateChannelsCtx; typedef struct { /* borrowed reference */ DelegateChannelsCtx *ctx; McdAccount *account; McdChannel *channel; /* Queue of reffed McdClientProxy */ GQueue *handlers; GError *error; } ChannelToDelegate; static ChannelToDelegate * channel_to_delegate_new (DelegateChannelsCtx *ctx, McdAccount *account, McdChannel *channel) { ChannelToDelegate *chan = g_slice_new0 (ChannelToDelegate); chan->ctx = ctx; chan->account = g_object_ref (account); chan->channel = g_object_ref (channel); chan->handlers = g_queue_new (); chan->error = NULL; return chan; } static void channel_to_delegate_free (ChannelToDelegate *chan) { g_object_unref (chan->account); g_object_unref (chan->channel); g_queue_foreach (chan->handlers, (GFunc) g_object_unref, NULL); g_queue_free (chan->handlers); tp_clear_pointer (&chan->error, g_error_free); g_slice_free (ChannelToDelegate, chan); } static void free_not_delegated_error (gpointer data) { g_boxed_free (TP_STRUCT_TYPE_NOT_DELEGATED_ERROR, data); } static DelegateChannelsCtx * delegate_channels_ctx_new (McdDispatcher *self, gint64 user_action_time, GDBusMethodInvocation *context) { DelegateChannelsCtx *ctx = g_slice_new0 (DelegateChannelsCtx); ctx->self = g_object_ref (self); ctx->user_action_time = user_action_time; ctx->context = context; ctx->channels = NULL; ctx->delegated = g_ptr_array_new_with_free_func (g_free); ctx->not_delegated = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_not_delegated_error); return ctx; } static void delegate_channels_ctx_free (DelegateChannelsCtx *ctx) { g_object_unref (ctx->self); g_ptr_array_unref (ctx->delegated); g_hash_table_unref (ctx->not_delegated); g_list_free_full (ctx->channels, (GDestroyNotify) channel_to_delegate_free); g_slice_free (DelegateChannelsCtx, ctx); } static void try_delegating (ChannelToDelegate *to_delegate); static void delegation_done (ChannelToDelegate *to_delegate) { DelegateChannelsCtx *ctx = to_delegate->ctx; ctx->channels = g_list_remove (ctx->channels, to_delegate); channel_to_delegate_free (to_delegate); if (ctx->channels == NULL) { /* We are done */ tp_svc_channel_dispatcher_return_from_delegate_channels ( ctx->context, ctx->delegated, ctx->not_delegated); delegate_channels_ctx_free (ctx); } } static void delegate_channels_cb (TpClient *client, const GError *error, gpointer user_data G_GNUC_UNUSED, GObject *weak_object) { ChannelToDelegate *to_delegate = user_data; DelegateChannelsCtx *ctx = to_delegate->ctx; McdClientProxy *clt_proxy = MCD_CLIENT_PROXY (client); /* If the delegation succeeded, the channel has a new handler. If * the delegation failed, the channel still has the old * handler. Either way, the channel still has a handler, so it has * been successfully dispatched (from 'handler invoked'). */ _mcd_channel_set_status (to_delegate->channel, MCD_CHANNEL_STATUS_DISPATCHED); if (error != NULL) { DEBUG ("Handler refused delegated channels"); if (to_delegate->error == NULL) to_delegate->error = g_error_copy (error); try_delegating (to_delegate); return; } DEBUG ("Channel %s has been delegated", mcd_channel_get_object_path ( to_delegate->channel)); _mcd_handler_map_set_path_handled (ctx->self->priv->handler_map, mcd_channel_get_object_path (to_delegate->channel), _mcd_client_proxy_get_unique_name (clt_proxy), tp_proxy_get_bus_name (client)); g_ptr_array_add (ctx->delegated, g_strdup ( mcd_channel_get_object_path (to_delegate->channel))); delegation_done (to_delegate); } static void try_delegating (ChannelToDelegate *to_delegate) { McdClientProxy *client; DEBUG ("%s", mcd_channel_get_object_path (to_delegate->channel)); if (g_queue_get_length (to_delegate->handlers) == 0) { GValueArray *v; const gchar *dbus_error; if (to_delegate->error == NULL) { g_set_error (&to_delegate->error, TP_ERROR, TP_ERROR_NOT_CAPABLE, "There is no other suitable handler"); } if (to_delegate->error->domain == TP_ERROR) dbus_error = tp_error_get_dbus_name (to_delegate->error->code); else dbus_error = TP_ERROR_STR_NOT_AVAILABLE; /* We failed to delegate this channel */ v = tp_value_array_build (2, G_TYPE_STRING, dbus_error, G_TYPE_STRING, to_delegate->error->message, G_TYPE_INVALID); g_hash_table_insert (to_delegate->ctx->not_delegated, g_strdup (mcd_channel_get_object_path (to_delegate->channel)), v); DEBUG ("...but failed to delegate %s: %s", mcd_channel_get_object_path (to_delegate->channel), to_delegate->error->message); delegation_done (to_delegate); return; } client = g_queue_pop_head (to_delegate->handlers); DEBUG ("...trying client %s", _mcd_client_proxy_get_unique_name ( client)); _mcd_client_proxy_handle_channel (client, -1, to_delegate->channel, to_delegate->ctx->user_action_time, NULL, delegate_channels_cb, to_delegate, NULL, NULL); g_object_unref (client); } static void add_possible_handlers (McdDispatcher *self, ChannelToDelegate *to_delegate, TpChannel *tp_channel, const gchar *sender, const gchar *preferred_handler) { GStrv possible_handlers; guint i; possible_handlers = mcd_dispatcher_dup_possible_handlers (self, NULL, tp_channel, NULL); for (i = 0; possible_handlers[i] != NULL; i++) { McdClientProxy *client; const gchar *unique_name; client = _mcd_client_registry_lookup (self->priv->clients, possible_handlers[i]); g_return_if_fail (client != NULL); unique_name = _mcd_client_proxy_get_unique_name (client); /* Skip the caller */ if (!tp_strdiff (unique_name, sender)) continue; /* Put the preferred handler at the head of the list so it will be tried * first */ if (!tp_strdiff (possible_handlers[i], preferred_handler)) g_queue_push_head (to_delegate->handlers, g_object_ref (client)); else g_queue_push_tail (to_delegate->handlers, g_object_ref (client)); } g_strfreev (possible_handlers); } static void dispatcher_delegate_channels ( TpSvcChannelDispatcher *iface, const GPtrArray *channels, gint64 user_action_time, const gchar *preferred_handler, GDBusMethodInvocation *context) { McdDispatcher *self = (McdDispatcher *) iface; GError *error = NULL; const gchar *sender; McdConnection *conn = NULL; DelegateChannelsCtx *ctx = NULL; McdAccountManager *am; guint i; GList *l; DEBUG ("called"); if (!check_preferred_handler (preferred_handler, &error)) goto error; if (channels->len == 0) { g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Need at least one channel to delegate"); goto error; } ctx = delegate_channels_ctx_new (self, user_action_time, context); sender = g_dbus_method_invocation_get_sender (context); g_object_get (self->priv->master, "account-manager", &am, NULL); g_assert (am != NULL); for (i = 0; i < channels->len; i++) { const gchar *chan_path = g_ptr_array_index (channels, i); const gchar *chan_account; const gchar *handler; McdChannel *mcd_channel; TpChannel *tp_channel; McdAccount *account; ChannelToDelegate *to_delegate; chan_account = _mcd_handler_map_get_channel_account ( self->priv->handler_map, chan_path); if (chan_account == NULL) { g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Unknown channel: %s", chan_path); goto error; } account = mcd_account_manager_lookup_account_by_path (am, chan_account); g_return_if_fail (account != NULL); /* Check the caller is handling the channel */ handler = _mcd_handler_map_get_handler (self->priv->handler_map, chan_path, NULL); if (tp_strdiff (sender, handler)) { g_set_error (&error, TP_ERROR, TP_ERROR_NOT_YOURS, "Your are not handling channel %s", chan_path); goto error; } conn = mcd_account_get_connection (account); g_return_if_fail (conn != NULL); mcd_channel = mcd_connection_find_channel_by_path (conn, chan_path); g_return_if_fail (mcd_channel != NULL); tp_channel = mcd_channel_get_tp_channel (mcd_channel); g_return_if_fail (tp_channel != NULL); to_delegate = channel_to_delegate_new (ctx, account, mcd_channel); add_possible_handlers (self, to_delegate, tp_channel, sender, preferred_handler); ctx->channels = g_list_prepend (ctx->channels, to_delegate); } /* All the channels were ok, we can start delegating */ for (l = ctx->channels; l != NULL; l = g_list_next (l)) { ChannelToDelegate *to_delegate = l->data; try_delegating (to_delegate); } g_object_unref (am); return; error: g_dbus_method_invocation_return_gerror (context, error); g_error_free (error); tp_clear_pointer (&ctx, delegate_channels_ctx_free); tp_clear_object (&am); } static void present_handle_channel_cb (TpClient *client, const GError *error, gpointer user_data, GObject *weak_object) { GDBusMethodInvocation *context = user_data; McdChannel *mcd_channel = MCD_CHANNEL (weak_object); /* Whether presenting the channel succeeded or failed, the * channel's handler hasn't been altered, so it must be set back * to the dispatched state (from 'handler invoked'). */ _mcd_channel_set_status (mcd_channel, MCD_CHANNEL_STATUS_DISPATCHED); if (error != NULL) { g_dbus_method_invocation_return_gerror (context, error); return; } tp_svc_channel_dispatcher_return_from_present_channel (context); } static void dispatcher_present_channel ( TpSvcChannelDispatcher *iface, const gchar *channel_path, gint64 user_action_time, GDBusMethodInvocation *context) { McdDispatcher *self = (McdDispatcher *) iface; McdAccountManager *am; const gchar *chan_account; McdAccount *account; McdConnection *conn; McdChannel *mcd_channel; GError *error = NULL; McdClientProxy *client; chan_account = _mcd_handler_map_get_channel_account ( self->priv->handler_map, channel_path); if (chan_account == NULL) { g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Unknown channel: %s", channel_path); goto error; } g_object_get (self->priv->master, "account-manager", &am, NULL); g_assert (am != NULL); account = mcd_account_manager_lookup_account_by_path (am, chan_account); g_return_if_fail (account != NULL); g_object_unref (am); conn = mcd_account_get_connection (account); g_return_if_fail (conn != NULL); mcd_channel = mcd_connection_find_channel_by_path (conn, channel_path); g_return_if_fail (mcd_channel != NULL); /* We take mcd_channel's request to base the search for a suitable Handler * on the handler that was preferred by the request that initially created * the Channel, if any. * Actually not, because of fd.o#41031 */ client = _mcd_dispatcher_lookup_handler (self, mcd_channel_get_tp_channel (mcd_channel), _mcd_channel_get_request (mcd_channel)); if (client == NULL) { g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Channel %s is currently not handled", channel_path); goto error; } _mcd_client_proxy_handle_channel (client, -1, mcd_channel, user_action_time, NULL, present_handle_channel_cb, context, NULL, G_OBJECT (mcd_channel)); return; error: g_dbus_method_invocation_return_gerror (context, error); g_error_free (error); } static void dispatcher_iface_init (gpointer g_iface, gpointer iface_data G_GNUC_UNUSED) { #define IMPLEMENT(x) tp_svc_channel_dispatcher_implement_##x (\ g_iface, dispatcher_##x) IMPLEMENT (create_channel); IMPLEMENT (ensure_channel); IMPLEMENT (delegate_channels); IMPLEMENT (present_channel); #undef IMPLEMENT }