[Telepathy-commits] [telepathy-glib/master] BaseConnection: add implementation of Requests interface.

Will Thompson will.thompson at collabora.co.uk
Thu Sep 18 16:07:11 PDT 2008


Subclasses must implement the GInterface, using the exported
tp_base_connection_requests_iface_init function, and call
tp_base_connection_register_requests_dbus_properties to make
TpBaseConnection use their D-Bus properties mixin.
---
 telepathy-glib/base-connection.c |  970 ++++++++++++++++++++++++++++++++++++--
 telepathy-glib/base-connection.h |   33 ++-
 2 files changed, 952 insertions(+), 51 deletions(-)

diff --git a/telepathy-glib/base-connection.c b/telepathy-glib/base-connection.c
index d88b4f4..4f94789 100644
--- a/telepathy-glib/base-connection.c
+++ b/telepathy-glib/base-connection.c
@@ -44,12 +44,14 @@
 #include <telepathy-glib/connection-manager.h>
 #include <telepathy-glib/contacts-mixin.h>
 #include <telepathy-glib/channel-factory-iface.h>
+#include <telepathy-glib/channel-manager.h>
 #include <telepathy-glib/dbus.h>
 #include <telepathy-glib/dbus-properties-mixin.h>
+#include <telepathy-glib/exportable-channel.h>
 #include <telepathy-glib/gtypes.h>
 #include <telepathy-glib/svc-generic.h>
-#include <telepathy-glib/util.h>
 #include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
 
 #define DEBUG_FLAG TP_DEBUG_CONNECTION
 #include "debug-internal.h"
@@ -62,7 +64,7 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE(TpBaseConnection,
     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION,
       service_iface_init);
     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
