-/* * Copyright (C) 2007-2008 Collabora Ltd.
+/* * Copyright (C) 2007-2009 Collabora Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Sjoerd Simons <sjoerd.simons@collabora.co.uk>
+ * Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
*/
#include <config.h>
#include <telepathy-glib/proxy-subclass.h>
#include <telepathy-glib/gtypes.h>
-#include <libmissioncontrol/mission-control.h>
-
#include <extensions/extensions.h>
#include "empathy-dispatcher.h"
/* channels which the dispatcher is listening "invalidated" */
GList *channels;
+
+ GHashTable *request_channel_class_async_ids;
} EmpathyDispatcherPriv;
G_DEFINE_TYPE (EmpathyDispatcher, empathy_dispatcher, G_TYPE_OBJECT);
static guint signals[LAST_SIGNAL];
static EmpathyDispatcher *dispatcher = NULL;
+static GList * empathy_dispatcher_find_channel_classes
+ (EmpathyDispatcher *dispatcher, TpConnection *connection,
+ const gchar *channel_type, guint handle_type, GArray *fixed_properties);
+
+
typedef struct
{
EmpathyDispatcher *dispatcher;
GHashTable *dispatched_channels;
/* ObjectPath -> EmpathyDispatchOperations */
GHashTable *dispatching_channels;
- /* ObjectPath -> EmpathyDispatchOperations */
+
+ /* ObjectPath -> EmpathyDispatchOperations
+ *
+ * This holds channels which were announced with NewChannel while we have an
+ * outstanding channel request for a channel of this type. On the Requests
+ * interface, CreateChannel and EnsureChannel are guaranteed by the spec to
+ * return before NewChannels is emitted, but there was no guarantee of the
+ * ordering of RequestChannel vs. NewChannel. So if necessary, channels are
+ * held in limbo here until we know whether they were requested.
+ */
GHashTable *outstanding_channels;
/* List of DispatcherRequestData */
GList *outstanding_requests;
TpConnection *connection;
char *channel_type;
guint handle_type;
+ GArray *properties;
EmpathyDispatcherFindChannelClassCb *callback;
gpointer user_data;
} FindChannelRequest;
static void
free_find_channel_request (FindChannelRequest *r)
{
+ int idx;
+ char *str;
+
g_object_unref (r->dispatcher);
g_free (r->channel_type);
+
+ if (r->properties != NULL)
+ {
+ for (idx = 0; idx < r->properties->len ; idx++)
+ {
+ str = g_array_index (r->properties, char *, idx);
+ g_free (str);
+ }
+
+ g_array_free (r->properties, TRUE);
+ }
+
g_slice_free (FindChannelRequest, r);
}
if (dispatcher_operation_can_start (self, operation, cd))
{
- dispatcher_start_dispatching (dispatcher, operation, cd);
g_hash_table_iter_remove (&iter);
+ dispatcher_start_dispatching (dispatcher, operation, cd);
}
}
}
ConnectionData *cd;
GList *requests, *l;
FindChannelRequest *request;
- GStrv retval;
+ GList *retval;
cd = g_hash_table_lookup (priv->connections, proxy);
g_assert (cd != NULL);
{
request = l->data;
- retval = empathy_dispatcher_find_channel_class (dispatcher,
+ retval = empathy_dispatcher_find_channel_classes (dispatcher,
TP_CONNECTION (proxy), request->channel_type,
- request->handle_type);
+ request->handle_type, request->properties);
request->callback (retval, request->user_data);
free_find_channel_request (request);
+ g_list_free (retval);
}
g_list_free (requests);
g_ptr_array_free (capabilities, TRUE);
}
+static void
+remove_idle_handlers (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ guint source_id;
+
+ source_id = GPOINTER_TO_UINT (value);
+ g_source_remove (source_id);
+}
+
static GObject *
dispatcher_constructor (GType type,
guint n_construct_params,
gpointer connection;
GList *list;
+ if (priv->request_channel_class_async_ids != NULL)
+ {
+ g_hash_table_foreach (priv->request_channel_class_async_ids,
+ remove_idle_handlers, NULL);
+ g_hash_table_destroy (priv->request_channel_class_async_ids);
+ }
+
g_signal_handlers_disconnect_by_func (priv->account_manager,
dispatcher_new_connection_cb, object);
g_object_unref (l->data);
}
g_list_free (connections);
+
+ priv->request_channel_class_async_ids = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
}
EmpathyDispatcher *
G_OBJECT (request_data->dispatcher));
}
-GStrv
-empathy_dispatcher_find_channel_class (EmpathyDispatcher *dispatcher,
- TpConnection *connection,
- const gchar *channel_type,
- guint handle_type)
+static gboolean
+channel_class_matches (GValueArray *class,
+ const char *channel_type,
+ guint handle_type,
+ GArray *fixed_properties)
+{
+ GHashTable *fprops;
+ GValue *v;
+ const char *c_type;
+ guint h_type;
+ gboolean valid;
+
+ v = g_value_array_get_nth (class, 0);
+
+ /* if the class doesn't match channel type discard it. */
+ fprops = g_value_get_boxed (v);
+ c_type = tp_asv_get_string (fprops, TP_IFACE_CHANNEL ".ChannelType");
+
+ if (tp_strdiff (channel_type, c_type))
+ return FALSE;
+
+ /* we have the right channel type, see if the handle type matches */
+ h_type = tp_asv_get_uint32 (fprops,
+ TP_IFACE_CHANNEL ".TargetHandleType", &valid);
+
+ if (!valid || handle_type != h_type)
+ return FALSE;
+
+ if (fixed_properties != NULL)
+ {
+ gpointer h_key, h_val;
+ int idx;
+ GHashTableIter iter;
+ gboolean found;
+
+ g_hash_table_iter_init (&iter, fprops);
+
+ while (g_hash_table_iter_next (&iter, &h_key, &h_val))
+ {
+ /* discard ChannelType and TargetHandleType, as we already
+ * checked them.
+ */
+ if (!tp_strdiff ((char *) h_key, TP_IFACE_CHANNEL ".ChannelType") ||
+ !tp_strdiff
+ ((char *) h_key, TP_IFACE_CHANNEL ".TargetHandleType"))
+ continue;
+
+ found = FALSE;
+
+ for (idx = 0; idx < fixed_properties->len; idx++)
+ {
+ /* if |key| doesn't exist in |fixed_properties|, discard
+ * the class.
+ */
+ if (!tp_strdiff
+ ((char *) h_key,
+ g_array_index (fixed_properties, char *, idx)))
+ {
+ found = TRUE;
+ /* exit the for() loop */
+ break;
+ }
+ }
+
+ if (!found)
+ return FALSE;
+ }
+ }
+ else
+ {
+ /* if no fixed_properties are specified, discard the classes
+ * with some fixed properties other than the two we already
+ * checked.
+ */
+ if (g_hash_table_size (fprops) > 2)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static GList *
+empathy_dispatcher_find_channel_classes (EmpathyDispatcher *dispatcher,
+ TpConnection *connection,
+ const gchar *channel_type,
+ guint handle_type,
+ GArray *fixed_properties)
{
EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
- ConnectionData *cd;
- int i;
+ GValueArray *class;
GPtrArray *classes;
+ GList *matching_classes;
+ int i;
+ ConnectionData *cd;
g_return_val_if_fail (channel_type != NULL, NULL);
g_return_val_if_fail (handle_type != 0, NULL);
if (classes == NULL)
return NULL;
+ matching_classes = NULL;
+
for (i = 0; i < classes->len; i++)
{
- GValueArray *class;
- GValue *fixed;
- GValue *allowed;
- GHashTable *fprops;
- const gchar *c_type;
- guint32 h_type;
- gboolean valid;
-
class = g_ptr_array_index (classes, i);
- fixed = g_value_array_get_nth (class, 0);
- fprops = g_value_get_boxed (fixed);
- c_type = tp_asv_get_string (fprops, TP_IFACE_CHANNEL ".ChannelType");
-
- if (tp_strdiff (channel_type, c_type))
+ if (!channel_class_matches
+ (class, channel_type, handle_type, fixed_properties))
continue;
- h_type = tp_asv_get_uint32 (fprops,
- TP_IFACE_CHANNEL ".TargetHandleType", &valid);
-
- if (!valid || handle_type != h_type)
- continue;
-
- allowed = g_value_array_get_nth (class, 1);
-
- return g_value_get_boxed (allowed);
+ matching_classes = g_list_prepend (matching_classes, class);
}
- return NULL;
+ return matching_classes;
}
static gboolean
find_channel_class_idle_cb (gpointer user_data)
{
- GStrv retval;
+ GList *retval;
GList *requests;
FindChannelRequest *request = user_data;
+ ConnectionData *cd;
+ gboolean is_ready = TRUE;
EmpathyDispatcherPriv *priv = GET_PRIV (request->dispatcher);
- retval = empathy_dispatcher_find_channel_class (request->dispatcher,
- request->connection, request->channel_type, request->handle_type);
+ g_hash_table_remove (priv->request_channel_class_async_ids, request);
+
+ cd = g_hash_table_lookup (priv->connections, request->connection);
+
+ if (cd == NULL)
+ is_ready = FALSE;
+ else if (cd->requestable_channels == NULL)
+ is_ready = FALSE;
- if (retval)
+ if (is_ready)
{
+ retval = empathy_dispatcher_find_channel_classes (request->dispatcher,
+ request->connection, request->channel_type, request->handle_type,
+ request->properties);
+
request->callback (retval, request->user_data);
free_find_channel_request (request);
+ g_list_free (retval);
+
return FALSE;
}
return FALSE;
}
+static GArray *
+setup_varargs (va_list var_args,
+ const char *channel_namespace,
+ const char *first_property_name)
+{
+ const char *name;
+ char *name_full;
+ GArray *properties;
+
+ if (first_property_name == NULL)
+ return NULL;
+
+ name = first_property_name;
+ properties = g_array_new (TRUE, TRUE, sizeof (char *));
+
+ while (name != NULL)
+ {
+ name_full = g_strdup (name);
+ properties = g_array_append_val (properties, name_full);
+ name = va_arg (var_args, char *);
+ }
+
+ return properties;
+}
+
+/**
+ * empathy_dispatcher_find_requestable_channel_classes:
+ * @dispatcher: an #EmpathyDispatcher
+ * @connection: a #TpConnection
+ * @channel_type: a string identifying the type of the channel to lookup
+ * @handle_type: the handle type for the channel
+ * @first_property_name: %NULL, or the name of the first fixed property,
+ * followed optionally by more names, followed by %NULL.
+ *
+ * Returns all the channel classes that a client can request for the connection
+ * @connection, of the type identified by @channel_type, @handle_type and the
+ * fixed properties list.
+ * The classes which are compatible with a fixed properties list (i.e. those
+ * that will be returned by this function) are intended as those that do not
+ * contain any fixed property other than those in the list; note that this
+ * doesn't guarantee that all the classes compatible with the list will contain
+ * all the requested fixed properties, so the clients will have to filter
+ * the returned list themselves.
+ * If @first_property_name is %NULL, only the classes with no other fixed
+ * properties than ChannelType and TargetHandleType will be returned.
+ * Note that this function may return %NULL without performing any lookup if
+ * @connection is not ready. To ensure that @connection is always ready,
+ * use the empathy_dispatcher_find_requestable_channel_classes_async() variant.
+ *
+ * Return value: a #GList of #GValueArray objects, where the first element in
+ * the array is a #GHashTable of the fixed properties, and the second is
+ * a #GStrv of the allowed properties for the class. The list should be free'd
+ * with g_list_free() when done, but the objects inside the list are owned
+ * by the #EmpathyDispatcher and must not be modified.
+ */
+GList *
+empathy_dispatcher_find_requestable_channel_classes
+ (EmpathyDispatcher *dispatcher,
+ TpConnection *connection,
+ const gchar *channel_type,
+ guint handle_type,
+ const char *first_property_name,
+ ...)
+{
+ va_list var_args;
+ GArray *properties;
+ EmpathyDispatcherPriv *priv;
+ GList *retval;
+ int idx;
+ char *str;
+
+ g_return_val_if_fail (EMPATHY_IS_DISPATCHER (dispatcher), NULL);
+ g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL);
+ g_return_val_if_fail (channel_type != NULL, NULL);
+ g_return_val_if_fail (handle_type != 0, NULL);
+
+ priv = GET_PRIV (dispatcher);
+
+ va_start (var_args, first_property_name);
+
+ properties = setup_varargs (var_args, channel_type, first_property_name);
+
+ va_end (var_args);
+
+ retval = empathy_dispatcher_find_channel_classes (dispatcher, connection,
+ channel_type, handle_type, properties);
+
+ if (properties != NULL)
+ {
+ /* free the properties array */
+ for (idx = 0; idx < properties->len ; idx++)
+ {
+ str = g_array_index (properties, char *, idx);
+ g_free (str);
+ }
+
+ g_array_free (properties, TRUE);
+ }
+
+ return retval;
+}
+
+/**
+ * empathy_dispatcher_find_requestable_channel_classes_async:
+ * @dispatcher: an #EmpathyDispatcher
+ * @connection: a #TpConnection
+ * @channel_type: a string identifying the type of the channel to lookup
+ * @handle_type: the handle type for the channel
+ * @callback: the callback to call when @connection is ready
+ * @user_data: the user data to pass to @callback
+ * @first_property_name: %NULL, or the name of the first fixed property,
+ * followed optionally by more names, followed by %NULL.
+ *
+ * Please see the documentation of
+ * empathy_dispatcher_find_requestable_channel_classes() for a detailed
+ * description of this function.
+ */
void
-empathy_dispatcher_find_channel_class_async (EmpathyDispatcher *dispatcher,
- TpConnection *connection,
- const gchar *channel_type,
- guint handle_type,
- EmpathyDispatcherFindChannelClassCb callback,
- gpointer user_data)
+empathy_dispatcher_find_requestable_channel_classes_async
+ (EmpathyDispatcher *dispatcher,
+ TpConnection *connection,
+ const gchar *channel_type,
+ guint handle_type,
+ EmpathyDispatcherFindChannelClassCb callback,
+ gpointer user_data,
+ const char *first_property_name,
+ ...)
{
+ va_list var_args;
+ GArray *properties;
FindChannelRequest *request;
+ EmpathyDispatcherPriv *priv;
+ guint source_id;
g_return_if_fail (EMPATHY_IS_DISPATCHER (dispatcher));
g_return_if_fail (TP_IS_CONNECTION (connection));
g_return_if_fail (channel_type != NULL);
g_return_if_fail (handle_type != 0);
+ priv = GET_PRIV (dispatcher);
+
+ va_start (var_args, first_property_name);
+
+ properties = setup_varargs (var_args, channel_type, first_property_name);
+
+ va_end (var_args);
+
/* append another request for this connection */
request = g_slice_new0 (FindChannelRequest);
request->dispatcher = dispatcher;
request->connection = connection;
request->callback = callback;
request->user_data = user_data;
+ request->properties = properties;
+
+ source_id = g_idle_add (find_channel_class_idle_cb, request);
- g_idle_add (find_channel_class_idle_cb, request);
+ g_hash_table_insert (priv->request_channel_class_async_ids,
+ request, GUINT_TO_POINTER (source_id));
}