[Telepathy-commits] [telepathy-gabble/master] GabbleRoster: implement GabbleExportableChannel
Simon McVittie
simon.mcvittie at collabora.co.uk
Thu Aug 21 08:19:43 PDT 2008
20080801154845-53eee-518ff9bd0ff8266e27632f74b8be7edeb328672d.gz
---
src/roster.c | 361 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 341 insertions(+), 20 deletions(-)
diff --git a/src/roster.c b/src/roster.c
index 3216bb0..1ce794b 100644
--- a/src/roster.c
+++ b/src/roster.c
@@ -27,11 +27,13 @@
#include <string.h>
#include <dbus/dbus-glib.h>
+#include <telepathy-glib/dbus.h>
#include <telepathy-glib/interfaces.h>
#include <telepathy-glib/channel-factory-iface.h>
#define DEBUG_FLAG GABBLE_DEBUG_ROSTER
+#include "channel-manager.h"
#include "conn-aliasing.h"
#include "connection.h"
#include "debug.h"
@@ -69,6 +71,11 @@ struct _GabbleRosterPrivate
GHashTable *group_channels;
GHashTable *items;
+ /* borrowed GabbleExportableChannel * => GSList of gpointer (request tokens)
+ * that will be satisfied when it's ready. The requests are in reverse
+ * chronological order */
+ GHashTable *queued_requests;
+
gboolean roster_received;
gboolean dispose_has_run;
};
@@ -109,6 +116,7 @@ struct _GabbleRosterItem
GabbleRosterItemEdit *unsent_edits;
};
+static void channel_manager_iface_init (gpointer, gpointer);
static void gabble_roster_factory_iface_init (gpointer g_iface,
gpointer iface_data);
static void gabble_roster_init (GabbleRoster *roster);
@@ -127,7 +135,9 @@ static void gabble_roster_close_all (GabbleRoster *roster);
G_DEFINE_TYPE_WITH_CODE (GabbleRoster, gabble_roster, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_FACTORY_IFACE,
- gabble_roster_factory_iface_init));
+ gabble_roster_factory_iface_init);
+ G_IMPLEMENT_INTERFACE (GABBLE_TYPE_CHANNEL_MANAGER,
+ channel_manager_iface_init));
#define GABBLE_ROSTER_GET_PRIVATE(o) ((o)->priv)
@@ -184,6 +194,9 @@ gabble_roster_init (GabbleRoster *obj)
priv->items = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, (GDestroyNotify) _gabble_roster_item_free);
+
+ priv->queued_requests = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
}
void
@@ -205,6 +218,7 @@ gabble_roster_dispose (GObject *object)
gabble_roster_close_all (self);
g_assert (priv->group_channels == NULL);
g_assert (priv->list_channels == NULL);
+ g_assert (priv->queued_requests == NULL);
if (G_OBJECT_CLASS (gabble_roster_parent_class)->dispose)
G_OBJECT_CLASS (gabble_roster_parent_class)->dispose (object);
@@ -493,6 +507,10 @@ _gabble_roster_item_remove (GabbleRoster *roster,
tp_handle_unref (contact_repo, handle);
}
+/* FIXME: we have _get_channel, _create_channel, request_channel and
+ * create_channel - this is confusing, and surely we ought to be able to
+ * simplify the non-API code? */
+
/* the TpHandleType must be GROUP or LIST */
static GabbleRosterChannel *_gabble_roster_get_channel (GabbleRoster *,
TpHandleType, TpHandle, gboolean *created);
@@ -826,6 +844,60 @@ DONE:
return message;
}
+
+static void
+gabble_roster_emit_new_channel (GabbleRoster *self,
+ GabbleRosterChannel *channel)
+{
+ GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (self);
+ GSList *requests_satisfied;
+
+ requests_satisfied = g_hash_table_lookup (priv->queued_requests, channel);
+ g_hash_table_steal (priv->queued_requests, channel);
+ requests_satisfied = g_slist_reverse (requests_satisfied);
+ gabble_channel_manager_emit_new_channel (self,
+ GABBLE_EXPORTABLE_CHANNEL (channel), requests_satisfied);
+ g_slist_free (requests_satisfied);
+
+ tp_channel_factory_iface_emit_new_channel (self,
+ TP_CHANNEL_IFACE (channel), NULL);
+}
+
+
+static void
+roster_channel_closed_cb (GabbleRosterChannel *channel,
+ gpointer user_data)
+{
+ GabbleRoster *self = GABBLE_ROSTER (user_data);
+ guint handle_type, handle;
+ GHashTable *channels;
+
+ DEBUG ("%p, channel %p", self, channel);
+
+ g_object_get (channel,
+ "handle-type", &handle_type,
+ "handle", &handle,
+ NULL);
+
+ g_assert (handle_type == TP_HANDLE_TYPE_LIST ||
+ handle_type == TP_HANDLE_TYPE_GROUP);
+
+ gabble_channel_manager_emit_channel_closed_for_object (self,
+ GABBLE_EXPORTABLE_CHANNEL (channel));
+
+ channels = (handle_type == TP_HANDLE_TYPE_LIST
+ ? self->priv->list_channels
+ : self->priv->group_channels);
+
+ if (channels != NULL)
+ {
+ DEBUG ("removing channel with handle (type %u) #%u", handle_type,
+ handle);
+ g_hash_table_remove (channels, GUINT_TO_POINTER (handle));
+ }
+}
+
+
static GabbleRosterChannel *
_gabble_roster_create_channel (GabbleRoster *roster,
guint handle_type,
@@ -869,6 +941,9 @@ _gabble_roster_create_channel (GabbleRoster *roster,
DEBUG ("created %s", object_path);
+ g_signal_connect (chan, "closed", (GCallback) roster_channel_closed_cb,
+ roster);
+
g_hash_table_insert (channels, GINT_TO_POINTER (handle), chan);
if (priv->roster_received)
@@ -876,8 +951,7 @@ _gabble_roster_create_channel (GabbleRoster *roster,
DEBUG ("roster already received, emitting signal for %s",
object_path);
- tp_channel_factory_iface_emit_new_channel (roster,
- (TpChannelIface *) chan, NULL);
+ gabble_roster_emit_new_channel (roster, chan);
}
else
{
@@ -933,6 +1007,7 @@ struct _EmitOneData {
guint handle_type; /* must be GROUP or LIST */
};
+
static void
_gabble_roster_emit_one (gpointer key,
gpointer value,
@@ -957,8 +1032,7 @@ _gabble_roster_emit_one (gpointer key,
name);
#endif
- tp_channel_factory_iface_emit_new_channel (roster, (TpChannelIface *) chan,
- NULL);
+ gabble_roster_emit_new_channel (roster, chan);
}
static void
@@ -1571,6 +1645,30 @@ OUT:
return ret;
}
+static gboolean
+cancel_queued_requests (gpointer k,
+ gpointer v,
+ gpointer d)
+{
+ GabbleRoster *self = GABBLE_ROSTER (d);
+ GSList *requests_satisfied = v;
+ GSList *iter;
+
+ requests_satisfied = g_slist_reverse (requests_satisfied);
+
+ for (iter = requests_satisfied; iter != NULL; iter = iter->next)
+ {
+ gabble_channel_manager_emit_request_failed (self,
+ iter->data, TP_ERRORS, TP_ERROR_DISCONNECTED,
+ "Unable to complete this channel request, we're disconnecting!");
+ }
+
+ g_slist_free (requests_satisfied);
+
+ return TRUE;
+}
+
+
static void
gabble_roster_close_all (GabbleRoster *self)
{
@@ -1578,6 +1676,14 @@ gabble_roster_close_all (GabbleRoster *self)
DEBUG ("closing channels");
+ if (priv->queued_requests != NULL)
+ {
+ g_hash_table_foreach_steal (priv->queued_requests,
+ cancel_queued_requests, self);
+ g_hash_table_destroy (priv->queued_requests);
+ priv->queued_requests = NULL;
+ }
+
if (priv->group_channels)
{
g_hash_table_destroy (priv->group_channels);
@@ -1675,27 +1781,29 @@ gabble_roster_constructor (GType type, guint n_props,
struct foreach_data {
- TpChannelFunc func;
+ GabbleExportableChannelFunc func;
gpointer data;
};
static void
-_gabble_roster_factory_iface_foreach_one (gpointer key,
+_gabble_roster_foreach_channel_helper (gpointer key,
gpointer value,
gpointer data)
{
- TpChannelIface *chan = TP_CHANNEL_IFACE (value);
+ GabbleExportableChannel *chan = GABBLE_EXPORTABLE_CHANNEL (value);
struct foreach_data *foreach = (struct foreach_data *) data;
+ g_assert (TP_IS_CHANNEL_IFACE (chan));
+
foreach->func (chan, foreach->data);
}
static void
-gabble_roster_factory_iface_foreach (TpChannelFactoryIface *iface,
- TpChannelFunc func,
- gpointer data)
+gabble_roster_foreach_channel (GabbleChannelManager *manager,
+ GabbleExportableChannelFunc func,
+ gpointer data)
{
- GabbleRoster *roster = GABBLE_ROSTER (iface);
+ GabbleRoster *roster = GABBLE_ROSTER (manager);
GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (roster);
struct foreach_data foreach;
@@ -1703,11 +1811,26 @@ gabble_roster_factory_iface_foreach (TpChannelFactoryIface *iface,
foreach.data = data;
g_hash_table_foreach (priv->group_channels,
- _gabble_roster_factory_iface_foreach_one, &foreach);
+ _gabble_roster_foreach_channel_helper, &foreach);
g_hash_table_foreach (priv->list_channels,
- _gabble_roster_factory_iface_foreach_one, &foreach);
+ _gabble_roster_foreach_channel_helper, &foreach);
+}
+
+
+static void
+gabble_roster_associate_request (GabbleRoster *self,
+ GabbleRosterChannel *channel,
+ gpointer request)
+{
+ GabbleRosterPrivate *priv = GABBLE_ROSTER_GET_PRIVATE (self);
+ GSList *list = g_hash_table_lookup (priv->queued_requests, channel);
+
+ g_hash_table_steal (priv->queued_requests, channel);
+ list = g_slist_prepend (list, request);
+ g_hash_table_insert (priv->queued_requests, channel, list);
}
+
static TpChannelFactoryRequestStatus
gabble_roster_factory_iface_request (TpChannelFactoryIface *iface,
const gchar *chan_type,
@@ -1724,32 +1847,61 @@ gabble_roster_factory_iface_request (TpChannelFactoryIface *iface,
gboolean created;
GabbleRosterChannel *chan;
+ g_assert (request != NULL);
+
if (strcmp (chan_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST))
return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_IMPLEMENTED;
if (handle_type != TP_HANDLE_TYPE_LIST &&
handle_type != TP_HANDLE_TYPE_GROUP)
- return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_AVAILABLE;
+ {
+ /* FIXME: should this be NotImplemented? */
+ gabble_channel_manager_emit_request_failed_printf (roster, request,
+ TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Can't make ContactList channels of handle type %u", handle_type);
+ return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_AVAILABLE;
+ }
if (!tp_handle_is_valid (handle_repo, handle, NULL))
- return TP_CHANNEL_FACTORY_REQUEST_STATUS_INVALID_HANDLE;
+ {
+ gabble_channel_manager_emit_request_failed (roster, request,
+ TP_ERRORS, TP_ERROR_INVALID_HANDLE, "Invalid handle");
+ return TP_CHANNEL_FACTORY_REQUEST_STATUS_INVALID_HANDLE;
+ }
/* disallow "deny" channels if we don't have google:roster support */
if (handle == GABBLE_LIST_HANDLE_DENY &&
handle_type == TP_HANDLE_TYPE_LIST &&
!(priv->conn->features & GABBLE_CONNECTION_FEATURES_GOOGLE_ROSTER))
- return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_AVAILABLE;
+ {
+ /* FIXME: should this be NotImplemented? */
+ gabble_channel_manager_emit_request_failed (roster, request,
+ TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "This server does not have Google roster extensions, so there's "
+ "no deny list");
+ return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_AVAILABLE;
+ }
chan = _gabble_roster_get_channel (roster, handle_type, handle,
&created);
if (priv->roster_received)
{
*ret = TP_CHANNEL_IFACE (chan);
- return created ? TP_CHANNEL_FACTORY_REQUEST_STATUS_CREATED
- : TP_CHANNEL_FACTORY_REQUEST_STATUS_EXISTING;
+
+ if (created)
+ {
+ return TP_CHANNEL_FACTORY_REQUEST_STATUS_CREATED;
+ }
+ else
+ {
+ gabble_channel_manager_emit_request_already_satisfied (roster,
+ request, GABBLE_EXPORTABLE_CHANNEL (chan));
+ return TP_CHANNEL_FACTORY_REQUEST_STATUS_EXISTING;
+ }
}
else
{
+ gabble_roster_associate_request (roster, chan, request);
return TP_CHANNEL_FACTORY_REQUEST_STATUS_QUEUED;
}
}
@@ -1761,7 +1913,8 @@ gabble_roster_factory_iface_init (gpointer g_iface,
TpChannelFactoryIfaceClass *klass = (TpChannelFactoryIfaceClass *) g_iface;
klass->close_all = (TpChannelFactoryIfaceProc) gabble_roster_close_all;
- klass->foreach = gabble_roster_factory_iface_foreach;
+ klass->foreach =
+ (TpChannelFactoryIfaceForeachImpl) gabble_roster_foreach_channel;
klass->request = gabble_roster_factory_iface_request;
}
@@ -2405,3 +2558,171 @@ gabble_roster_handle_remove_from_group (GabbleRoster *roster,
return ret;
}
+
+
+static const gchar * const list_channel_required_properties[] = {
+ TP_IFACE_CHANNEL ".TargetHandle",
+ NULL
+};
+static const gchar * const *group_channel_required_properties =
+ list_channel_required_properties;
+
+
+static const gchar * const list_channel_optional_properties[] = {
+ NULL
+};
+static const gchar * const *group_channel_optional_properties =
+ list_channel_optional_properties;
+
+
+static void
+gabble_roster_foreach_channel_class (GabbleChannelManager *manager,
+ GabbleChannelManagerChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+ GValue *value, *handle_type_value;
+
+ value = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType", value);
+
+ handle_type_value = tp_g_value_slice_new (G_TYPE_UINT);
+ /* no uint value yet - we'll change it for each channel class */
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType",
+ handle_type_value);
+
+ g_value_set_uint (handle_type_value, TP_HANDLE_TYPE_GROUP);
+ func (manager, table, group_channel_required_properties,
+ group_channel_optional_properties, user_data);
+
+ /* FIXME: should these actually be in RequestableChannelClasses? You can't
+ * usefully call CreateChannel on them, although EnsureChannel would be
+ * OK. */
+ /* FIXME: since we have a finite set of possible values for TargetHandle,
+ * should we enumerate them all as separate channel classes? */
+ g_value_set_uint (handle_type_value, TP_HANDLE_TYPE_LIST);
+ func (manager, table, list_channel_required_properties,
+ list_channel_optional_properties, user_data);
+
+ g_hash_table_destroy (table);
+}
+
+
+static gboolean
+gabble_roster_request (GabbleRoster *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ gboolean created;
+ GabbleRosterChannel *channel;
+ TpHandleType handle_type;
+ TpHandle handle;
+ GError *error = NULL;
+ TpHandleRepoIface *handle_repo;
+
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL ".ChannelType"),
+ TP_IFACE_CHANNEL_TYPE_CONTACT_LIST))
+ return FALSE;
+
+ handle_type = tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", NULL);
+
+ if (handle_type != TP_HANDLE_TYPE_LIST &&
+ handle_type != TP_HANDLE_TYPE_GROUP)
+ return FALSE;
+
+ handle_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self->priv->conn, handle_type);
+
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandle", NULL);
+
+ if (!tp_handle_is_valid (handle_repo, handle, &error))
+ goto error;
+
+ /* disallow "deny" channels if we don't have google:roster support */
+ if (handle_type == TP_HANDLE_TYPE_LIST &&
+ handle == GABBLE_LIST_HANDLE_DENY &&
+ !(self->priv->conn->features & GABBLE_CONNECTION_FEATURES_GOOGLE_ROSTER))
+ {
+ /* FIXME: should this be NotImplemented? */
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "This server does not have Google roster extensions, so there's "
+ "no deny list");
+ goto error;
+ }
+
+ channel = _gabble_roster_get_channel (self, handle_type, handle,
+ &created);
+
+ if (require_new && !created)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "That contact list has already been created (or requested)");
+ goto error;
+ }
+
+ if (self->priv->roster_received)
+ {
+ if (!created)
+ gabble_channel_manager_emit_request_already_satisfied (self,
+ request_token, GABBLE_EXPORTABLE_CHANNEL (channel));
+ }
+ else
+ {
+ gabble_roster_associate_request (self, channel, request_token);
+ }
+
+ return TRUE;
+
+error:
+ gabble_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+
+static gboolean
+gabble_roster_create_channel (GabbleChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ GabbleRoster *self = GABBLE_ROSTER (manager);
+
+ /* FIXME: the channel will come out with Requested=FALSE... is this
+ * reasonable? Or should we just deny all attempts to CreateChannel() on this
+ * factory? */
+
+ return gabble_roster_request (self, request_token, request_properties,
+ TRUE);
+}
+
+
+static gboolean
+gabble_roster_request_channel (GabbleChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ GabbleRoster *self = GABBLE_ROSTER (manager);
+
+ return gabble_roster_request (self, request_token, request_properties,
+ FALSE);
+}
+
+
+static void
+channel_manager_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ GabbleChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = gabble_roster_foreach_channel;
+ iface->foreach_channel_class = gabble_roster_foreach_channel_class;
+ iface->request_channel = gabble_roster_request_channel;
+ iface->create_channel = gabble_roster_create_channel;
+}
--
1.5.6.3
More information about the Telepathy-commits
mailing list