From 9940bfed4c589710add05be776b7b1817bf9d48d Mon Sep 17 00:00:00 2001 From: Sjoerd Simons Date: Fri, 9 Jan 2009 16:12:07 +0000 Subject: Start reworking the dispatcher Rework the channel dispatche so it's model is somewhat similar to the ChannelDispatcher in the telepathy-spec. Which allows for a much cleaner and extensible implementation containg no ugly hacks. Currently it's only able to do Text Channels and a lot of the other functionality is temporary disabled Signed-off-by: Sjoerd Simons svn path=/trunk/; revision=2130 --- libempathy/Makefile.am | 2 + libempathy/empathy-dispatch-operation.c | 519 +++++++++++++ libempathy/empathy-dispatch-operation.h | 118 +++ libempathy/empathy-dispatcher.c | 1215 ++++++++++++++++++++++--------- libempathy/empathy-dispatcher.h | 44 +- 5 files changed, 1546 insertions(+), 352 deletions(-) create mode 100644 libempathy/empathy-dispatch-operation.c create mode 100644 libempathy/empathy-dispatch-operation.h (limited to 'libempathy') diff --git a/libempathy/Makefile.am b/libempathy/Makefile.am index c1bdd6248..7ca8b7104 100644 --- a/libempathy/Makefile.am +++ b/libempathy/Makefile.am @@ -28,6 +28,7 @@ libempathy_la_SOURCES = \ empathy-contact-monitor.c \ empathy-debug.c \ empathy-dispatcher.c \ + empathy-dispatch-operation.c \ empathy-idle.c \ empathy-irc-network.c \ empathy-irc-network-manager.c \ @@ -71,6 +72,7 @@ libempathy_headers = \ empathy-contact-monitor.h \ empathy-debug.h \ empathy-dispatcher.h \ + empathy-dispatch-operation.h \ empathy-idle.h \ empathy-irc-network.h \ empathy-irc-network-manager.h \ diff --git a/libempathy/empathy-dispatch-operation.c b/libempathy/empathy-dispatch-operation.c new file mode 100644 index 000000000..7f3357b8f --- /dev/null +++ b/libempathy/empathy-dispatch-operation.c @@ -0,0 +1,519 @@ +/* + * empathy-dispatch-operation.c - Source for EmpathyDispatchOperation + * Copyright (C) 2008 Collabora Ltd. + * @author Sjoerd Simons + * + * 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 +#include + +#include "empathy-dispatch-operation.h" +#include +#include +#include + +#include "empathy-marshal.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER +#include + +G_DEFINE_TYPE(EmpathyDispatchOperation, empathy_dispatch_operation, + G_TYPE_OBJECT) + +static void empathy_dispatch_operation_set_status ( + EmpathyDispatchOperation *self, EmpathyDispatchOperationState status); +static void empathy_dispatch_operation_channel_ready_cb (TpChannel *channel, + const GError *error, gpointer user_data); + +/* signal enum */ +enum +{ + /* Ready for dispatching */ + READY, + /* Approved by an approver, can only happens on incoming operations */ + APPROVED, + /* Claimed by a handler */ + CLAIMED, + /* Error, channel went away, inspecting it failed etc */ + INVALIDATED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +/* properties */ +enum { + PROP_CONNECTION = 1, + PROP_CHANNEL, + PROP_CHANNEL_WRAPPER, + PROP_CONTACT, + PROP_INCOMING, + PROP_STATUS, +}; + +/* private structure */ +typedef struct _EmpathyDispatchOperationPriv \ + EmpathyDispatchOperationPriv; + +struct _EmpathyDispatchOperationPriv +{ + gboolean dispose_has_run; + TpConnection *connection; + TpChannel *channel; + GObject *channel_wrapper; + EmpathyContact *contact; + EmpathyDispatchOperationState status; + gboolean incoming; + gboolean approved; + gulong invalidated_handler; +}; + +#define GET_PRIV(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_DISPATCH_OPERATION, \ + EmpathyDispatchOperationPriv)) + +static void +empathy_dispatch_operation_init (EmpathyDispatchOperation *obj) +{ + //EmpathyDispatchOperationPriv *priv = + // GET_PRIV (obj); + + /* allocate any data required by the object here */ +} + +static void empathy_dispatch_operation_dispose (GObject *object); +static void empathy_dispatch_operation_finalize (GObject *object); + +static void +empathy_dispatch_operation_set_property (GObject *object, + guint property_id, const GValue *value, GParamSpec *pspec) +{ + EmpathyDispatchOperation *operation = EMPATHY_DISPATCH_OPERATION (object); + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + + switch (property_id) + { + case PROP_CONNECTION: + priv->connection = g_value_dup_object (value); + break; + case PROP_CHANNEL: + priv->channel = g_value_dup_object (value); + break; + case PROP_CHANNEL_WRAPPER: + priv->channel_wrapper = g_value_dup_object (value); + break; + case PROP_CONTACT: + if (priv->contact != NULL) + g_object_unref (priv->contact); + priv->contact = g_value_dup_object (value); + break; + case PROP_INCOMING: + priv->incoming = g_value_get_boolean (value); + break; + } +} + +static void +empathy_dispatch_operation_get_property (GObject *object, + guint property_id, GValue *value, GParamSpec *pspec) +{ + EmpathyDispatchOperation *operation = EMPATHY_DISPATCH_OPERATION (object); + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + + switch (property_id) + { + case PROP_CONNECTION: + g_value_set_object (value, priv->connection); + break; + case PROP_CHANNEL: + g_value_set_object (value, priv->channel); + break; + case PROP_CHANNEL_WRAPPER: + g_value_set_object (value, priv->channel_wrapper); + break; + case PROP_CONTACT: + g_value_set_object (value, priv->contact); + break; + case PROP_INCOMING: + g_value_set_boolean (value, priv->incoming); + break; + case PROP_STATUS: + g_value_set_enum (value, priv->status); + break; + } +} + +static void +empathy_dispatch_operation_invalidated (TpProxy *proxy, guint domain, + gint code, char *message, EmpathyDispatchOperation *self) +{ + empathy_dispatch_operation_set_status (self, + EMPATHY_DISPATCHER_OPERATION_STATE_INVALIDATED); + + g_signal_emit (self, signals[INVALIDATED], 0, domain, code, message); +} + +static void +empathy_dispatch_operation_constructed (GObject *object) +{ + EmpathyDispatchOperation *self = EMPATHY_DISPATCH_OPERATION (object); + EmpathyDispatchOperationPriv *priv = GET_PRIV (self); + + empathy_dispatch_operation_set_status (self, + EMPATHY_DISPATCHER_OPERATION_STATE_PREPARING); + + priv->invalidated_handler = + g_signal_connect (priv->channel, "invalidated", + G_CALLBACK (empathy_dispatch_operation_invalidated), self); + + tp_channel_call_when_ready (priv->channel, + empathy_dispatch_operation_channel_ready_cb, self); +} + +static void +empathy_dispatch_operation_class_init ( + EmpathyDispatchOperationClass *empathy_dispatch_operation_class) +{ + GObjectClass *object_class = + G_OBJECT_CLASS (empathy_dispatch_operation_class); + GParamSpec *param_spec; + + g_type_class_add_private (empathy_dispatch_operation_class, + sizeof (EmpathyDispatchOperationPriv)); + + object_class->set_property = empathy_dispatch_operation_set_property; + object_class->get_property = empathy_dispatch_operation_get_property; + + object_class->dispose = empathy_dispatch_operation_dispose; + object_class->finalize = empathy_dispatch_operation_finalize; + object_class->constructed = empathy_dispatch_operation_constructed; + + signals[READY] = g_signal_new ("ready", + G_OBJECT_CLASS_TYPE(empathy_dispatch_operation_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[APPROVED] = g_signal_new ("approved", + G_OBJECT_CLASS_TYPE(empathy_dispatch_operation_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[CLAIMED] = g_signal_new ("claimed", + G_OBJECT_CLASS_TYPE(empathy_dispatch_operation_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[INVALIDATED] = g_signal_new ("invalidated", + G_OBJECT_CLASS_TYPE(empathy_dispatch_operation_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _empathy_marshal_VOID__UINT_INT_STRING, + G_TYPE_NONE, 0); + + param_spec = g_param_spec_object ("connection", + "connection", "The telepathy connection", + TP_TYPE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION, + param_spec); + + param_spec = g_param_spec_object ("channel", + "channel", "The telepathy channel", + TP_TYPE_CHANNEL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CHANNEL, + param_spec); + + param_spec = g_param_spec_object ("channel-wrapper", + "channel wrapper", "The empathy specific channel wrapper", + G_TYPE_OBJECT, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CHANNEL_WRAPPER, + param_spec); + + param_spec = g_param_spec_object ("contact", + "contact", "The empathy contact", + EMPATHY_TYPE_CONTACT, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONTACT, + param_spec); + + param_spec = g_param_spec_boolean ("incoming", + "incoming", "Whether or not the channel is incoming", + FALSE, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INCOMING, + param_spec); + + param_spec = g_param_spec_enum ("status", + "status", "Status of the dispatch operation", + EMPATHY_TYPE_DISPATCH_OPERATION_STATE, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_STATUS, param_spec); +} + +void +empathy_dispatch_operation_dispose (GObject *object) +{ + EmpathyDispatchOperation *self = EMPATHY_DISPATCH_OPERATION (object); + EmpathyDispatchOperationPriv *priv = + GET_PRIV (self); + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + g_object_unref (priv->connection); + + g_signal_handler_disconnect (priv->channel, priv->invalidated_handler); + g_object_unref (priv->channel); + + if (priv->contact != NULL) + g_object_unref (priv->contact); + + if (G_OBJECT_CLASS (empathy_dispatch_operation_parent_class)->dispose) + G_OBJECT_CLASS (empathy_dispatch_operation_parent_class)->dispose (object); +} + +void +empathy_dispatch_operation_finalize (GObject *object) +{ + /* free any data held directly by the object here */ + G_OBJECT_CLASS (empathy_dispatch_operation_parent_class)->finalize (object); +} + +static void +empathy_dispatch_operation_set_status (EmpathyDispatchOperation *self, + EmpathyDispatchOperationState status) +{ + EmpathyDispatchOperationPriv *priv = GET_PRIV (self); + + g_assert (status >= priv->status); + + + if (priv->status != status) + { + DEBUG ("Dispatch operation %s status: %d -> %d", + empathy_dispatch_operation_get_object_path (self), + priv->status, status); + + priv->status = status; + g_object_notify (G_OBJECT (self), "status"); + + if (status == EMPATHY_DISPATCHER_OPERATION_STATE_PENDING) + g_signal_emit (self, signals[READY], 0); + } +} + +static void +empathy_dispatch_operation_channel_ready_cb (TpChannel *channel, + const GError *error, gpointer user_data) +{ + EmpathyDispatchOperation *self = EMPATHY_DISPATCH_OPERATION (user_data); + EmpathyDispatchOperationPriv *priv = GET_PRIV (self); + GQuark channel_type; + + g_assert (channel == priv->channel); + + if (priv->channel_wrapper != NULL) + goto out; + + channel_type = tp_channel_get_channel_type_id (channel); + + if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_TEXT) + { + EmpathyTpChat *chat= empathy_tp_chat_new (channel); + priv->channel_wrapper = G_OBJECT (chat); + } + else if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA) + { + EmpathyTpCall *call = empathy_tp_call_new (channel); + priv->channel_wrapper = G_OBJECT (call); + } + +out: + empathy_dispatch_operation_set_status (self, + EMPATHY_DISPATCHER_OPERATION_STATE_PENDING); +} + +EmpathyDispatchOperation * +empathy_dispatch_operation_new (TpConnection *connection, TpChannel *channel, + EmpathyContact *contact, gboolean incoming) +{ + return empathy_dispatch_operation_new_with_wrapper (connection, channel, + contact, incoming, NULL); +} + +EmpathyDispatchOperation * +empathy_dispatch_operation_new_with_wrapper (TpConnection *connection, + TpChannel *channel, EmpathyContact *contact, gboolean incoming, + GObject *wrapper) +{ + g_return_val_if_fail (connection != NULL, NULL); + g_return_val_if_fail (channel != NULL, NULL); + + return EMPATHY_DISPATCH_OPERATION ( + g_object_new (EMPATHY_TYPE_DISPATCH_OPERATION, + "connection", connection, + "channel", channel, + "channel-wrapper", wrapper, + "contact", contact, + "incoming", incoming, + NULL)); +} + +void +empathy_dispatch_operation_start (EmpathyDispatchOperation *operation) +{ + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + g_return_if_fail ( + priv->status == EMPATHY_DISPATCHER_OPERATION_STATE_PENDING); + + if (priv->incoming && !priv->approved) + empathy_dispatch_operation_set_status (operation, + EMPATHY_DISPATCHER_OPERATION_STATE_APPROVING); + else + empathy_dispatch_operation_set_status (operation, + EMPATHY_DISPATCHER_OPERATION_STATE_DISPATCHING); +} + +void +empathy_dispatch_operation_approve (EmpathyDispatchOperation *operation) +{ + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + + if (priv->status == EMPATHY_DISPATCHER_OPERATION_STATE_APPROVING) + { + DEBUG ("Approving operation %s", + empathy_dispatch_operation_get_object_path (operation)); + + empathy_dispatch_operation_set_status (operation, + EMPATHY_DISPATCHER_OPERATION_STATE_DISPATCHING); + + g_signal_emit (operation, signals[APPROVED], 0); + } + else + { + DEBUG ("Pre-approving operation %s", + empathy_dispatch_operation_get_object_path (operation)); + priv->approved = TRUE; + } +} + +/* Returns whether or not the operation was successfully claimed */ +gboolean +empathy_dispatch_operation_claim (EmpathyDispatchOperation *operation) +{ + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + + if (priv->status == EMPATHY_DISPATCHER_OPERATION_STATE_CLAIMED) + return FALSE; + + empathy_dispatch_operation_set_status (operation, + EMPATHY_DISPATCHER_OPERATION_STATE_CLAIMED); + + g_signal_emit (operation, signals[CLAIMED], 0); + + return TRUE; +} + +TpConnection * +empathy_dispatch_operation_get_tp_connection ( + EmpathyDispatchOperation *operation) +{ + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + + return g_object_ref (priv->connection); +} + +TpChannel * +empathy_dispatch_operation_get_channel (EmpathyDispatchOperation *operation) +{ + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + + return TP_CHANNEL (g_object_ref (priv->channel)); +} + +GObject * +empathy_dispatch_operation_get_channel_wrapper ( + EmpathyDispatchOperation *operation) +{ + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + + if (priv->channel_wrapper != NULL) + g_object_ref (priv->channel_wrapper); + + return priv->channel_wrapper; +} + +const gchar * +empathy_dispatch_operation_get_channel_type ( + EmpathyDispatchOperation *operation) +{ + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + + return tp_channel_get_channel_type (priv->channel); +} + +GQuark +empathy_dispatch_operation_get_channel_type_id ( + EmpathyDispatchOperation *operation) +{ + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + + return tp_channel_get_channel_type_id (priv->channel); +} + +const gchar * +empathy_dispatch_operation_get_object_path ( + EmpathyDispatchOperation *operation) +{ + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + + return tp_proxy_get_object_path (TP_PROXY (priv->channel)); +} + +EmpathyDispatchOperationState +empathy_dispatch_operation_get_status (EmpathyDispatchOperation *operation) +{ + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + + return priv->status; +} + +gboolean +empathy_dispatch_operation_is_incoming (EmpathyDispatchOperation *operation) +{ + EmpathyDispatchOperationPriv *priv = GET_PRIV (operation); + + return priv->incoming; +} diff --git a/libempathy/empathy-dispatch-operation.h b/libempathy/empathy-dispatch-operation.h new file mode 100644 index 000000000..182fa3790 --- /dev/null +++ b/libempathy/empathy-dispatch-operation.h @@ -0,0 +1,118 @@ +/* + * empathy-dispatch-operation.h - Header for EmpathyDispatchOperation + * Copyright (C) 2008 Collabora Ltd. + * @author Sjoerd Simons + * + * 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 + */ + +#ifndef __EMPATHY_DISPATCH_OPERATION_H__ +#define __EMPATHY_DISPATCH_OPERATION_H__ + +#include + +#include + +G_BEGIN_DECLS + +typedef struct _EmpathyDispatchOperation EmpathyDispatchOperation; +typedef struct _EmpathyDispatchOperationClass EmpathyDispatchOperationClass; + +struct _EmpathyDispatchOperationClass { + GObjectClass parent_class; +}; + +struct _EmpathyDispatchOperation { + GObject parent; +}; + +typedef enum { + /* waiting for the channel information to be ready */ + EMPATHY_DISPATCHER_OPERATION_STATE_PREPARING = 0, + /* Information gathered ready to be dispatched */ + EMPATHY_DISPATCHER_OPERATION_STATE_PENDING, + /* Send to approving bits for approval */ + EMPATHY_DISPATCHER_OPERATION_STATE_APPROVING, + /* Send to handlers for dispatching */ + EMPATHY_DISPATCHER_OPERATION_STATE_DISPATCHING, + /* somebody claimed the channel */ + EMPATHY_DISPATCHER_OPERATION_STATE_CLAIMED, + /* dispatch operation invalidated, underlying channel died */ + EMPATHY_DISPATCHER_OPERATION_STATE_INVALIDATED, +} EmpathyDispatchOperationState; + +GType empathy_dispatch_operation_get_type(void); + +/* TYPE MACROS */ +#define EMPATHY_TYPE_DISPATCH_OPERATION \ + (empathy_dispatch_operation_get_type()) +#define EMPATHY_DISPATCH_OPERATION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), EMPATHY_TYPE_DISPATCH_OPERATION, \ + EmpathyDispatchOperation)) +#define EMPATHY_DISPATCH_OPERATION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), EMPATHY_TYPE_DISPATCH_OPERATION, \ + EmpathyDispatchOperationClass)) +#define EMPATHY_IS_DISPATCH_OPERATION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), EMPATHY_TYPE_DISPATCH_OPERATION)) +#define EMPATHY_IS_DISPATCH_OPERATION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), EMPATHY_TYPE_DISPATCH_OPERATION)) +#define EMPATHY_DISPATCH_OPERATION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_DISPATCH_OPERATION, \ + EmpathyDispatchOperationClass)) + +EmpathyDispatchOperation *empathy_dispatch_operation_new ( + TpConnection *connection, TpChannel *channel, EmpathyContact *contact, + gboolean incoming); + +EmpathyDispatchOperation *empathy_dispatch_operation_new_with_wrapper ( + TpConnection *connection, TpChannel *channel, EmpathyContact *contact, + gboolean incoming, GObject *channel_wrapper); + +/* Start the dispatching process, goes to the APPROVING state for incoming + * channels and DISPATCHING for outgoing ones */ +void empathy_dispatch_operation_start (EmpathyDispatchOperation *operation); + +void empathy_dispatch_operation_approve (EmpathyDispatchOperation *operation); + +/* Returns whether or not the operation was successfully claimed */ +gboolean empathy_dispatch_operation_claim (EmpathyDispatchOperation *operation); + +TpChannel *empathy_dispatch_operation_get_channel ( + EmpathyDispatchOperation *operation); + +GObject *empathy_dispatch_operation_get_channel_wrapper ( + EmpathyDispatchOperation *operation); + +TpConnection *empathy_dispatch_operation_get_tp_connection ( + EmpathyDispatchOperation *operation); + +const gchar *empathy_dispatch_operation_get_channel_type ( + EmpathyDispatchOperation *operation); + +GQuark empathy_dispatch_operation_get_channel_type_id ( + EmpathyDispatchOperation *operation); + +const gchar * empathy_dispatch_operation_get_object_path ( + EmpathyDispatchOperation *operation); + +EmpathyDispatchOperationState empathy_dispatch_operation_get_status ( + EmpathyDispatchOperation *operation); + +gboolean empathy_dispatch_operation_is_incoming ( + EmpathyDispatchOperation *operation); + +G_END_DECLS + +#endif /* #ifndef __EMPATHY_DISPATCH_OPERATION_H__*/ diff --git a/libempathy/empathy-dispatcher.c b/libempathy/empathy-dispatcher.c index 623a24aef..441da69db 100644 --- a/libempathy/empathy-dispatcher.c +++ b/libempathy/empathy-dispatcher.c @@ -1,4 +1,3 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 2007-2008 Collabora Ltd. * @@ -51,41 +50,157 @@ #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyDispatcher) typedef struct { - GHashTable *connections; - EmpathyAccountManager *account_manager; - MissionControl *mc; - GSList *tubes; - EmpathyChatroomManager *chatroom_mgr; + EmpathyAccountManager *account_manager; + MissionControl *mc; + /* connection to connection data mapping */ + GHashTable *connections; + /* accounts to connection mapping */ + GHashTable *accounts; + gpointer token; + GSList *tubes; + EmpathyChatroomManager *chatroom_mgr; } EmpathyDispatcherPriv; G_DEFINE_TYPE (EmpathyDispatcher, empathy_dispatcher, G_TYPE_OBJECT); enum { - DISPATCH_CHANNEL, - FILTER_CHANNEL, - FILTER_TUBE, - LAST_SIGNAL + OBSERVE, + APPROVE, + DISPATCH, + LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; static EmpathyDispatcher *dispatcher = NULL; -void -empathy_dispatcher_channel_process (EmpathyDispatcher *dispatcher, - TpChannel *channel) +typedef struct { + EmpathyDispatcherTube public; + EmpathyContactFactory *factory; + gchar *bus_name; + gchar *object_path; + guint ref_count; + gboolean handled; +} DispatcherTube; + +typedef struct { + EmpathyDispatcher *dispatcher; + EmpathyDispatchOperation *operation; + TpConnection *connection; + gchar *channel_type; + guint handle_type; + guint handle; + EmpathyContact *contact; + EmpathyDispatcherRequestCb *cb; + gpointer user_data; + gpointer *request_data; +} DispatcherRequestData; + +typedef struct { + TpChannel *channel; + /* Channel type specific wrapper object */ + GObject *channel_wrapper; +} DispatchData; + +typedef struct { + McAccount *account; + /* ObjectPath => DispatchData.. */ + GHashTable *dispatched_channels; + /* ObjectPath -> EmpathyDispatchOperations */ + GHashTable *dispatching_channels; + /* ObjectPath -> EmpathyDispatchOperations */ + GHashTable *outstanding_channels; + /* List of DispatcherRequestData */ + GList *outstanding_requests; +} ConnectionData; + +static DispatchData * +new_dispatch_data (TpChannel *channel, GObject *channel_wrapper) { - g_signal_emit (dispatcher, signals[DISPATCH_CHANNEL], 0, channel); + DispatchData *d = g_slice_new0 (DispatchData); + d->channel = channel; + d->channel_wrapper = channel_wrapper; + + return d; } -typedef struct { - EmpathyDispatcherTube public; - EmpathyContactFactory *factory; - gchar *bus_name; - gchar *object_path; - guint ref_count; - gboolean handled; -} DispatcherTube; +static void +free_dispatch_data (DispatchData *data) +{ + g_object_unref (data->channel); + g_object_unref (data->channel_wrapper); + + g_slice_free (DispatchData, data); +} + + +static DispatcherRequestData * +new_dispatcher_request_data (EmpathyDispatcher *dispatcher, + TpConnection *connection, const gchar *channel_type, guint handle_type, + guint handle, EmpathyContact *contact, + EmpathyDispatcherRequestCb *cb, gpointer user_data) +{ + DispatcherRequestData *result = g_slice_new0 (DispatcherRequestData); + result->dispatcher = dispatcher; + result->connection = connection; + + result->channel_type = g_strdup (channel_type); + result->handle_type = handle_type; + result->handle = handle; + + if (contact != NULL) + result->contact = g_object_ref (contact); + + result->cb = cb; + result->user_data = user_data; + + return result; +} + +static void +free_dispatcher_request_data (DispatcherRequestData *r) +{ + g_free (r->channel_type); + + if (r->contact != NULL) + g_object_unref (r->contact); + + g_slice_free (DispatcherRequestData, r); +} + +static ConnectionData * +new_connection_data (McAccount *account) +{ + ConnectionData *cd = g_slice_new0 (ConnectionData); + cd->account = g_object_ref (account); + + cd->dispatched_channels = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) free_dispatch_data); + + cd->dispatching_channels = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); + + cd->outstanding_channels = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + return cd; +} + +static void +free_connection_data (ConnectionData *cd) +{ + GList *l; + g_object_unref (cd->account); + g_hash_table_destroy (cd->dispatched_channels); + g_hash_table_destroy (cd->dispatching_channels); + + for (l = cd->outstanding_requests ; l != NULL; l = g_list_delete_link (l,l)) + { + free_dispatcher_request_data (l->data); + } +} + +#if 0 GType empathy_dispatcher_tube_get_type (void) { @@ -152,11 +267,12 @@ dispatcher_tubes_handle_tube_cb (TpProxy *channel, } } + void empathy_dispatcher_tube_process (EmpathyDispatcher *dispatcher, - EmpathyDispatcherTube *user_data) + EmpathyDispatcherTube *etube) { - DispatcherTube *tube = (DispatcherTube*) user_data; + DispatcherTube *tube = (DispatcherTube*) etube; if (tube->public.activatable) { TpProxy *connection; @@ -188,7 +304,7 @@ empathy_dispatcher_tube_process (EmpathyDispatcher *dispatcher, object_path, handle_type, handle, tube->public.id, dispatcher_tubes_handle_tube_cb, - empathy_dispatcher_tube_ref (user_data), + empathy_dispatcher_tube_ref (etube), (GDestroyNotify) empathy_dispatcher_tube_unref, G_OBJECT (dispatcher)); @@ -351,6 +467,7 @@ dispatcher_tubes_tube_closed_cb (TpChannel *channel, } } + static void dispatcher_tubes_handle_channel (EmpathyDispatcher *dispatcher, TpChannel *channel) @@ -378,28 +495,25 @@ dispatcher_tubes_handle_channel (EmpathyDispatcher *dispatcher, G_OBJECT (dispatcher)); } +#endif + static void dispatcher_connection_invalidated_cb (TpConnection *connection, - guint domain, - gint code, - gchar *message, - EmpathyDispatcher *dispatcher) + guint domain, gint code, gchar *message, + EmpathyDispatcher *dispatcher) { - EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher); - GHashTableIter iter; - gpointer key, value; + EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher); + ConnectionData *cd; - DEBUG ("Error: %s", message); + DEBUG ("Error: %s", message); + cd = g_hash_table_lookup (priv->connections, connection); - g_hash_table_iter_init (&iter, priv->connections); - while (g_hash_table_iter_next (&iter, &key, &value)) { - if (value == connection) { - g_hash_table_remove (priv->connections, key); - break; - } - } + g_hash_table_remove (priv->accounts, cd->account); + g_hash_table_remove (priv->connections, connection); } +#if 0 + typedef struct { EmpathyDispatcher *self; @@ -453,35 +567,424 @@ static void dispatcher_chatroom_invalidated_cb ( } } +#endif + + +/********************* Sanity from here at some point *********/ +static gboolean +dispatcher_operation_can_start (EmpathyDispatcher *self, + EmpathyDispatchOperation *operation, ConnectionData *cd) +{ + GList *l; + const gchar *channel_type = + empathy_dispatch_operation_get_channel_type (operation); + + for (l = cd->outstanding_requests; l != NULL; l = g_list_next (l)) + { + DispatcherRequestData *d = (DispatcherRequestData *) l->data; + + if (d->operation == NULL && !tp_strdiff (d->channel_type, channel_type)) + { + return FALSE; + } + } + + return TRUE; +} + static void -dispatcher_connection_new_channel_cb (TpConnection *connection, - const gchar *object_path, - const gchar *channel_type, - guint handle_type, - guint handle, - gboolean suppress_handler, - gpointer user_data, - GObject *object) +dispatch_operation_flush_requests (EmpathyDispatcher *dispatcher, + EmpathyDispatchOperation *operation, GError *error, ConnectionData *cd) { - EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (object); + GList *l; + + l = cd->outstanding_requests; + while (l != NULL) + { + DispatcherRequestData *d = (DispatcherRequestData *) l->data; + GList *lt = l; + + l = g_list_next (l); + + if (d->operation == operation) + { + if (d->cb != NULL) + { + if (error != NULL) + d->cb (NULL, error, d->user_data); + else + d->cb (operation, NULL, d->user_data); + } + + cd->outstanding_requests = g_list_delete_link + (cd->outstanding_requests, lt); + + free_dispatcher_request_data (d); + } + } +} + +static void +dispatcher_channel_invalidated_cb (TpProxy *proxy, guint domain, gint code, + gchar *message, EmpathyDispatcher *dispatcher) +{ + /* Channel went away... */ EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher); - TpChannel *channel; - gpointer had_channels; + TpConnection *connection; + EmpathyDispatchOperation *operation; + ConnectionData *cd; + const gchar *object_path; - had_channels = g_object_get_data (G_OBJECT (connection), "had-channels"); - if (had_channels == NULL) { - /* ListChannels didn't return yet, return to avoid duplicate - * dispatching */ - return; - } + g_object_get (G_OBJECT (proxy), "connection", &connection, NULL); - channel = tp_channel_new (connection, object_path, channel_type, - handle_type, handle, NULL); - tp_channel_run_until_ready (channel, NULL, NULL); + cd = g_hash_table_lookup (priv->connections, connection); - if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES)) { - dispatcher_tubes_handle_channel (dispatcher, channel); - } + object_path = tp_proxy_get_object_path (proxy); + + DEBUG ("Channel %s invalidated", object_path); + + g_hash_table_remove (cd->dispatched_channels, object_path); + g_hash_table_remove (cd->dispatching_channels, object_path); + + operation = g_hash_table_lookup (cd->outstanding_channels, object_path); + if (operation != NULL) + { + GError error = { domain, code, message }; + dispatch_operation_flush_requests (dispatcher, operation, &error, cd); + g_hash_table_remove (cd->outstanding_channels, object_path); + g_object_unref (operation); + } +} + +static void +dispatch_operation_approved_cb (EmpathyDispatchOperation *operation, + EmpathyDispatcher *dispatcher) +{ + g_assert (empathy_dispatch_operation_is_incoming (operation)); + DEBUG ("Send of for dispatching: %s", + empathy_dispatch_operation_get_object_path (operation)); + g_signal_emit (dispatcher, signals[DISPATCH], 0, operation); +} + +static void +dispatch_operation_claimed_cb (EmpathyDispatchOperation *operation, + EmpathyDispatcher *dispatcher) +{ + /* Our job is done, remove the dispatch operation and mark the channel as + * dispatched */ + EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher); + TpConnection *connection; + ConnectionData *cd; + const gchar *object_path; + + connection = empathy_dispatch_operation_get_tp_connection (operation); + cd = g_hash_table_lookup (priv->connections, connection); + g_assert (cd != NULL); + g_object_unref (G_OBJECT (connection)); + + object_path = empathy_dispatch_operation_get_object_path (operation); + + if (g_hash_table_lookup (cd->dispatched_channels, object_path) == NULL) + { + DispatchData *d; + d = new_dispatch_data ( + empathy_dispatch_operation_get_channel (operation), + empathy_dispatch_operation_get_channel_wrapper (operation)); + g_hash_table_insert (cd->dispatched_channels, + g_strdup (object_path), d); + } + g_hash_table_remove (cd->dispatching_channels, object_path); + + DEBUG ("Channel claimed: %s", object_path); +} + +static void +dispatch_operation_ready_cb (EmpathyDispatchOperation *operation, + EmpathyDispatcher *dispatcher) +{ + EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher); + TpConnection *connection; + ConnectionData *cd; + EmpathyDispatchOperationState status; + + g_signal_connect (operation, "approved", + G_CALLBACK (dispatch_operation_approved_cb), dispatcher); + + g_signal_connect (operation, "claimed", + G_CALLBACK (dispatch_operation_claimed_cb), dispatcher); + + /* Signal the observers */ + DEBUG ("Send to observers: %s", + empathy_dispatch_operation_get_object_path (operation)); + g_signal_emit (dispatcher, signals[OBSERVE], 0, operation); + + empathy_dispatch_operation_start (operation); + + /* Signal potential requestors */ + connection = empathy_dispatch_operation_get_tp_connection (operation); + cd = g_hash_table_lookup (priv->connections, connection); + g_assert (cd != NULL); + g_object_unref (G_OBJECT (connection)); + + g_object_ref (operation); + + dispatch_operation_flush_requests (dispatcher, operation, NULL, cd); + status = empathy_dispatch_operation_get_status (operation); + g_object_unref (operation); + + if (status == EMPATHY_DISPATCHER_OPERATION_STATE_CLAIMED) + return; + + if (status == EMPATHY_DISPATCHER_OPERATION_STATE_APPROVING) + { + DEBUG ("Send to approvers: %s", + empathy_dispatch_operation_get_object_path (operation)); + g_signal_emit (dispatcher, signals[APPROVE], 0, operation); + } + else + { + g_assert (status == EMPATHY_DISPATCHER_OPERATION_STATE_DISPATCHING); + DEBUG ("Send of for dispatching: %s", + empathy_dispatch_operation_get_object_path (operation)); + g_signal_emit (dispatcher, signals[DISPATCH], 0, operation); + } + +} + +static void +dispatcher_start_dispatching (EmpathyDispatcher *self, + EmpathyDispatchOperation *operation, ConnectionData *cd) +{ + const gchar *object_path = + empathy_dispatch_operation_get_object_path (operation); + + DEBUG ("Dispatching process started for %s", object_path); + + if (g_hash_table_lookup (cd->dispatching_channels, object_path) == NULL) + { + g_assert (g_hash_table_lookup (cd->outstanding_channels, + object_path) == NULL); + + g_hash_table_insert (cd->dispatching_channels, + g_strdup (object_path), operation); + + switch (empathy_dispatch_operation_get_status (operation)) + { + case EMPATHY_DISPATCHER_OPERATION_STATE_PREPARING: + g_signal_connect (operation, "ready", + G_CALLBACK (dispatch_operation_ready_cb), dispatcher); + break; + case EMPATHY_DISPATCHER_OPERATION_STATE_PENDING: + dispatch_operation_ready_cb (operation, dispatcher); + break; + default: + g_assert_not_reached(); + } + + } + else if (empathy_dispatch_operation_get_status (operation) >= + EMPATHY_DISPATCHER_OPERATION_STATE_PENDING) + { + /* Already dispatching and the operation is pending, thus the observers + * have seen it (if applicable), so we can flush the request right away. + */ + dispatch_operation_flush_requests (self, operation, NULL, cd); + } +} + + +static void +dispatcher_flush_outstanding_operations (EmpathyDispatcher *self, + ConnectionData *cd) +{ + GHashTableIter iter; + gpointer value; + + g_hash_table_iter_init (&iter, cd->outstanding_channels); + while (g_hash_table_iter_next (&iter, NULL, &value)) + { + EmpathyDispatchOperation *operation = EMPATHY_DISPATCH_OPERATION (value); + + if (dispatcher_operation_can_start (self, operation, cd)) + { + dispatcher_start_dispatching (dispatcher, operation, cd); + g_hash_table_iter_remove (&iter); + } + } +} + + +static void +dispatcher_connection_new_channel (EmpathyDispatcher *dispatcher, + TpConnection *connection, + const gchar *object_path, const gchar *channel_type, + guint handle_type, guint handle, GHashTable *properties, + gboolean incoming) +{ + EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher); + TpChannel *channel; + ConnectionData *cd; + EmpathyDispatchOperation *operation; + EmpathyContact *contact = NULL; + + cd = g_hash_table_lookup (priv->connections, connection); + + /* Don't bother with channels we have already dispatched or are dispatching + * currently. This can happen when NewChannel(s) is fired after + * RequestChannel/CreateChannel/EnsureChannel */ + if (g_hash_table_lookup (cd->dispatched_channels, object_path) != NULL) + return; + + if (g_hash_table_lookup (cd->dispatching_channels, object_path) != NULL) + return; + + /* Should never occur, but just in case a CM fires spurious NewChannel(s) + * signals */ + if (g_hash_table_lookup (cd->outstanding_channels, object_path) != NULL) + return; + + DEBUG ("New channel of type %s on %s", + channel_type, object_path); + + if (properties == NULL) + channel = tp_channel_new (connection, object_path, channel_type, + handle_type, handle, NULL); + else + channel = tp_channel_new_from_properties (connection, object_path, + properties, NULL); + + g_signal_connect (channel, "invalidated", + G_CALLBACK (dispatcher_channel_invalidated_cb), + dispatcher); + + if (handle_type == TP_CONN_HANDLE_TYPE_CONTACT) + { + EmpathyContactFactory *factory = empathy_contact_factory_new (); + contact = empathy_contact_factory_get_from_handle (factory, + cd->account, handle); + g_object_unref (factory); + } + + operation = empathy_dispatch_operation_new (connection, channel, contact, + incoming); + + g_object_unref (channel); + + if (incoming) + { + /* Request could either be by us or by a remote party. If there are no + * outstanding requests for this channel type we can assume it's remote. + * Otherwise we wait untill they are all satisfied */ + if (dispatcher_operation_can_start (dispatcher, operation, cd)) + dispatcher_start_dispatching (dispatcher, operation, cd); + else + g_hash_table_insert (cd->outstanding_channels, + g_strdup (object_path), operation); + } + else + { + dispatcher_start_dispatching (dispatcher, operation, cd); + } +} + +static void +dispatcher_connection_new_channel_cb (TpConnection *connection, + const gchar *object_path, const gchar *channel_type, + guint handle_type, guint handle, + gboolean suppress_handler, gpointer user_data, + GObject *object) +{ + EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (object); + + /* Empathy heavily abuses surpress handler (don't try this at home), if + * surpress handler is true then it is an outgoing channel, which is + * requested either by us or some other party (like the megaphone applet). + * Otherwise it's an incoming channel */ + dispatcher_connection_new_channel (dispatcher, connection, + object_path, channel_type, handle_type, handle, NULL, !suppress_handler); +} + +static void +dispatcher_connection_new_channel_with_properties ( + EmpathyDispatcher *dispatcher, TpConnection *connection, + const gchar *object_path, GHashTable *properties) +{ + const gchar *channel_type; + guint handle_type; + guint handle; + gboolean requested; + gboolean valid; + + + channel_type = tp_asv_get_string (properties, + TP_IFACE_CHANNEL ".ChannelType"); + if (channel_type == NULL) + { + g_message ("%s had an invalid ChannelType property", object_path); + return; + } + + handle_type = tp_asv_get_uint32 (properties, + TP_IFACE_CHANNEL ".TargetHandleType", &valid); + if (!valid) + { + g_message ("%s had an invalid TargetHandleType property", object_path); + return; + } + + handle = tp_asv_get_uint32 (properties, + TP_IFACE_CHANNEL ".TargetHandle", &valid); + if (!valid) + { + g_message ("%s had an invalid TargetHandle property", object_path); + return; + } + + /* We assume there is no channel dispather, so we're the only one dispatching + * it. Which means that a requested channel it is outgoing one */ + requested = tp_asv_get_boolean (properties, + TP_IFACE_CHANNEL ".Requested", &valid); + if (!valid) + { + g_message ("%s had an invalid Requested property", object_path); + return; + } + + dispatcher_connection_new_channel (dispatcher, connection, + object_path, channel_type, handle_type, handle, properties, !requested); +} + + +static void +dispatcher_connection_new_channels_cb ( + TpConnection *connection, const GPtrArray *channels, gpointer user_data, + GObject *object) +{ + EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (object); + int i; + + for (i = 0; i < channels->len ; i++) + { + GValueArray *arr = g_ptr_array_index (channels, i); + const gchar *object_path; + GHashTable *properties; + + object_path = g_value_get_boxed (g_value_array_get_nth (arr, 0)); + properties = g_value_get_boxed (g_value_array_get_nth (arr, 1)); + + dispatcher_connection_new_channel_with_properties (dispatcher, + connection, object_path, properties); + } +} + +#if 0 /* old dispatching */ + channel = tp_channel_new (connection, object_path, channel_type, + handle_type, handle, NULL); + tp_channel_run_until_ready (channel, NULL, NULL); + + if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES)) { + dispatcher_tubes_handle_channel (dispatcher, channel); + } if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT) && handle_type == TP_HANDLE_TYPE_ROOM) @@ -541,6 +1044,25 @@ dispatcher_connection_new_channel_cb (TpConnection *connection, } g_object_unref (channel); + +} +#endif + +static void +dispatcher_connection_got_channels_property (TpProxy *proxy, + const GValue *channels_prop, const GError *error, gpointer user_data, + GObject *object) +{ + GPtrArray *channels; + + if (error) { + DEBUG ("Error: %s", error->message); + return; + } + + channels = g_value_get_boxed (channels_prop); + dispatcher_connection_new_channels_cb (TP_CONNECTION (proxy), + channels, NULL, object); } static void @@ -550,26 +1072,26 @@ dispatcher_connection_list_channels_cb (TpConnection *connection, gpointer user_data, GObject *dispatcher) { - guint i; + int i; if (error) { DEBUG ("Error: %s", error->message); return; } - g_object_set_data (G_OBJECT (connection), "had-channels", - GUINT_TO_POINTER (1)); - for (i = 0; i < channels->len; i++) { GValueArray *values; values = g_ptr_array_index (channels, i); - dispatcher_connection_new_channel_cb (connection, + /* We don't have any extra info, so assume already existing channels are + * incoming... */ + dispatcher_connection_new_channel (EMPATHY_DISPATCHER (dispatcher), + connection, g_value_get_boxed (g_value_array_get_nth (values, 0)), g_value_get_string (g_value_array_get_nth (values, 1)), g_value_get_uint (g_value_array_get_nth (values, 2)), g_value_get_uint (g_value_array_get_nth (values, 3)), - FALSE, user_data, dispatcher); + NULL, TRUE); } } @@ -587,357 +1109,364 @@ dispatcher_connection_advertise_capabilities_cb (TpConnection *connection, static void dispatcher_connection_ready_cb (TpConnection *connection, - const GError *error, - gpointer dispatcher) + const GError *error, gpointer dispatcher) { - GPtrArray *capabilities; - GType cap_type; - GValue cap = {0, }; - const gchar *remove = NULL; + GPtrArray *capabilities; + GType cap_type; + GValue cap = {0, }; + const gchar *remove = NULL; - if (error) { - dispatcher_connection_invalidated_cb (connection, - error->domain, - error->code, - error->message, - dispatcher); - return; - } + if (error) + { + dispatcher_connection_invalidated_cb (connection, error->domain, + error->code, error->message, dispatcher); + return; + } - g_signal_connect (connection, "invalidated", - G_CALLBACK (dispatcher_connection_invalidated_cb), - dispatcher); - tp_cli_connection_connect_to_new_channel (connection, - dispatcher_connection_new_channel_cb, - NULL, NULL, - G_OBJECT (dispatcher), NULL); - tp_cli_connection_call_list_channels (connection, -1, - dispatcher_connection_list_channels_cb, - NULL, NULL, - G_OBJECT (dispatcher)); - - /* Advertise VoIP capabilities */ - capabilities = g_ptr_array_sized_new (1); - cap_type = dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, - G_TYPE_UINT, G_TYPE_INVALID); - g_value_init (&cap, cap_type); - g_value_take_boxed (&cap, dbus_g_type_specialized_construct (cap_type)); - dbus_g_type_struct_set (&cap, - 0, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, - 1, TP_CHANNEL_MEDIA_CAPABILITY_AUDIO | - TP_CHANNEL_MEDIA_CAPABILITY_VIDEO | - TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_STUN | - TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_GTALK_P2P, - G_MAXUINT); - g_ptr_array_add (capabilities, g_value_get_boxed (&cap)); - - tp_cli_connection_interface_capabilities_call_advertise_capabilities ( - connection, -1, - capabilities, &remove, - dispatcher_connection_advertise_capabilities_cb, - NULL, NULL, G_OBJECT (dispatcher)); - /* FIXME: Is that leaked? */ + g_signal_connect (connection, "invalidated", + G_CALLBACK (dispatcher_connection_invalidated_cb), dispatcher); + + + if (tp_proxy_has_interface_by_id (TP_PROXY (connection), + TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS)) + { + tp_cli_connection_interface_requests_connect_to_new_channels (connection, + dispatcher_connection_new_channels_cb, + NULL, NULL, G_OBJECT (dispatcher), NULL); + + tp_cli_dbus_properties_call_get (connection, -1, + TP_IFACE_CONNECTION_INTERFACE_REQUESTS, "Channels", + dispatcher_connection_got_channels_property, + NULL, NULL, dispatcher); + } + else + { + tp_cli_connection_connect_to_new_channel (connection, + dispatcher_connection_new_channel_cb, + NULL, NULL, G_OBJECT (dispatcher), NULL); + + tp_cli_connection_call_list_channels (connection, -1, + dispatcher_connection_list_channels_cb, NULL, NULL, + G_OBJECT (dispatcher)); + + } + + /* Advertise VoIP capabilities */ + capabilities = g_ptr_array_sized_new (1); + cap_type = dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, + G_TYPE_UINT, G_TYPE_INVALID); + g_value_init (&cap, cap_type); + g_value_take_boxed (&cap, dbus_g_type_specialized_construct (cap_type)); + dbus_g_type_struct_set (&cap, + 0, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, + 1, TP_CHANNEL_MEDIA_CAPABILITY_AUDIO | + TP_CHANNEL_MEDIA_CAPABILITY_VIDEO | + TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_STUN | + TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_GTALK_P2P, G_MAXUINT); + g_ptr_array_add (capabilities, g_value_get_boxed (&cap)); + + tp_cli_connection_interface_capabilities_call_advertise_capabilities ( + connection, -1, capabilities, &remove, + dispatcher_connection_advertise_capabilities_cb, + NULL, NULL, G_OBJECT (dispatcher)); } static void -dispatcher_update_account (EmpathyDispatcher *dispatcher, - McAccount *account) +dispatcher_update_account (EmpathyDispatcher *dispatcher, McAccount *account) { - EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher); - TpConnection *connection; + EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher); + TpConnection *connection; - connection = g_hash_table_lookup (priv->connections, account); - if (connection) { - return; - } + connection = g_hash_table_lookup (priv->accounts, account); + if (connection != NULL) + return; - connection = mission_control_get_tpconnection (priv->mc, account, NULL); - if (!connection) { - return; - } + connection = mission_control_get_tpconnection (priv->mc, account, NULL); + if (connection == NULL) + return; + + g_hash_table_insert (priv->connections, g_object_ref (connection), + new_connection_data (account)); + + g_hash_table_insert (priv->accounts, g_object_ref (account), + g_object_ref (connection)); - g_hash_table_insert (priv->connections, g_object_ref (account), connection); - tp_connection_call_when_ready (connection, - dispatcher_connection_ready_cb, - dispatcher); + tp_connection_call_when_ready (connection, dispatcher_connection_ready_cb, + dispatcher); } static void dispatcher_account_connection_cb (EmpathyAccountManager *manager, - McAccount *account, - TpConnectionStatusReason reason, - TpConnectionStatus status, - TpConnectionStatus previous, - EmpathyDispatcher *dispatcher) + McAccount *account, TpConnectionStatusReason reason, + TpConnectionStatus status, TpConnectionStatus previous, + EmpathyDispatcher *dispatcher) { - dispatcher_update_account (dispatcher, account); + dispatcher_update_account (dispatcher, account); } static void dispatcher_finalize (GObject *object) { - EmpathyDispatcherPriv *priv = GET_PRIV (object); - GSList *l; - - g_signal_handlers_disconnect_by_func (priv->account_manager, - dispatcher_account_connection_cb, - object); - g_object_unref (priv->account_manager); - g_object_unref (priv->mc); - - for (l = priv->tubes; l; l = l->next) { - g_signal_handlers_disconnect_by_func (l->data, - dispatcher_tubes_channel_invalidated_cb, - object); - g_object_unref (l->data); - } - g_slist_free (priv->tubes); + EmpathyDispatcherPriv *priv = GET_PRIV (object); + + g_signal_handlers_disconnect_by_func (priv->account_manager, + dispatcher_account_connection_cb, object); + + g_object_unref (priv->account_manager); + g_object_unref (priv->mc); - g_hash_table_destroy (priv->connections); + g_hash_table_destroy (priv->connections); - g_object_unref (priv->chatroom_mgr); + g_object_unref (priv->chatroom_mgr); } static void empathy_dispatcher_class_init (EmpathyDispatcherClass *klass) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = dispatcher_finalize; - - signals[DISPATCH_CHANNEL] = - g_signal_new ("dispatch-channel", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__OBJECT, - G_TYPE_NONE, - 1, TP_TYPE_CHANNEL); - signals[FILTER_CHANNEL] = - g_signal_new ("filter-channel", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__OBJECT, - G_TYPE_NONE, - 1, TP_TYPE_CHANNEL); - signals[FILTER_TUBE] = - g_signal_new ("filter-tube", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__BOXED, - G_TYPE_NONE, - 1, EMPATHY_TYPE_DISPATCHER_TUBE); - - g_type_class_add_private (object_class, sizeof (EmpathyDispatcherPriv)); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = dispatcher_finalize; + + signals[OBSERVE] = + g_signal_new ("observe", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, EMPATHY_TYPE_DISPATCH_OPERATION); + + signals[APPROVE] = + g_signal_new ("approve", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, EMPATHY_TYPE_DISPATCH_OPERATION); + + signals[DISPATCH] = + g_signal_new ("dispatch", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, EMPATHY_TYPE_DISPATCH_OPERATION); + + g_type_class_add_private (object_class, sizeof (EmpathyDispatcherPriv)); + } static void empathy_dispatcher_init (EmpathyDispatcher *dispatcher) { - GList *accounts, *l; - EmpathyDispatcherPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (dispatcher, - EMPATHY_TYPE_DISPATCHER, EmpathyDispatcherPriv); + GList *accounts, *l; + EmpathyDispatcherPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (dispatcher, + EMPATHY_TYPE_DISPATCHER, EmpathyDispatcherPriv); - dispatcher->priv = priv; - priv->mc = empathy_mission_control_new (); - priv->account_manager = empathy_account_manager_dup_singleton (); + dispatcher->priv = priv; + priv->mc = empathy_mission_control_new (); + priv->account_manager = empathy_account_manager_dup_singleton (); - g_signal_connect (priv->account_manager, - "account-connection-changed", - G_CALLBACK (dispatcher_account_connection_cb), - dispatcher); + g_signal_connect (priv->account_manager, + "account-connection-changed", + G_CALLBACK (dispatcher_account_connection_cb), + dispatcher); - priv->connections = g_hash_table_new_full (empathy_account_hash, - empathy_account_equal, - g_object_unref, - g_object_unref); - accounts = mc_accounts_list_by_enabled (TRUE); - for (l = accounts; l; l = l->next) { - dispatcher_update_account (dispatcher, l->data); - g_object_unref (l->data); - } - g_list_free (accounts); + priv->accounts = g_hash_table_new_full (empathy_account_hash, + empathy_account_equal, g_object_unref, g_object_unref); + + priv->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, + g_object_unref, (GDestroyNotify) free_connection_data); + + accounts = mc_accounts_list_by_enabled (TRUE); - priv->chatroom_mgr = empathy_chatroom_manager_new (NULL); + for (l = accounts; l; l = l->next) { + dispatcher_update_account (dispatcher, l->data); + g_object_unref (l->data); + } + g_list_free (accounts); + + priv->chatroom_mgr = empathy_chatroom_manager_new (NULL); } EmpathyDispatcher * -empathy_dispatcher_new (void) +empathy_get_dispatcher (void) { - if (!dispatcher) { - dispatcher = g_object_new (EMPATHY_TYPE_DISPATCHER, NULL); - g_object_add_weak_pointer (G_OBJECT (dispatcher), (gpointer) &dispatcher); - } else { - g_object_ref (dispatcher); - } - - return dispatcher; + if (!dispatcher) { + dispatcher = g_object_new (EMPATHY_TYPE_DISPATCHER, NULL); + g_object_add_weak_pointer (G_OBJECT (dispatcher), (gpointer) &dispatcher); + } else { + g_object_ref (dispatcher); + } + + return dispatcher; } -typedef struct { - const gchar *channel_type; - guint handle_type; - guint handle; -} DispatcherRequestData; - static void dispatcher_request_channel_cb (TpConnection *connection, - const gchar *object_path, - const GError *error, - gpointer user_data, - GObject *weak_object) + const gchar *object_path, const GError *error, + gpointer user_data, GObject *weak_object) { - DispatcherRequestData *data = (DispatcherRequestData*) user_data; + DispatcherRequestData *request_data = (DispatcherRequestData*) user_data; + EmpathyDispatcherPriv *priv = GET_PRIV (request_data->dispatcher); + EmpathyDispatchOperation *operation = NULL; + ConnectionData *conn_data; - if (error) { - DEBUG ("Error: %s", error->message); - return; - } + conn_data = g_hash_table_lookup (priv->connections, + request_data->connection); - if (dispatcher) { - TpChannel *channel; + if (error) + { + DEBUG ("Channel request failed: %s", error->message); - channel = tp_channel_new (connection, object_path, - data->channel_type, - data->handle_type, - data->handle, NULL); + if (request_data->cb != NULL) + request_data->cb (NULL, error, request_data->user_data); - g_signal_emit (dispatcher, signals[DISPATCH_CHANNEL], 0, channel); - } -} + conn_data->outstanding_requests = + g_list_remove (conn_data->outstanding_requests, request_data); + free_dispatcher_request_data (request_data); -void -empathy_dispatcher_call_with_contact (EmpathyContact *contact) -{ - MissionControl *mc; - McAccount *account; - TpConnection *connection; - gchar *object_path; - TpChannel *channel; - EmpathyContactFactory *factory; - EmpathyTpGroup *group; - EmpathyContact *self_contact; - GError *error = NULL; + goto out; + } - g_return_if_fail (EMPATHY_IS_CONTACT (contact)); + operation = g_hash_table_lookup (conn_data->outstanding_channels, + object_path); - mc = empathy_mission_control_new (); - account = empathy_contact_get_account (contact); - connection = mission_control_get_tpconnection (mc, account, NULL); - tp_connection_run_until_ready (connection, FALSE, NULL, NULL); - g_object_unref (mc); + if (operation != NULL) + g_hash_table_remove (conn_data->outstanding_channels, object_path); + else + operation = g_hash_table_lookup (conn_data->dispatching_channels, + object_path); - /* We abuse of suppress_handler, TRUE means OUTGOING. The channel - * will be catched in EmpathyFilter */ - if (!tp_cli_connection_run_request_channel (connection, -1, - TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, - TP_HANDLE_TYPE_NONE, - 0, - TRUE, - &object_path, - &error, - NULL)) { - DEBUG ("Couldn't request channel: %s", - error ? error->message : "No error given"); - g_clear_error (&error); - g_object_unref (connection); - return; - } + /* FIXME check if we got an existing channel back */ + if (operation == NULL) + { + DispatchData *data = g_hash_table_lookup (conn_data->dispatched_channels, + object_path); - channel = tp_channel_new (connection, - object_path, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, - TP_HANDLE_TYPE_NONE, 0, NULL); + if (data != NULL) + { + operation = empathy_dispatch_operation_new_with_wrapper (connection, + data->channel, request_data->contact, FALSE, + data->channel_wrapper); + } + else + { + TpChannel *channel = tp_channel_new (connection, object_path, + request_data->channel_type, request_data->handle_type, + request_data->handle, NULL); - group = empathy_tp_group_new (channel); - empathy_run_until_ready (group); + g_signal_connect (channel, "invalidated", + G_CALLBACK (dispatcher_channel_invalidated_cb), + request_data->dispatcher); - factory = empathy_contact_factory_dup_singleton (); - self_contact = empathy_contact_factory_get_user (factory, account); - empathy_contact_run_until_ready (self_contact, - EMPATHY_CONTACT_READY_HANDLE, - NULL); + operation = empathy_dispatch_operation_new (connection, channel, + request_data->contact, FALSE); + g_object_unref (channel); + } + } + else + { + /* Already existed set potential extra information */ + g_object_set (G_OBJECT (operation), + "contact", request_data->contact, + NULL); + } - empathy_tp_group_add_member (group, contact, ""); - empathy_tp_group_add_member (group, self_contact, ""); + request_data->operation = operation; - g_object_unref (factory); - g_object_unref (self_contact); - g_object_unref (group); - g_object_unref (connection); - g_object_unref (channel); - g_free (object_path); + /* (pre)-approve this right away as we requested it */ + empathy_dispatch_operation_approve (operation); + + dispatcher_start_dispatching (request_data->dispatcher, operation, + conn_data); +out: + dispatcher_flush_outstanding_operations (request_data->dispatcher, + conn_data); } void -empathy_dispatcher_call_with_contact_id (McAccount *account, const gchar *contact_id) +empathy_dispatcher_call_with_contact ( EmpathyContact *contact, + EmpathyDispatcherRequestCb *callback, gpointer user_data) { - EmpathyContactFactory *factory; - EmpathyContact *contact; + g_assert_not_reached (); +} - factory = empathy_contact_factory_dup_singleton (); - contact = empathy_contact_factory_get_from_id (factory, account, contact_id); - empathy_contact_run_until_ready (contact, EMPATHY_CONTACT_READY_HANDLE, NULL); +void +empathy_dispatcher_call_with_contact_id (McAccount *account, + const gchar *contact_id, EmpathyDispatcherRequestCb *callback, + gpointer user_data) +{ + g_assert_not_reached (); +} + +static void +dispatcher_chat_with_contact_cb (EmpathyContact *contact, gpointer user_data) +{ + DispatcherRequestData *request_data = (DispatcherRequestData *) user_data; - empathy_dispatcher_call_with_contact (contact); + request_data->handle = empathy_contact_get_handle (contact); - g_object_unref (contact); - g_object_unref (factory); + /* Note this does rape the surpress handler semantics */ + tp_cli_connection_call_request_channel (request_data->connection, -1, + request_data->channel_type, + request_data->handle_type, + request_data->handle, + TRUE, dispatcher_request_channel_cb, + request_data, NULL, NULL); } void -empathy_dispatcher_chat_with_contact (EmpathyContact *contact) +empathy_dispatcher_chat_with_contact (EmpathyContact *contact, + EmpathyDispatcherRequestCb *callback, gpointer user_data) { - MissionControl *mc; - McAccount *account; - TpConnection *connection; - DispatcherRequestData *data; + EmpathyDispatcher *dispatcher = empathy_get_dispatcher(); + EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher); + McAccount *account = empathy_contact_get_account (contact); + TpConnection *connection = g_hash_table_lookup (priv->accounts, account); + ConnectionData *connection_data = + g_hash_table_lookup (priv->connections, connection); + DispatcherRequestData *request_data; - g_return_if_fail (EMPATHY_IS_CONTACT (contact)); + /* The contact handle might not be known yet */ + request_data = new_dispatcher_request_data (dispatcher, connection, + TP_IFACE_CHANNEL_TYPE_TEXT, TP_HANDLE_TYPE_CONTACT, 0, contact, callback, + user_data); - mc = empathy_mission_control_new (); - account = empathy_contact_get_account (contact); - connection = mission_control_get_tpconnection (mc, account, NULL); - tp_connection_run_until_ready (connection, FALSE, NULL, NULL); - g_object_unref (mc); + connection_data->outstanding_requests = g_list_prepend + (connection_data->outstanding_requests, request_data); - /* We abuse of suppress_handler, TRUE means OUTGOING. */ - data = g_new (DispatcherRequestData, 1); - data->channel_type = TP_IFACE_CHANNEL_TYPE_TEXT; - data->handle_type = TP_HANDLE_TYPE_CONTACT; - data->handle = empathy_contact_get_handle (contact); - tp_cli_connection_call_request_channel (connection, -1, - data->channel_type, - data->handle_type, - data->handle, - TRUE, - dispatcher_request_channel_cb, - data, g_free, - NULL); - g_object_unref (connection); + empathy_contact_call_when_ready (contact, + EMPATHY_CONTACT_READY_HANDLE, dispatcher_chat_with_contact_cb, + request_data); + + g_object_unref (dispatcher); } void -empathy_dispatcher_chat_with_contact_id (McAccount *account, - const gchar *contact_id) +empathy_dispatcher_chat_with_contact_id (McAccount *account, const gchar + *contact_id, EmpathyDispatcherRequestCb *callback, gpointer user_data) { - EmpathyContactFactory *factory; - EmpathyContact *contact; + EmpathyDispatcher *dispatcher = empathy_get_dispatcher (); + EmpathyContactFactory *factory; + EmpathyContact *contact; - factory = empathy_contact_factory_dup_singleton (); - contact = empathy_contact_factory_get_from_id (factory, account, contact_id); - empathy_contact_run_until_ready (contact, EMPATHY_CONTACT_READY_HANDLE, NULL); + factory = empathy_contact_factory_dup_singleton (); + contact = empathy_contact_factory_get_from_id (factory, account, contact_id); - empathy_dispatcher_chat_with_contact (contact); + empathy_dispatcher_chat_with_contact (contact, callback, user_data); - g_object_unref (contact); - g_object_unref (factory); + g_object_unref (contact); + g_object_unref (factory); + g_object_unref (dispatcher); } +#if 0 typedef struct { GFile *gfile; TpHandle handle; @@ -993,10 +1522,17 @@ file_channel_create_cb (TpConnection *connection, g_object_unref (channel); } +#endif + void empathy_dispatcher_send_file (EmpathyContact *contact, GFile *gfile) { + g_assert_not_reached(); + return; +} + +#if 0 MissionControl *mc; McAccount *account; TpConnection *connection; @@ -1096,3 +1632,4 @@ empathy_dispatcher_send_file (EmpathyContact *contact, g_object_unref (connection); } +#endif diff --git a/libempathy/empathy-dispatcher.h b/libempathy/empathy-dispatcher.h index 6f9d4e718..0c6cecb32 100644 --- a/libempathy/empathy-dispatcher.h +++ b/libempathy/empathy-dispatcher.h @@ -28,6 +28,7 @@ #include #include "empathy-contact.h" +#include "empathy-dispatch-operation.h" G_BEGIN_DECLS @@ -58,23 +59,40 @@ typedef struct { gboolean activatable; } EmpathyDispatcherTube; -GType empathy_dispatcher_get_type (void) G_GNUC_CONST; -EmpathyDispatcher * empathy_dispatcher_new (void); -void empathy_dispatcher_channel_process (EmpathyDispatcher *dispatcher, - TpChannel *channel); + +/* Will be called when the channel is ready for dispatching. The requestor + * handle the channel itself by calling empathy_dispatch_operation_handles */ +typedef void (EmpathyDispatcherRequestCb) ( + EmpathyDispatchOperation *dispatch, const GError *error, + gpointer user_data); + +GType empathy_dispatcher_get_type (void) G_GNUC_CONST; + +void empathy_dispatcher_call_with_contact (EmpathyContact *contact, + EmpathyDispatcherRequestCb *callback, gpointer user_data); +void empathy_dispatcher_call_with_contact_id (McAccount *account, + const gchar *contact_id, EmpathyDispatcherRequestCb *callback, + gpointer user_data); + +void empathy_dispatcher_chat_with_contact_id (McAccount *account, + const gchar *contact_id, EmpathyDispatcherRequestCb *callback, + gpointer user_data); +void empathy_dispatcher_chat_with_contact (EmpathyContact *contact, + EmpathyDispatcherRequestCb *callback, gpointer user_data); + +/* Get the dispatcher singleton */ +EmpathyDispatcher * empathy_get_dispatcher (void); GType empathy_dispatcher_tube_get_type (void); + + +void empathy_dispatcher_send_file (EmpathyContact *contact, + GFile *gfile); + +/* tube stuff */ EmpathyDispatcherTube *empathy_dispatcher_tube_ref (EmpathyDispatcherTube *tube); void empathy_dispatcher_tube_unref (EmpathyDispatcherTube *tube); void empathy_dispatcher_tube_process (EmpathyDispatcher *dispatcher, - EmpathyDispatcherTube *tube); -void empathy_dispatcher_call_with_contact (EmpathyContact *contact); -void empathy_dispatcher_call_with_contact_id (McAccount *account, - const gchar *contact_id); -void empathy_dispatcher_chat_with_contact_id (McAccount *account, - const gchar *contact_id); -void empathy_dispatcher_chat_with_contact (EmpathyContact *contact); -void empathy_dispatcher_send_file (EmpathyContact *contact, - GFile *gfile); + EmpathyDispatcherTube *etube); G_END_DECLS -- cgit v1.2.3