-      tp_dbus_properties_mixin_iface_init))
+      tp_dbus_properties_mixin_iface_init));
 
 enum
 {
@@ -85,17 +87,31 @@ static guint signals[N_SIGNALS] = {0};
 
 typedef struct _ChannelRequest ChannelRequest;
 
+typedef enum {
+    METHOD_REQUEST_CHANNEL,
+    METHOD_CREATE_CHANNEL,
+#if 0
+    METHOD_ENSURE_CHANNEL,
+#endif
+    NUM_METHODS
+} ChannelRequestMethod;
+
 struct _ChannelRequest
 {
   DBusGMethodInvocation *context;
+  ChannelRequestMethod method;
+
+  /* relevant if the method is METHOD_REQUEST_CHANNEL */
   gchar *channel_type;
   guint handle_type;
   guint handle;
+  /* always TRUE for CREATE */
   gboolean suppress_handler;
 };
 
 static ChannelRequest *
 channel_request_new (DBusGMethodInvocation *context,
+                     ChannelRequestMethod method,
                      const char *channel_type,
                      guint handle_type,
                      guint handle,
@@ -105,9 +121,11 @@ channel_request_new (DBusGMethodInvocation *context,
 
   g_assert (NULL != context);
   g_assert (NULL != channel_type);
+  g_assert (method < NUM_METHODS);
 
   ret = g_slice_new0 (ChannelRequest);
   ret->context = context;
+  ret->method = method;
   ret->channel_type = g_strdup (channel_type);
   ret->handle_type = handle_type;
   ret->handle = handle;
@@ -155,6 +173,8 @@ struct _TpBaseConnectionPrivate
   gboolean dispose_has_run;
   /* array of (TpChannelFactoryIface *) */
   GPtrArray *channel_factories;
+  /* array of (TpChannelManager *) */
+  GPtrArray *channel_managers;
   /* array of (ChannelRequest *) */
   GPtrArray *channel_requests;
 
@@ -276,6 +296,10 @@ tp_base_connection_dispose (GObject *object)
   g_ptr_array_free (priv->channel_factories, TRUE);
   priv->channel_factories = NULL;
 
+  g_ptr_array_foreach (priv->channel_managers, (GFunc) g_object_unref, NULL);
+  g_ptr_array_free (priv->channel_managers, TRUE);
+  priv->channel_managers = NULL;
+
   if (priv->channel_requests)
     {
       g_assert (priv->channel_requests->len == 0);
@@ -314,6 +338,132 @@ tp_base_connection_finalize (GObject *object)
   G_OBJECT_CLASS (tp_base_connection_parent_class)->finalize (object);
 }
 
+
+/**
+ * exportable_channel_get_old_info:
+ * @channel: a channel
+ * @object_path_out:  address at which to store the channel's object path
+ * @channel_type_out: address at which to store the channel's type
+ * @handle_type_out:  address at which to store the channel's associated handle
+ *                    type
+ * @handle_out:       address at which to store the channel's associated
+ *                    handle, if any
+ *
+ * Given a new-style exportable channel, as used by the Requests interface's
+ * API, fetches the information needed for the old-style ListChannels method
+ * on Connections.
+ */
+static void
+exportable_channel_get_old_info (TpExportableChannel *channel,
+                                 gchar **object_path_out,
+                                 gchar **channel_type_out,
+                                 guint *handle_type_out,
+                                 guint *handle_out)
+{
+  gchar *object_path;
+  GHashTable *channel_properties;
+  gboolean valid;
+
+  g_object_get (channel,
+      "object-path", &object_path,
+      "channel-properties", &channel_properties,
+      NULL);
+
+  g_assert (object_path != NULL);
+  g_assert (tp_dbus_check_valid_object_path (object_path, NULL));
+
+  if (object_path_out != NULL)
+    *object_path_out = object_path;
+  else
+    g_free (object_path);
+
+  if (channel_type_out != NULL)
+    {
+      *channel_type_out = g_strdup (tp_asv_get_string (channel_properties,
+          TP_IFACE_CHANNEL ".ChannelType"));
+      g_assert (*channel_type_out != NULL);
+      g_assert (tp_dbus_check_valid_interface_name (*channel_type_out, NULL));
+    }
+
+  if (handle_type_out != NULL)
+    {
+      *handle_type_out = tp_asv_get_uint32 (channel_properties,
+          TP_IFACE_CHANNEL ".TargetHandleType", &valid);
+      g_assert (valid);
+    }
+
+  if (handle_out != NULL)
+    {
+      *handle_out = tp_asv_get_uint32 (channel_properties,
+          TP_IFACE_CHANNEL ".TargetHandle", &valid);
+      g_assert (valid);
+
+      if (handle_type_out != NULL)
+        {
+          if (*handle_type_out == TP_HANDLE_TYPE_NONE)
+            g_assert (*handle_out == 0);
+          else
+            g_assert (*handle_out != 0);
+        }
+    }
+
+  g_hash_table_destroy (channel_properties);
+}
+
+
+static GValueArray *
+get_channel_details (GObject *obj)
+{
+  GValueArray *structure = g_value_array_new (1);
+  GHashTable *table;
+  GValue *value;
+  gchar *object_path;
+
+  g_object_get (obj,
+      "object-path", &object_path,
+      NULL);
+
+  g_value_array_append (structure, NULL);
+  value = g_value_array_get_nth (structure, 0);
+  g_value_init (value, DBUS_TYPE_G_OBJECT_PATH);
+  g_value_take_boxed (value, object_path);
+  object_path = NULL;
+
+  g_assert (TP_IS_EXPORTABLE_CHANNEL (obj) || TP_IS_CHANNEL_IFACE (obj));
+
+  if (TP_IS_EXPORTABLE_CHANNEL (obj))
+    {
+      g_object_get (obj,
+          "channel-properties", &table,
+          NULL);
+    }
+  else
+    {
+     table = g_hash_table_new_full (g_str_hash, g_str_equal,
+          NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+      value = tp_g_value_slice_new (G_TYPE_UINT);
+      g_object_get_property (obj, "handle", value);
+      g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandle", value);
+
+      value = tp_g_value_slice_new (G_TYPE_UINT);
+      g_object_get_property (obj, "handle-type", value);
+      g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType", value);
+
+      value = tp_g_value_slice_new (G_TYPE_STRING);
+      g_object_get_property (obj, "channel-type", value);
+      g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType", value);
+    }
+
+  g_value_array_append (structure, NULL);
+  value = g_value_array_get_nth (structure, 1);
+  g_value_init (value, TP_HASH_TYPE_STRING_VARIANT_MAP);
+  g_value_take_boxed (value, table);
+
+  return structure;
+}
+
+
 static GPtrArray *
 find_matching_channel_requests (TpBaseConnection *conn,
                                 const gchar *channel_type,
@@ -335,7 +485,8 @@ find_matching_channel_requests (TpBaseConnection *conn,
        * satisfy the request for which it was returned as EXISTING).
        */
       g_assert (handle == 0);
-      g_assert (channel_request == NULL || tp_g_ptr_array_contains (priv->channel_requests, channel_request));
+      g_assert (channel_request == NULL ||
+          tp_g_ptr_array_contains (priv->channel_requests, channel_request));
 
       if (channel_request)
         {
@@ -356,7 +507,7 @@ find_matching_channel_requests (TpBaseConnection *conn,
     {
       ChannelRequest *request = g_ptr_array_index (priv->channel_requests, i);
 
-      if (0 != strcmp (request->channel_type, channel_type))
+      if (tp_strdiff (request->channel_type, channel_type))
         continue;
 
       if (handle_type != request->handle_type)
@@ -374,11 +525,57 @@ find_matching_channel_requests (TpBaseConnection *conn,
   /* if this channel was created or returned as a result of a particular
    * request, that request had better be among the matching ones in the queue
    */
-  g_assert (channel_request == NULL || tp_g_ptr_array_contains (requests, channel_request));
+  g_assert (channel_request == NULL ||
+      tp_g_ptr_array_contains (requests, channel_request));
 
   return requests;
 }
 
+
+static void
+satisfy_request (TpBaseConnection *conn,
+                 ChannelRequest *request,
+                 GObject *channel,
+                 const gchar *object_path)
+{
+  TpBaseConnectionPrivate *priv = TP_BASE_CONNECTION_GET_PRIVATE (conn);
+  DEBUG ("completing queued request %p with success, "
+      "channel_type=%s, handle_type=%u, "
+      "handle=%u, suppress_handler=%u", request, request->channel_type,
+      request->handle_type, request->handle, request->suppress_handler);
+
+  switch (request->method)
+    {
+    case METHOD_REQUEST_CHANNEL:
+      tp_svc_connection_return_from_request_channel (request->context,
+          object_path);
+      break;
+
+    case METHOD_CREATE_CHANNEL:
+        {
+          GHashTable *properties;
+
+          g_assert (TP_IS_EXPORTABLE_CHANNEL (channel));
+          g_object_get (channel,
+              "channel-properties", &properties,
+              NULL);
+          tp_svc_connection_interface_requests_return_from_create_channel (
+              request->context, object_path, properties);
+          g_hash_table_destroy (properties);
+        }
+        break;
+
+    default:
+      g_assert_not_reached ();
+    }
+  request->context = NULL;
+
+  g_ptr_array_remove (priv->channel_requests, request);
+
+  channel_request_free (request);
+}
+
+
 static void
 satisfy_requests (TpBaseConnection *conn,
                   TpChannelFactoryIface *factory,
@@ -386,7 +583,6 @@ satisfy_requests (TpBaseConnection *conn,
                   ChannelRequest *channel_request,
                   gboolean is_new)
 {
-  TpBaseConnectionPrivate *priv = TP_BASE_CONNECTION_GET_PRIVATE (conn);
   gchar *object_path = NULL, *channel_type = NULL;
   guint handle_type = 0, handle = 0;
   gboolean suppress_handler = FALSE;
@@ -407,33 +603,47 @@ satisfy_requests (TpBaseConnection *conn,
                                         &suppress_handler);
 
   if (is_new)
-    tp_svc_connection_emit_new_channel (conn, object_path, channel_type,
-        handle_type, handle, suppress_handler);
-
-  for (i = 0; i < tmp->len; i++)
     {
-      ChannelRequest *request = g_ptr_array_index (tmp, i);
-
-      DEBUG ("completing queued request %p with success, "
-          "channel_type=%s, handle_type=%u, "
-          "handle=%u, suppress_handler=%u", request, request->channel_type,
-          request->handle_type, request->handle, request->suppress_handler);
+      GPtrArray *array = g_ptr_array_sized_new (1);
 
-      tp_svc_connection_return_from_request_channel (request->context,
-          object_path);
-      request->context = NULL;
+      tp_svc_connection_emit_new_channel (conn, object_path, channel_type,
+          handle_type, handle, suppress_handler);
 
-      g_ptr_array_remove (priv->channel_requests, request);
-
-      channel_request_free (request);
+      g_ptr_array_add (array, get_channel_details (G_OBJECT (chan)));
+      tp_svc_connection_interface_requests_emit_new_channels (conn, array);
+      g_value_array_free (g_ptr_array_index (array, 0));
+      g_ptr_array_free (array, TRUE);
     }
 
+  for (i = 0; i < tmp->len; i++)
+    satisfy_request (conn, g_ptr_array_index (tmp, i), G_OBJECT (chan),
+        object_path);
+
   g_ptr_array_free (tmp, TRUE);
 
   g_free (object_path);
   g_free (channel_type);
 }
 
+
+/* Channel factory signal handlers */
+
+static void
+channel_closed_cb (GObject *channel,
+                   TpBaseConnection *conn)
+{
+  gchar *object_path;
+
+  g_object_get (channel,
+      "object-path", &object_path,
+      NULL);
+
+  tp_svc_connection_interface_requests_emit_channel_closed (conn,
+      object_path);
+
+  g_free (object_path);
+}
+
 static void
 connection_new_channel_cb (TpChannelFactoryIface *factory,
                            GObject *chan,
@@ -442,8 +652,31 @@ connection_new_channel_cb (TpChannelFactoryIface *factory,
 {
   satisfy_requests (TP_BASE_CONNECTION (data), factory,
       TP_CHANNEL_IFACE (chan), channel_request, TRUE);
+
+  g_signal_connect (chan, "closed", (GCallback) channel_closed_cb, data);
 }
 
+
+static void
+fail_channel_request (TpBaseConnection *conn,
+                      ChannelRequest *request,
+                      GError *error)
+{
+  TpBaseConnectionPrivate *priv = TP_BASE_CONNECTION_GET_PRIVATE (conn);
+  DEBUG ("completing queued request %p with error, channel_type=%s, "
+      "handle_type=%u, handle=%u, suppress_handler=%u",
+      request, request->channel_type,
+      request->handle_type, request->handle, request->suppress_handler);
+
+  dbus_g_method_return_error (request->context, error);
+  request->context = NULL;
+
+  g_ptr_array_remove (priv->channel_requests, request);
+
+  channel_request_free (request);
+}
+
+
 static void
 connection_channel_error_cb (TpChannelFactoryIface *factory,
                              GObject *chan,
@@ -452,7 +685,6 @@ connection_channel_error_cb (TpChannelFactoryIface *factory,
                              gpointer data)
 {
   TpBaseConnection *conn = TP_BASE_CONNECTION (data);
-  TpBaseConnectionPrivate *priv = TP_BASE_CONNECTION_GET_PRIVATE (conn);
   gchar *channel_type = NULL;
   guint handle_type = 0, handle = 0;
   GPtrArray *tmp;
@@ -472,26 +704,141 @@ connection_channel_error_cb (TpChannelFactoryIface *factory,
                                         handle, channel_request, NULL);
 
   for (i = 0; i < tmp->len; i++)
-    {
-      ChannelRequest *request = g_ptr_array_index (tmp, i);
+    fail_channel_request (conn, g_ptr_array_index (tmp, i), error);
 
-      DEBUG ("completing queued request %p with error, channel_type=%s, "
-          "handle_type=%u, handle=%u, suppress_handler=%u",
-          request, request->channel_type,
-          request->handle_type, request->handle, request->suppress_handler);
+  g_ptr_array_free (tmp, TRUE);
+  g_free (channel_type);
+}
 
-      dbus_g_method_return_error (request->context, error);
-      request->context = NULL;
 
-      g_ptr_array_remove (priv->channel_requests, request);
+/* Channel manager signal handlers */
 
-      channel_request_free (request);
+static void
+manager_new_channel (gpointer key,
+                     gpointer value,
+                     gpointer data)
+{
+  TpExportableChannel *channel = TP_EXPORTABLE_CHANNEL (key);
+  GSList *request_tokens = value;
+  TpBaseConnection *self = TP_BASE_CONNECTION (data);
+  gchar *object_path, *channel_type;
+  guint handle_type, handle;
+  GSList *iter;
+  gboolean suppress_handler = FALSE;
+
+  exportable_channel_get_old_info (channel, &object_path, &channel_type,
+      &handle_type, &handle);
+
+  for (iter = request_tokens; iter != NULL; iter = iter->next)
+    {
+      ChannelRequest *request = iter->data;
+
+      if (request->suppress_handler)
+        {
+          suppress_handler = TRUE;
+          break;
+        }
     }
 
-  g_ptr_array_free (tmp, TRUE);
+  tp_svc_connection_emit_new_channel (self, object_path, channel_type,
+      handle_type, handle, suppress_handler);
+
+  for (iter = request_tokens; iter != NULL; iter = iter->next)
+    {
+      satisfy_request (self, iter->data, G_OBJECT (channel),
+          object_path);
+    }
+
+  g_free (object_path);
   g_free (channel_type);
 }
 
+
+static void
+manager_new_channels_foreach (gpointer key,
+                              gpointer value,
+                              gpointer data)
+{
+  GPtrArray *details = data;
+
+  g_ptr_array_add (details, get_channel_details (G_OBJECT (key)));
+}
+
+
+static void
+manager_new_channels_cb (TpChannelManager *manager,
+                         GHashTable *channels,
+                         TpBaseConnection *self)
+{
+  GPtrArray *array;
+
+  g_assert (TP_IS_CHANNEL_MANAGER (manager));
+  g_assert (TP_IS_BASE_CONNECTION (self));
+
+  array = g_ptr_array_sized_new (g_hash_table_size (channels));
+  g_hash_table_foreach (channels, manager_new_channels_foreach, array);
+  tp_svc_connection_interface_requests_emit_new_channels (self,
+      array);
+  g_ptr_array_foreach (array, (GFunc) g_value_array_free, NULL);
+  g_ptr_array_free (array, TRUE);
+
+  g_hash_table_foreach (channels, manager_new_channel, self);
+}
+
+
+static void
+manager_request_already_satisfied_cb (TpChannelManager *manager,
+                                      gpointer request_token,
+                                      TpExportableChannel *channel,
+                                      TpBaseConnection *self)
+{
+  gchar *object_path;
+
+  g_assert (TP_IS_CHANNEL_MANAGER (manager));
+  g_assert (TP_IS_EXPORTABLE_CHANNEL (channel));
+  g_assert (TP_IS_BASE_CONNECTION (self));
+
+  g_object_get (channel,
+      "object-path", &object_path,
+      NULL);
+
+  satisfy_request (self, request_token, G_OBJECT (channel), object_path);
+  g_free (object_path);
+}
+
+
+static void
+manager_request_failed_cb (TpChannelManager *manager,
+                           gpointer request_token,
+                           guint domain,
+                           gint code,
+                           gchar *message,
+                           TpBaseConnection *self)
+{
+  GError error = { domain, code, message };
+
+  g_assert (TP_IS_CHANNEL_MANAGER (manager));
+  g_assert (domain > 0);
+  g_assert (message != NULL);
+  g_assert (TP_IS_BASE_CONNECTION (self));
+
+  fail_channel_request (self, request_token, &error);
+}
+
+
+static void
+manager_channel_closed_cb (TpChannelManager *manager,
+                           const gchar *path,
+                           TpBaseConnection *self)
+{
+  g_assert (TP_IS_CHANNEL_MANAGER (manager));
+  g_assert (path != NULL);
+  g_assert (TP_IS_BASE_CONNECTION (self));
+
+  tp_svc_connection_interface_requests_emit_channel_closed (self, path);
+}
+
+
 static GObject *
 tp_base_connection_constructor (GType type, guint n_construct_properties,
     GObjectConstructParam *construct_params)
@@ -507,7 +854,8 @@ tp_base_connection_constructor (GType type, guint n_construct_properties,
   DEBUG("Post-construction: (TpBaseConnection *)%p", self);
 
   g_assert (cls->create_handle_repos != NULL);
-  g_assert (cls->create_channel_factories != NULL);
+  g_assert (cls->create_channel_factories != NULL ||
+            cls->create_channel_managers  != NULL);
   g_assert (cls->shut_down != NULL);
   g_assert (cls->start_connecting != NULL);
 
@@ -524,7 +872,15 @@ tp_base_connection_constructor (GType type, guint n_construct_properties,
       }
     }
 
-  priv->channel_factories = cls->create_channel_factories (self);
+  if (cls->create_channel_factories != NULL)
+    priv->channel_factories = cls->create_channel_factories (self);
+  else
+    priv->channel_factories = g_ptr_array_sized_new (0);
+
+  if (cls->create_channel_managers != NULL)
+    priv->channel_managers = cls->create_channel_managers (self);
+  else
+    priv->channel_managers = g_ptr_array_sized_new (0);
 
   for (i = 0; i < priv->channel_factories->len; i++)
     {
@@ -536,6 +892,23 @@ tp_base_connection_constructor (GType type, guint n_construct_properties,
           (connection_channel_error_cb), self);
     }
 
+  for (i = 0; i < priv->channel_managers->len; i++)
+    {
+      TpChannelManager *manager = TP_CHANNEL_MANAGER (
+          g_ptr_array_index (priv->channel_managers, i));
+
+      DEBUG("Channel manager #%u at %p", i, manager);
+
+      g_signal_connect (manager, "new-channels",
+          (GCallback) manager_new_channels_cb, self);
+      g_signal_connect (manager, "request-already-satisfied",
+          (GCallback) manager_request_already_satisfied_cb, self);
+      g_signal_connect (manager, "request-failed",
+          (GCallback) manager_request_failed_cb, self);
+      g_signal_connect (manager, "channel-closed",
+          (GCallback) manager_channel_closed_cb, self);
+    }
+
   return (GObject *)self;
 }
 
@@ -769,6 +1142,11 @@ tp_base_connection_close_all_channels (TpBaseConnection *self)
   TpBaseConnectionPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
       TP_TYPE_BASE_CONNECTION, TpBaseConnectionPrivate);
 
+  /* We deliberately don't iterate over channel managers here -
+   * they don't need this, and are expected to listen to status-changed
+   * for themselves.
+   */
+
   /* trigger close_all on all channel factories */
   g_ptr_array_foreach (priv->channel_factories, (GFunc)
       tp_channel_factory_iface_close_all, NULL);
@@ -1108,13 +1486,12 @@ tp_base_connection_inspect_handles (TpSvcConnection *iface,
 
 /**
  * list_channel_factory_foreach_one:
- * @key: iterated key
- * @value: iterated value
- * @data: data attached to this key/value pair
+ * @chan: a channel
+ * @data: a GPtrArray in which channel information should be stored
  *
- * Called by the exported ListChannels function, this should iterate over
- * the handle/channel pairs in a channel factory, and to the GPtrArray in
- * the data pointer, add a GValueArray containing the following:
+ * Called by the exported ListChannels function for each channel in a channel
+ * factory, this should add to the GPtrArray (in the data pointer) a
+ * GValueArray containing the following:
  *  a D-Bus object path for the channel object on this service
  *  a D-Bus interface name representing the channel type
  *  an integer representing the handle type this channel communicates with,
@@ -1153,6 +1530,51 @@ list_channel_factory_foreach_one (TpChannelIface *chan,
   g_free (type);
 }
 
+
+/**
+ * list_channel_manager_foreach_one:
+ * @chan: a channel
+ * @data: a GPtrArray in which channel information should be stored
+ *
+ * Called by the exported ListChannels function for each channel in a channel
+ * manager, this should add to the GPtrArray (in the data pointer) a
+ * GValueArray containing the following:
+ *  a D-Bus object path for the channel object on this service
+ *  a D-Bus interface name representing the channel type
+ *  an integer representing the handle type this channel communicates with,
+ *    or zero
+ *  an integer handle representing the contact, room or list this channel
+ *    communicates with, or zero
+ */
+static void
+list_channel_manager_foreach_one (TpExportableChannel *channel,
+                                  gpointer data)
+{
+  GPtrArray *values = (GPtrArray *) data;
+  gchar *path, *type;
+  guint handle_type, handle;
+  GValue *entry = tp_dbus_specialized_value_slice_new
+      (TP_STRUCT_TYPE_CHANNEL_INFO);
+
+  g_assert (TP_IS_EXPORTABLE_CHANNEL (channel));
+
+  exportable_channel_get_old_info (channel, &path, &type, &handle_type,
+      &handle);
+
+  dbus_g_type_struct_set (entry,
+      0, path,
+      1, type,
+      2, handle_type,
+      3, handle,
+      G_MAXUINT);
+
+  g_ptr_array_add (values, entry);
+
+  g_free (path);
+  g_free (type);
+}
+
+
 static void
 tp_base_connection_list_channels (TpSvcConnection *iface,
                                   DBusGMethodInvocation *context)
@@ -1169,16 +1591,27 @@ tp_base_connection_list_channels (TpSvcConnection *iface,
   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (self, context);
 
   /* I think on average, each factory will have 2 channels :D */
-  values = g_ptr_array_sized_new (priv->channel_factories->len * 2);
+  values = g_ptr_array_sized_new (priv->channel_factories->len * 2
+      + priv->channel_managers->len * 2);
 
   for (i = 0; i < priv->channel_factories->len; i++)
     {
       TpChannelFactoryIface *factory = g_ptr_array_index
         (priv->channel_factories, i);
+
       tp_channel_factory_iface_foreach (factory,
           list_channel_factory_foreach_one, values);
     }
 
+  for (i = 0; i < priv->channel_managers->len; i++)
+    {
+      TpChannelManager *manager = g_ptr_array_index
+        (priv->channel_managers, i);
+
+      tp_channel_manager_foreach_channel (manager,
+          list_channel_manager_foreach_one, values);
+    }
+
   channels = g_ptr_array_sized_new (values->len);
 
   for (i = 0; i < values->len; i++)
@@ -1222,6 +1655,8 @@ tp_base_connection_request_channel (TpSvcConnection *iface,
   GError *error = NULL;
   guint i;
   ChannelRequest *request;
+  GHashTable *request_properties;
+  GValue *v;
 
   g_assert (TP_IS_BASE_CONNECTION (self));
 
@@ -1229,10 +1664,43 @@ tp_base_connection_request_channel (TpSvcConnection *iface,
 
   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (self, context);
 
-  request = channel_request_new (context, type, handle_type, handle,
-      suppress_handler);
+  request = channel_request_new (context, METHOD_REQUEST_CHANNEL,
+      type, handle_type, handle, suppress_handler);
   g_ptr_array_add (priv->channel_requests, request);
 
+  /* First try the channel managers */
+
+  request_properties = g_hash_table_new_full (g_str_hash, g_str_equal,
+      NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+  v = tp_g_value_slice_new (G_TYPE_STRING);
+  g_value_set_string (v, type);
+  g_hash_table_insert (request_properties, TP_IFACE_CHANNEL ".ChannelType", v);
+
+  v = tp_g_value_slice_new (G_TYPE_UINT);
+  g_value_set_uint (v, handle_type);
+  g_hash_table_insert (request_properties,
+      TP_IFACE_CHANNEL ".TargetHandleType", v);
+
+  v = tp_g_value_slice_new (G_TYPE_UINT);
+  g_value_set_uint (v, handle);
+  g_hash_table_insert (request_properties,
+      TP_IFACE_CHANNEL ".TargetHandle", v);
+
+  for (i = 0; i < priv->channel_managers->len; i++)
+    {
+      TpChannelManager *manager = TP_CHANNEL_MANAGER (
+          g_ptr_array_index (priv->channel_managers, i));
+
+      if (tp_channel_manager_request_channel (manager, request,
+            request_properties))
+        return;
+    }
+
+  g_hash_table_destroy (request_properties);
+
+  /* OK, none of them wanted it. Now try the channel factories */
+
   for (i = 0; i < priv->channel_factories->len; i++)
     {
       TpChannelFactoryIface *factory = g_ptr_array_index
@@ -1250,14 +1718,16 @@ tp_base_connection_request_channel (TpSvcConnection *iface,
             g_assert (NULL != chan);
             satisfy_requests (self, factory, chan, request, FALSE);
             /* satisfy_requests should remove the request */
-            g_assert (!tp_g_ptr_array_contains (priv->channel_requests, request));
+            g_assert (!tp_g_ptr_array_contains (priv->channel_requests,
+                  request));
             return;
           }
         case TP_CHANNEL_FACTORY_REQUEST_STATUS_CREATED:
           g_assert (NULL != chan);
           /* the signal handler should have completed the queued request
            * and freed the ChannelRequest already */
-          g_assert (!tp_g_ptr_array_contains (priv->channel_requests, request));
+          g_assert (!tp_g_ptr_array_contains (priv->channel_requests,
+                request));
           return;
         case TP_CHANNEL_FACTORY_REQUEST_STATUS_QUEUED:
           DEBUG ("queued request, channel_type=%s, handle_type=%u, "
@@ -1819,6 +2289,247 @@ service_iface_init (gpointer g_iface, gpointer iface_data)
 #undef IMPLEMENT
 }
 
+
+static void
+conn_requests_requestotron (TpBaseConnection *self,
+                            GHashTable *requested_properties,
+                            ChannelRequestMethod method,
+                            DBusGMethodInvocation *context)
+{
+  TpBaseConnectionPrivate *priv = TP_BASE_CONNECTION_GET_PRIVATE (self);
+  TpHandleRepoIface *handles;
+  guint i;
+  ChannelRequest *request = NULL;
+  GHashTable *altered_properties = NULL;
+  const gchar *type;
+  TpChannelManagerRequestFunc func;
+  gboolean suppress_handler;
+  TpHandleType target_handle_type;
+  TpHandle target_handle;
+  GValue *target_handle_value = NULL;
+  const gchar *target_id;
+  gboolean valid;
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (self, context);
+
+  type = tp_asv_get_string (requested_properties,
+        TP_IFACE_CHANNEL ".ChannelType");
+
+  if (type == NULL)
+    {
+      GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+          "ChannelType is required" };
+
+      dbus_g_method_return_error (context, &e);
+      goto out;
+    }
+
+  target_handle_type = tp_asv_get_uint32 (requested_properties,
+      TP_IFACE_CHANNEL ".TargetHandleType", &valid);
+
+  /* Allow it to be missing, but not to be otherwise broken */
+  if (!valid && tp_asv_lookup (requested_properties,
+          TP_IFACE_CHANNEL ".TargetHandleType") != NULL)
+    {
+      GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+          "TargetHandleType must be an integer in range 0 to 2**32-1" };
+
+      dbus_g_method_return_error (context, &e);
+      goto out;
+    }
+
+  target_handle = tp_asv_get_uint32 (requested_properties,
+      TP_IFACE_CHANNEL ".TargetHandle", &valid);
+
+  /* Allow it to be missing, but not to be otherwise broken */
+  if (!valid && tp_asv_lookup (requested_properties,
+          TP_IFACE_CHANNEL ".TargetHandle") != NULL)
+    {
+      GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+          "TargetHandle must be an integer in range 0 to 2**32-1" };
+
+      dbus_g_method_return_error (context, &e);
+      goto out;
+    }
+
+  target_id = tp_asv_get_string (requested_properties,
+      TP_IFACE_CHANNEL ".TargetID");
+
+  /* Handle type 0 cannot have a handle */
+  if (target_handle_type == TP_HANDLE_TYPE_NONE && target_handle != 0)
+    {
+      GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+          "When TargetHandleType is NONE, TargetHandle must be omitted or 0" };
+
+      dbus_g_method_return_error (context, &e);
+      goto out;
+    }
+
+  /* Handle type 0 cannot have a target id */
+  if (target_handle_type == TP_HANDLE_TYPE_NONE && target_id != NULL)
+    {
+      GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+          "When TargetHandleType is NONE, TargetID must be omitted" };
+
+      dbus_g_method_return_error (context, &e);
+      goto out;
+    }
+
+  /* FIXME: when InitiatorHandle, InitiatorID and Requested are officially
+   * supported, if the request has any of them, raise an error */
+
+  if (target_handle_type != TP_HANDLE_TYPE_NONE)
+    {
+      GError *error = NULL;
+
+      if ((target_handle == 0 && target_id == NULL) ||
+          (target_handle != 0 && target_id != NULL))
+        {
+          GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+            "Exactly one of TargetHandle and TargetID must be supplied" };
+
+          dbus_g_method_return_error (context, &e);
+          goto out;
+        }
+
+      handles = tp_base_connection_get_handles (self, target_handle_type);
+
+      if (target_handle == 0)
+        {
+          /* Turn TargetID into TargetHandle */
+          target_handle = tp_handle_ensure (handles, target_id, NULL, &error);
+
+          if (target_handle == 0)
+            {
+              dbus_g_method_return_error (context, error);
+              g_error_free (error);
+              goto out;
+            }
+
+          altered_properties = g_hash_table_new_full (g_str_hash, g_str_equal,
+              NULL, NULL);
+          tp_g_hash_table_update (altered_properties, requested_properties,
+              NULL, NULL);
+
+          target_handle_value = tp_g_value_slice_new (G_TYPE_UINT);
+          g_value_set_uint (target_handle_value, target_handle);
+          g_hash_table_insert (altered_properties,
+              TP_IFACE_CHANNEL ".TargetHandle", target_handle_value);
+
+          g_hash_table_remove (altered_properties,
+              TP_IFACE_CHANNEL ".TargetID");
+
+          requested_properties = altered_properties;
+        }
+      else
+        {
+          /* Check the supplied TargetHandle is valid */
+          if (!tp_handle_is_valid (handles, target_handle, &error))
+            {
+              dbus_g_method_return_error (context, error);
+              g_error_free (error);
+              goto out;
+            }
+
+          tp_handle_ref (handles, target_handle);
+        }
+    }
+
+
+  switch (method)
+    {
+    case METHOD_CREATE_CHANNEL:
+      func = tp_channel_manager_create_channel;
+      suppress_handler = TRUE;
+      break;
+
+#if 0
+    case METHOD_ENSURE_CHANNEL:
+      func = tp_channel_manager_ensure_channel;
+      suppress_handler = FALSE;
+      break;
+#endif
+
+    default:
+      g_assert_not_reached ();
+    }
+
+  request = channel_request_new (context, method,
+      type, target_handle_type, target_handle, suppress_handler);
+  g_ptr_array_add (priv->channel_requests, request);
+
+  for (i = 0; i < priv->channel_managers->len; i++)
+    {
+      TpChannelManager *manager = TP_CHANNEL_MANAGER (
+          g_ptr_array_index (priv->channel_managers, i));
+
+      if (func (manager, request, requested_properties))
+        goto out;
+    }
+
+  /* Nobody accepted the request */
+  tp_dbus_g_method_return_not_implemented (context);
+
+  request->context = NULL;
+  g_ptr_array_remove (priv->channel_requests, request);
+  channel_request_free (request);
+
+
+out:
+  if (target_handle != 0)
+    tp_handle_unref (handles, target_handle);
+  if (altered_properties != NULL)
+    g_hash_table_destroy (altered_properties);
+  if (target_handle_value != NULL)
+    tp_g_value_slice_free (target_handle_value);
+
+  return;
+}
+
+
+static void
+conn_requests_create_channel (TpSvcConnectionInterfaceRequests *svc,
+                              GHashTable *requested_properties,
+                              DBusGMethodInvocation *context)
+{
+  TpBaseConnection *self = TP_BASE_CONNECTION (svc);
+
+  return conn_requests_requestotron (self, requested_properties,
+      METHOD_CREATE_CHANNEL, context);
+}
+
+
+#if 0
+static void
+conn_requests_ensure_channel (TpSvcConnectionInterfaceRequests *svc,
+                              GHashTable *requested_properties,
+                              DBusGMethodInvocation *context)
+{
+  TpBaseConnection *self = TP_BASE_CONNECTION (svc);
+
+  return conn_requests_requestotron (self, requested_properties,
+      METHOD_ENSURE_CHANNEL, context);
+}
+#endif
+
+
+void
+tp_base_connection_requests_iface_init (gpointer g_iface,
+                                        gpointer iface_data G_GNUC_UNUSED)
+{
+  TpSvcConnectionInterfaceRequestsClass *iface = g_iface;
+
+#define IMPLEMENT(x) \
+    tp_svc_connection_interface_requests_implement_##x (\
+        iface, conn_requests_##x)
+  IMPLEMENT (create_channel);
+#if 0
+  IMPLEMENT (ensure_channel);
+#endif
+#undef IMPLEMENT
+}
+
+
 static void
 tp_base_connection_fill_contact_attributes (GObject *obj,
   const GArray *contacts, GHashTable *attributes_hash)
@@ -1863,3 +2574,168 @@ tp_base_connection_register_with_contacts_mixin (TpBaseConnection *self)
       TP_IFACE_CONNECTION,
       tp_base_connection_fill_contact_attributes);
 }
+
+
+/* D-Bus properties for the Requests interface */
+
+static void
+factory_get_channel_details_foreach (TpChannelIface *chan,
+                                     gpointer data)
+{
+  GPtrArray *details = data;
+
+  g_ptr_array_add (details, get_channel_details (G_OBJECT (chan)));
+}
+
+
+static void
+manager_get_channel_details_foreach (TpExportableChannel *chan,
+                                     gpointer data)
+{
+  GPtrArray *details = data;
+
+  g_ptr_array_add (details, get_channel_details (G_OBJECT (chan)));
+}
+
+
+static GPtrArray *
+conn_requests_get_channel_details (TpBaseConnection *self)
+{
+  TpBaseConnectionPrivate *priv = TP_BASE_CONNECTION_GET_PRIVATE (self);
+  /* guess that each ChannelManager and each ChannelFactory has two
+   * channels, on average */
+  GPtrArray *details = g_ptr_array_sized_new (priv->channel_managers->len * 2
+      + priv->channel_factories->len * 2);
+  guint i;
+
+  for (i = 0; i < priv->channel_factories->len; i++)
+    {
+      TpChannelFactoryIface *factory = TP_CHANNEL_FACTORY_IFACE (
+          g_ptr_array_index (priv->channel_factories, i));
+
+      tp_channel_factory_iface_foreach (factory,
+          factory_get_channel_details_foreach, details);
+    }
+
+  for (i = 0; i < priv->channel_managers->len; i++)
+    {
+      TpChannelManager *manager = TP_CHANNEL_MANAGER (
+          g_ptr_array_index (priv->channel_managers, i));
+
+      tp_channel_manager_foreach_channel (manager,
+          manager_get_channel_details_foreach, details);
+    }
+
+  return details;
+}
+
+
+static void
+get_requestables_foreach (TpChannelManager *manager,
+                          GHashTable *fixed_properties,
+                          const gchar * const *allowed_properties,
+                          gpointer user_data)
+{
+  GPtrArray *details = user_data;
+  GValueArray *requestable = g_value_array_new (2);
+  GValue *value;
+
+  g_value_array_append (requestable, NULL);
+  value = g_value_array_get_nth (requestable, 0);
+  g_value_init (value, TP_HASH_TYPE_CHANNEL_CLASS);
+  g_value_set_boxed (value, fixed_properties);
+
+  g_value_array_append (requestable, NULL);
+  value = g_value_array_get_nth (requestable, 1);
+  g_value_init (value, G_TYPE_STRV);
+  g_value_set_boxed (value, allowed_properties);
+
+  g_ptr_array_add (details, requestable);
+}
+
+
+static GPtrArray *
+conn_requests_get_requestables (TpBaseConnection *self)
+{
+  TpBaseConnectionPrivate *priv = TP_BASE_CONNECTION_GET_PRIVATE (self);
+  /* generously guess that each ChannelManager has about 2 ChannelClasses */
+  GPtrArray *details = g_ptr_array_sized_new (priv->channel_managers->len * 2);
+  guint i;
+
+  for (i = 0; i < priv->channel_managers->len; i++)
+    {
+      TpChannelManager *manager = TP_CHANNEL_MANAGER (
+          g_ptr_array_index (priv->channel_managers, i));
+
+      tp_channel_manager_foreach_channel_class (manager,
+          get_requestables_foreach, details);
+    }
+
+  return details;
+}
+
+
+static void
+conn_requests_get_dbus_property (GObject *object,
+                                 GQuark interface,
+                                 GQuark name,
+                                 GValue *value,
+                                 gpointer unused G_GNUC_UNUSED)
+{
+  TpBaseConnection *self = TP_BASE_CONNECTION (object);
+
+  g_return_if_fail (interface == TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS);
+
+  if (name == g_quark_from_static_string ("Channels"))
+    {
+      g_value_take_boxed (value, conn_requests_get_channel_details (self));
+    }
+  else if (name == g_quark_from_static_string ("RequestableChannelClasses"))
+    {
+      g_value_take_boxed (value, conn_requests_get_requestables (self));
+    }
+  else
+    {
+      g_return_if_reached ();
+    }
+}
+
+
+static TpDBusPropertiesMixinPropImpl requests_properties[] = {
+      { "Channels", NULL, NULL },
+      { "RequestableChannelClasses", NULL, NULL },
+      { NULL }
+};
+
+
+/**
+ * tp_base_connection_register_requests_dbus_properties:
+ * @cls: A subclass of #TpBaseConnectionClass featuring the D-Bus properties
+ *  mixin, which must also implement
+ *  #TpBaseConnectionClass::create_channel_managers and include
+ *  #TP_SVC_CONNECTION_INTERFACE_REQUESTS in
+ *  #TpBaseConnectionClass::interfaces_always_present
+ *
+ * Implements Connection.Interface.Requests' D-Bus properties for instances of
+ * this subclass, using the subclass' D-Bus properties mixin.  Before calling
+ * this function, the subclass must have initialized the D-Bus properties
+ * mixin, implemented #TpBaseConnectionClass::create_channel_managers and
+ * included #TP_SVC_CONNECTION_INTERFACE_REQUESTS in
+ * #TpBaseConnectionClass::interfaces_always_present.
+ */
+void
+tp_base_connection_register_requests_dbus_properties (GObjectClass *cls)
+{
+  TpBaseConnectionClass *base_class = TP_BASE_CONNECTION_CLASS (cls);
+
+  /* We need some channel managers... */
+  g_assert (base_class->create_channel_managers != NULL);
+  /* ...and to advertise that Requests is supported. */
+  g_assert (tp_strv_contains (base_class->interfaces_always_present,
+      TP_IFACE_CONNECTION_INTERFACE_REQUESTS));
+
+  tp_dbus_properties_mixin_implement_interface (cls,
+      TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS,
+      conn_requests_get_dbus_property, NULL,
+      requests_properties);
+}
diff --git a/telepathy-glib/base-connection.h b/telepathy-glib/base-connection.h
index 49162e6..9567630 100644
--- a/telepathy-glib/base-connection.h
+++ b/telepathy-glib/base-connection.h
@@ -95,7 +95,7 @@ typedef void (*TpBaseConnectionCreateHandleReposImpl) (TpBaseConnection *self,
  * TpBaseConnectionCreateChannelFactoriesImpl:
  * @self: The implementation, a subclass of TpBaseConnection
  *
- * Signature of an implementation of the create_handle_repos method
+ * Signature of an implementation of the create_channel_factories method
  * of #TpBaseConnection.
  *
  * Returns: a GPtrArray of objects implementing #TpChannelFactoryIface
@@ -106,6 +106,20 @@ typedef GPtrArray *(*TpBaseConnectionCreateChannelFactoriesImpl) (
     TpBaseConnection *self);
 
 /**
+ * TpBaseConnectionCreateChannelManagersImpl:
+ * @self: The implementation, a subclass of TpBaseConnection
+ *
+ * Signature of an implementation of the create_channel_managers method
+ * of #TpBaseConnection.
+ *
+ * Returns: a GPtrArray of objects implementing #TpChannelManager
+ * which, between them, implement all channel types this Connection
+ * supports.
+ */
+typedef GPtrArray *(*TpBaseConnectionCreateChannelManagersImpl) (
+    TpBaseConnection *self);
+
+/**
  * TpBaseConnectionGetUniqueConnectionNameImpl:
  * @self: The implementation, a subclass of TpBaseConnection
  *
@@ -127,7 +141,8 @@ typedef gchar *(*TpBaseConnectionGetUniqueConnectionNameImpl) (
  *  Must be set by subclasses to a non-%NULL value; the function must create
  *  at least a CONTACT handle repository (failing to do so will cause a crash).
  * @create_channel_factories: Create an array of channel factories for this
- *  Connection. Must be set by subclasses to a non-%NULL value.
+ *  Connection. At least one of this or @create_channel_managers must be set by
+ *  subclasses to a non-%NULL value.
  * @get_unique_connection_name: Construct a unique name for this connection
  *  (for example using the protocol's format for usernames). If %NULL (the
  *  default), a unique name will be generated. Subclasses should usually
@@ -153,11 +168,15 @@ typedef gchar *(*TpBaseConnectionGetUniqueConnectionNameImpl) (
  *  Individual instances may detect which additional interfaces they support
  *  and signal them before going to state CONNECTED by calling
  *  tp_base_connection_add_interfaces().
+ * @create_channel_managers: Create an array of channel managers for this
+ *  Connection. At least one of this or @create_channel_factories must be set
+ *  by subclasses to a non-%NULL value.
+ *  Since: 0.7.UNRELEASED
  *
  * The class of a #TpBaseConnection. Many members are virtual methods etc.
  * to be filled in in the subclass' class_init function.
  *
- * In addition to the fields documented here, there are four gpointer fields
+ * In addition to the fields documented here, there are three gpointer fields
  * which must currently be %NULL (a meaning may be defined for these in a
  * future version of telepathy-glib), and a pointer to opaque private data.
  */
@@ -180,8 +199,9 @@ struct _TpBaseConnectionClass {
 
     const gchar **interfaces_always_present;
 
+    TpBaseConnectionCreateChannelManagersImpl create_channel_managers;
+
     /*<private>*/
-    gpointer _future1;
     gpointer _future2;
     gpointer _future3;
     gpointer _future4;
@@ -275,6 +295,11 @@ void tp_base_connection_dbus_request_handles (TpSvcConnection *iface,
 
 void tp_base_connection_register_with_contacts_mixin (TpBaseConnection *self);
 
+void tp_base_connection_requests_iface_init (gpointer g_iface,
+    gpointer iface_data);
+
+void tp_base_connection_register_requests_dbus_properties (GObjectClass *cls);
+
 /* TYPE MACROS */
 #define TP_TYPE_BASE_CONNECTION \
   (tp_base_connection_get_type ())
-- 
1.5.6.5




More information about the Telepathy-commits mailing list