]> git.0d.be Git - empathy.git/commitdiff
Start reworking the dispatcher
authorSjoerd Simons <sjoerd.simons@collabora.co.uk>
Fri, 9 Jan 2009 16:12:07 +0000 (16:12 +0000)
committerXavier Claessens <xclaesse@src.gnome.org>
Fri, 9 Jan 2009 16:12:07 +0000 (16:12 +0000)
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 <sjoerd.simons@collabora.co.uk>
svn path=/trunk/; revision=2130

libempathy/Makefile.am
libempathy/empathy-dispatch-operation.c [new file with mode: 0644]
libempathy/empathy-dispatch-operation.h [new file with mode: 0644]
libempathy/empathy-dispatcher.c
libempathy/empathy-dispatcher.h

index c1bdd62487cac952105ca320381ecee847fcee48..7ca8b710466057dbcde6abcfdae27fadf626ed7a 100644 (file)
@@ -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 (file)
index 0000000..7f3357b
--- /dev/null
@@ -0,0 +1,519 @@
+/*
+ * empathy-dispatch-operation.c - Source for EmpathyDispatchOperation
+ * Copyright (C) 2008 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+
+#include "empathy-dispatch-operation.h"
+#include <libempathy/empathy-enum-types.h>
+#include <libempathy/empathy-tp-chat.h>
+#include <libempathy/empathy-tp-call.h>
+
+#include "empathy-marshal.h"
+
+#define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER
+#include <libempathy/empathy-debug.h>
+
+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 (file)
index 0000000..182fa37
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * empathy-dispatch-operation.h - Header for EmpathyDispatchOperation
+ * Copyright (C) 2008 Collabora Ltd.
+ * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ *
+ * 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 <glib-object.h>
+
+#include <libempathy/empathy-contact.h>
+
+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__*/
index 623a24aefb0e7af5c1fc64d09836027d3bd7c364..441da69db2da2fac868cded990d5260a0b77d0a3 100644 (file)
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
  * Copyright (C) 2007-2008 Collabora Ltd.
  *
 
 #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
index 6f9d4e718fa509c8661a4e2f31cba272d56a5fac..0c6cecb32be61bede97083f5809bbc2877806fbe 100644 (file)
@@ -28,6 +28,7 @@
 #include <telepathy-glib/channel.h>
 
 #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