]> git.0d.be Git - empathy.git/blob - libempathy/empathy-dispatcher.c
Merge remote branch 'lfrb/ft-cancel'
[empathy.git] / libempathy / empathy-dispatcher.c
1 /* * Copyright (C) 2007-2009 Collabora Ltd.
2  *
3  * This library is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU Lesser General Public
5  * License as published by the Free Software Foundation; either
6  * version 2.1 of the License, or (at your option) any later version.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with this library; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
16  *
17  * Authors: Xavier Claessens <xclaesse@gmail.com>
18  *          Sjoerd Simons <sjoerd.simons@collabora.co.uk>
19  *          Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
20  */
21
22 #define DISPATCHER_BUS_NAME TP_CLIENT_BUS_NAME_BASE "Empathy"
23 #define DISPATCHER_OBJECT_PATH TP_CLIENT_OBJECT_PATH_BASE "Empathy"
24
25 #include <config.h>
26
27 #include <string.h>
28
29 #include <glib/gi18n-lib.h>
30
31 #include <telepathy-glib/account-manager.h>
32 #include <telepathy-glib/enums.h>
33 #include <telepathy-glib/connection.h>
34 #include <telepathy-glib/channel-dispatcher.h>
35 #include <telepathy-glib/channel-request.h>
36 #include <telepathy-glib/util.h>
37 #include <telepathy-glib/dbus.h>
38 #include <telepathy-glib/proxy-subclass.h>
39 #include <telepathy-glib/gtypes.h>
40 #include <telepathy-glib/defs.h>
41 #include <telepathy-glib/svc-client.h>
42 #include <telepathy-glib/svc-generic.h>
43 #include <telepathy-glib/interfaces.h>
44
45 #include <extensions/extensions.h>
46
47 #include "empathy-dispatcher.h"
48 #include "empathy-utils.h"
49 #include "empathy-tp-contact-factory.h"
50 #include "empathy-chatroom-manager.h"
51 #include "empathy-utils.h"
52
53 #define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER
54 #include <libempathy/empathy-debug.h>
55
56 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyDispatcher)
57 typedef struct
58 {
59   gboolean dispose_has_run;
60
61   TpAccountManager *account_manager;
62   /* connection to connection data mapping */
63   GHashTable *connections;
64   GHashTable *outstanding_classes_requests;
65   gpointer token;
66
67   /* channels which the dispatcher is listening "invalidated" */
68   GList *channels;
69   GPtrArray *array;
70
71   GHashTable *request_channel_class_async_ids;
72   /* reffed (TpAccount *) => gulong
73    * Signal handler ID of the "status-changed" signal */
74   GHashTable *status_changed_handlers;
75
76   TpChannelDispatcher *channel_dispatcher;
77   TpDBusDaemon *dbus;
78 } EmpathyDispatcherPriv;
79
80 G_DEFINE_TYPE (EmpathyDispatcher, empathy_dispatcher, G_TYPE_OBJECT);
81
82 static EmpathyDispatcher *dispatcher = NULL;
83
84 static void dispatcher_init_connection_if_needed (
85     EmpathyDispatcher *dispatcher,
86     TpConnection *connection);
87
88 static GList * empathy_dispatcher_find_channel_classes
89   (EmpathyDispatcher *dispatcher, TpConnection *connection,
90    const gchar *channel_type, guint handle_type, GArray *fixed_properties);
91
92 typedef struct
93 {
94   TpChannel *channel;
95   /* Channel type specific wrapper object */
96   GObject *channel_wrapper;
97 } DispatchData;
98
99 typedef struct
100 {
101   /* ObjectPath -> EmpathyDispatchOperations */
102   GHashTable *dispatching_channels;
103
104   /* List of requestable channel classes */
105   GPtrArray *requestable_channels;
106 } ConnectionData;
107
108 typedef struct
109 {
110   EmpathyDispatcher *dispatcher;
111   TpConnection *connection;
112   char *channel_type;
113   guint handle_type;
114   GArray *properties;
115   EmpathyDispatcherFindChannelClassCb *callback;
116   gpointer user_data;
117 } FindChannelRequest;
118
119 static ConnectionData *
120 new_connection_data (void)
121 {
122   ConnectionData *cd = g_slice_new0 (ConnectionData);
123
124   cd->dispatching_channels = g_hash_table_new_full (g_str_hash, g_str_equal,
125       g_free, g_object_unref);
126
127   return cd;
128 }
129
130 static void
131 free_connection_data (ConnectionData *cd)
132 {
133   guint i;
134
135   g_hash_table_destroy (cd->dispatching_channels);
136
137   if (cd->requestable_channels  != NULL)
138     {
139       for (i = 0 ; i < cd->requestable_channels->len ; i++)
140           g_value_array_free (
141             g_ptr_array_index (cd->requestable_channels, i));
142       g_ptr_array_free (cd->requestable_channels, TRUE);
143     }
144 }
145
146 static void
147 free_find_channel_request (FindChannelRequest *r)
148 {
149   guint idx;
150   char *str;
151
152   g_object_unref (r->dispatcher);
153   g_free (r->channel_type);
154
155   if (r->properties != NULL)
156     {
157       for (idx = 0; idx < r->properties->len ; idx++)
158         {
159           str = g_array_index (r->properties, char *, idx);
160           g_free (str);
161         }
162
163       g_array_free (r->properties, TRUE);
164     }
165
166   g_slice_free (FindChannelRequest, r);
167 }
168
169 static void
170 dispatcher_connection_invalidated_cb (TpConnection *connection,
171                                       guint domain,
172                                       gint code,
173                                       gchar *message,
174                                       EmpathyDispatcher *self)
175 {
176   EmpathyDispatcherPriv *priv = GET_PRIV (self);
177
178   DEBUG ("Error: %s", message);
179
180   g_hash_table_remove (priv->connections, connection);
181 }
182
183 static void
184 dispatcher_channel_invalidated_cb (TpProxy *proxy,
185                                    guint domain,
186                                    gint code,
187                                    gchar *message,
188                                    EmpathyDispatcher *self)
189 {
190   /* Channel went away... */
191   EmpathyDispatcherPriv *priv = GET_PRIV (self);
192   TpConnection *connection;
193   ConnectionData *cd;
194   const gchar *object_path;
195
196   connection = tp_channel_borrow_connection (TP_CHANNEL (proxy));
197
198   priv->channels = g_list_remove (priv->channels, proxy);
199
200   cd = g_hash_table_lookup (priv->connections, connection);
201   /* Connection itself invalidated? */
202   if (cd == NULL)
203     return;
204
205   object_path = tp_proxy_get_object_path (proxy);
206
207   DEBUG ("Channel %s invalidated", object_path);
208
209   g_hash_table_remove (cd->dispatching_channels, object_path);
210 }
211
212 static void
213 got_connection_rcc (EmpathyDispatcher *self,
214     TpConnection *connection)
215 {
216   EmpathyDispatcherPriv *priv = GET_PRIV (self);
217   TpCapabilities *caps;
218   ConnectionData *cd;
219   GList *requests, *l;
220   FindChannelRequest *request;
221   GList *retval;
222
223   caps = tp_connection_get_capabilities (connection);
224   g_assert (caps != NULL);
225
226   cd = g_hash_table_lookup (priv->connections, connection);
227   g_assert (cd != NULL);
228
229   cd->requestable_channels = g_boxed_copy (
230     TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST,
231     tp_capabilities_get_channel_classes (caps));
232
233   requests = g_hash_table_lookup (priv->outstanding_classes_requests,
234       connection);
235
236   for (l = requests; l != NULL; l = l->next)
237     {
238       request = l->data;
239
240       retval = empathy_dispatcher_find_channel_classes (self,
241           connection, request->channel_type,
242           request->handle_type, request->properties);
243       request->callback (retval, request->user_data);
244
245       free_find_channel_request (request);
246       g_list_free (retval);
247     }
248
249   g_list_free (requests);
250
251   g_hash_table_remove (priv->outstanding_classes_requests, connection);
252 }
253
254 static void
255 connection_prepare_cb (GObject *source,
256     GAsyncResult *result,
257     gpointer user_data)
258 {
259   EmpathyDispatcher *self = EMPATHY_DISPATCHER (user_data);
260   GError *error = NULL;
261   TpConnection *connection = (TpConnection *) source;
262
263   if (!tp_proxy_prepare_finish (source, result, &error))
264     {
265       DEBUG ("Error: %s", error->message);
266
267       g_error_free (error);
268       goto out;
269     }
270
271   got_connection_rcc (self, connection);
272
273 out:
274   g_object_unref (self);
275 }
276
277 static void
278 dispatcher_init_connection_if_needed (EmpathyDispatcher *self,
279     TpConnection *connection)
280 {
281   EmpathyDispatcherPriv *priv = GET_PRIV (self);
282   GQuark features[] = { TP_CONNECTION_FEATURE_CORE,
283          TP_CONNECTION_FEATURE_CAPABILITIES,
284          0 };
285
286   if (g_hash_table_lookup (priv->connections, connection) != NULL)
287     return;
288
289   g_hash_table_insert (priv->connections, g_object_ref (connection),
290     new_connection_data ());
291
292   g_signal_connect (connection, "invalidated",
293     G_CALLBACK (dispatcher_connection_invalidated_cb), self);
294
295   /* Ensure to keep the self object alive while preparing the connection */
296   g_object_ref (self);
297
298   tp_proxy_prepare_async (connection, features, connection_prepare_cb, self);
299 }
300
301 static void
302 dispatcher_status_changed_cb (TpAccount *account,
303                               guint old_status,
304                               guint new_status,
305                               guint reason,
306                               gchar *dbus_error_name,
307                               GHashTable *details,
308                               EmpathyDispatcher *self)
309 {
310   TpConnection *conn = tp_account_get_connection (account);
311
312   if (conn != NULL)
313     dispatcher_init_connection_if_needed (self, conn);
314 }
315
316 static void
317 remove_idle_handlers (gpointer key,
318                       gpointer value,
319                       gpointer user_data)
320 {
321   guint source_id;
322
323   source_id = GPOINTER_TO_UINT (value);
324   g_source_remove (source_id);
325 }
326
327 static GObject *
328 dispatcher_constructor (GType type,
329                         guint n_construct_params,
330                         GObjectConstructParam *construct_params)
331 {
332   GObject *retval;
333   EmpathyDispatcherPriv *priv;
334
335   if (dispatcher != NULL)
336     return g_object_ref (dispatcher);
337
338   retval = G_OBJECT_CLASS (empathy_dispatcher_parent_class)->constructor
339     (type, n_construct_params, construct_params);
340
341   dispatcher = EMPATHY_DISPATCHER (retval);
342   g_object_add_weak_pointer (retval, (gpointer) &dispatcher);
343
344   priv = GET_PRIV (dispatcher);
345
346   return retval;
347 }
348
349 static void
350 dispatcher_dispose (GObject *object)
351 {
352   EmpathyDispatcherPriv *priv = GET_PRIV (object);
353   GHashTableIter iter;
354   gpointer connection;
355
356   if (priv->dispose_has_run)
357     return;
358
359   priv->dispose_has_run = TRUE;
360
361   g_hash_table_iter_init (&iter, priv->connections);
362   while (g_hash_table_iter_next (&iter, &connection, NULL))
363     {
364       g_signal_handlers_disconnect_by_func (connection,
365           dispatcher_connection_invalidated_cb, object);
366     }
367
368   g_hash_table_destroy (priv->connections);
369   priv->connections = NULL;
370
371   if (priv->channel_dispatcher != NULL)
372     g_object_unref (priv->channel_dispatcher);
373   priv->channel_dispatcher = NULL;
374
375   if (priv->dbus != NULL)
376     g_object_unref (priv->dbus);
377   priv->dbus = NULL;
378
379   G_OBJECT_CLASS (empathy_dispatcher_parent_class)->dispose (object);
380 }
381
382 static void
383 dispatcher_finalize (GObject *object)
384 {
385   EmpathyDispatcherPriv *priv = GET_PRIV (object);
386   GList *l;
387   GHashTableIter iter;
388   gpointer connection;
389   GList *list;
390   gpointer account, id;
391
392   if (priv->request_channel_class_async_ids != NULL)
393     {
394       g_hash_table_foreach (priv->request_channel_class_async_ids,
395         remove_idle_handlers, NULL);
396       g_hash_table_destroy (priv->request_channel_class_async_ids);
397     }
398
399   for (l = priv->channels; l; l = l->next)
400     {
401       g_signal_handlers_disconnect_by_func (l->data,
402           dispatcher_channel_invalidated_cb, object);
403     }
404
405   g_list_free (priv->channels);
406
407   g_hash_table_iter_init (&iter, priv->outstanding_classes_requests);
408   while (g_hash_table_iter_next (&iter, &connection, (gpointer *) &list))
409     {
410       g_list_foreach (list, (GFunc) free_find_channel_request, NULL);
411       g_list_free (list);
412     }
413
414   g_hash_table_iter_init (&iter, priv->status_changed_handlers);
415   while (g_hash_table_iter_next (&iter, &account, &id))
416     {
417       g_signal_handler_disconnect (account, GPOINTER_TO_UINT (id));
418     }
419   g_hash_table_destroy (priv->status_changed_handlers);
420
421   g_object_unref (priv->account_manager);
422
423   g_hash_table_destroy (priv->outstanding_classes_requests);
424 }
425
426 static void
427 empathy_dispatcher_class_init (EmpathyDispatcherClass *klass)
428 {
429   GObjectClass *object_class = G_OBJECT_CLASS (klass);
430
431   object_class->dispose = dispatcher_dispose;
432   object_class->finalize = dispatcher_finalize;
433   object_class->constructor = dispatcher_constructor;
434
435   g_type_class_add_private (object_class, sizeof (EmpathyDispatcherPriv));
436 }
437
438 static void
439 connect_account (EmpathyDispatcher *self,
440     TpAccount *account)
441 {
442   EmpathyDispatcherPriv *priv = GET_PRIV (self);
443   TpConnection *conn = tp_account_get_connection (account);
444   gulong id;
445
446   id = GPOINTER_TO_UINT (g_hash_table_lookup (priv->status_changed_handlers,
447         account));
448
449   if (id != 0)
450     return;
451
452   if (conn != NULL)
453     dispatcher_status_changed_cb (account, 0, 0, 0, NULL, NULL, self);
454
455   id = g_signal_connect (account, "status-changed",
456       G_CALLBACK (dispatcher_status_changed_cb), self);
457
458   g_hash_table_insert (priv->status_changed_handlers, g_object_ref (account),
459       GUINT_TO_POINTER (id));
460 }
461
462 static void
463 account_manager_prepared_cb (GObject *source_object,
464                              GAsyncResult *result,
465                              gpointer user_data)
466 {
467   GList *accounts, *l;
468   EmpathyDispatcher *self = user_data;
469   TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
470   GError *error = NULL;
471
472   if (!tp_account_manager_prepare_finish (account_manager, result, &error))
473     {
474       DEBUG ("Failed to prepare account manager: %s", error->message);
475       g_error_free (error);
476       return;
477     }
478
479   accounts = tp_account_manager_get_valid_accounts (account_manager);
480   for (l = accounts; l; l = l->next)
481     {
482       TpAccount *a = l->data;
483
484       connect_account (self, a);
485     }
486
487   g_list_foreach (accounts, (GFunc) g_object_ref, NULL);
488   g_list_free (accounts);
489 }
490
491 static void
492 account_prepare_cb (GObject *source_object,
493     GAsyncResult *result,
494     gpointer user_data)
495 {
496   EmpathyDispatcher *self = user_data;
497   TpAccount *account = TP_ACCOUNT (source_object);
498   GError *error = NULL;
499
500   if (!tp_account_prepare_finish (account, result, &error))
501     {
502       DEBUG ("Failed to prepare account: %s", error->message);
503       g_error_free (error);
504       return;
505     }
506
507   connect_account (self, account);
508 }
509
510 static void
511 account_validity_changed_cb (TpAccountManager *manager,
512     TpAccount *account,
513     gboolean valid,
514     gpointer user_data)
515 {
516   if (!valid)
517     return;
518
519   tp_account_prepare_async (account, NULL, account_prepare_cb, user_data);
520 }
521
522 static void
523 empathy_dispatcher_init (EmpathyDispatcher *self)
524 {
525   EmpathyDispatcherPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
526     EMPATHY_TYPE_DISPATCHER, EmpathyDispatcherPriv);
527
528   self->priv = priv;
529   priv->account_manager = tp_account_manager_dup ();
530
531   priv->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal,
532     g_object_unref, (GDestroyNotify) free_connection_data);
533
534   priv->outstanding_classes_requests = g_hash_table_new_full (g_direct_hash,
535     g_direct_equal, g_object_unref, NULL);
536
537   priv->channels = NULL;
538
539   tp_account_manager_prepare_async (priv->account_manager, NULL,
540       account_manager_prepared_cb, self);
541
542   tp_g_signal_connect_object (priv->account_manager,
543       "account-validity-changed", G_CALLBACK (account_validity_changed_cb),
544       self, 0);
545
546   priv->request_channel_class_async_ids = g_hash_table_new (g_direct_hash,
547     g_direct_equal);
548   priv->status_changed_handlers = g_hash_table_new_full (NULL, NULL,
549       (GDestroyNotify) g_object_unref, NULL);
550
551   priv->dbus = tp_dbus_daemon_dup (NULL);
552   priv->channel_dispatcher = tp_channel_dispatcher_new (priv->dbus);
553 }
554
555 EmpathyDispatcher *
556 empathy_dispatcher_dup_singleton (void)
557 {
558   return EMPATHY_DISPATCHER (g_object_new (EMPATHY_TYPE_DISPATCHER, NULL));
559 }
560
561 void
562 empathy_dispatcher_chat_with_contact (EmpathyContact *contact,
563     gint64 timestamp)
564 {
565   empathy_dispatcher_chat_with_contact_id (
566       empathy_contact_get_account (contact), empathy_contact_get_id (contact),
567       timestamp);
568 }
569
570 static void
571 ensure_text_channel_cb (GObject *source,
572     GAsyncResult *result,
573     gpointer user_data)
574 {
575   GError *error = NULL;
576
577   if (!tp_account_channel_request_ensure_channel_finish (
578         TP_ACCOUNT_CHANNEL_REQUEST (source), result, &error))
579     {
580       DEBUG ("Failed to ensure text channel: %s", error->message);
581       g_error_free (error);
582     }
583 }
584
585 void
586 empathy_dispatcher_chat_with_contact_id (TpAccount *account,
587     const gchar *contact_id,
588     gint64 timestamp)
589 {
590   GHashTable *request;
591   TpAccountChannelRequest *req;
592
593   request = tp_asv_new (
594       TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
595         TP_IFACE_CHANNEL_TYPE_TEXT,
596       TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
597       TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, contact_id,
598       NULL);
599
600   req = tp_account_channel_request_new (account, request, timestamp);
601
602   tp_account_channel_request_ensure_channel_async (req, NULL, NULL,
603       ensure_text_channel_cb, NULL);
604
605   g_hash_table_unref (request);
606   g_object_unref (req);
607 }
608
609 void
610 empathy_dispatcher_join_muc (TpAccount *account,
611     const gchar *room_name,
612     gint64 timestamp)
613 {
614   GHashTable *request;
615   TpAccountChannelRequest *req;
616
617   request = tp_asv_new (
618       TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
619         TP_IFACE_CHANNEL_TYPE_TEXT,
620       TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_ROOM,
621       TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, room_name,
622       NULL);
623
624   req = tp_account_channel_request_new (account, request, timestamp);
625
626   tp_account_channel_request_ensure_channel_async (req, NULL, NULL,
627       ensure_text_channel_cb, NULL);
628
629   g_hash_table_unref (request);
630   g_object_unref (req);
631 }
632
633 static gboolean
634 channel_class_matches (GValueArray *class,
635                        const char *channel_type,
636                        guint handle_type,
637                        GArray *fixed_properties)
638 {
639   GHashTable *fprops;
640   GValue *v;
641   const char *c_type;
642   guint h_type;
643   gboolean valid;
644
645   v = g_value_array_get_nth (class, 0);
646
647   /* if the class doesn't match channel type discard it. */
648   fprops = g_value_get_boxed (v);
649   c_type = tp_asv_get_string (fprops, TP_IFACE_CHANNEL ".ChannelType");
650
651   if (tp_strdiff (channel_type, c_type))
652     return FALSE;
653
654   /* we have the right channel type, see if the handle type matches */
655   h_type = tp_asv_get_uint32 (fprops,
656                               TP_IFACE_CHANNEL ".TargetHandleType", &valid);
657
658   if (!valid || (handle_type != h_type && handle_type != TP_UNKNOWN_HANDLE_TYPE))
659     return FALSE;
660
661   if (fixed_properties != NULL)
662     {
663       gpointer h_key, h_val;
664       guint idx;
665       GHashTableIter iter;
666       gboolean found;
667
668       g_hash_table_iter_init (&iter, fprops);
669
670       while (g_hash_table_iter_next (&iter, &h_key, &h_val))
671         {
672           /* discard ChannelType and TargetHandleType, as we already
673            * checked them.
674            */
675           if (!tp_strdiff ((char *) h_key, TP_IFACE_CHANNEL ".ChannelType") ||
676               !tp_strdiff
677                 ((char *) h_key, TP_IFACE_CHANNEL ".TargetHandleType"))
678             continue;
679
680           found = FALSE;
681
682           for (idx = 0; idx < fixed_properties->len; idx++)
683             {
684               /* if |key| doesn't exist in |fixed_properties|, discard
685                * the class.
686                */
687               if (!tp_strdiff
688                     ((char *) h_key,
689                      g_array_index (fixed_properties, char *, idx)))
690                 {
691                   found = TRUE;
692                   /* exit the for() loop */
693                   break;
694                 }
695             }
696
697           if (!found)
698             return FALSE;
699         }
700     }
701   else
702     {
703       /* if no fixed_properties are specified, discard the classes
704        * with some fixed properties other than the two we already
705        * checked.
706        */
707       if (g_hash_table_size (fprops) > 2)
708         return FALSE;
709     }
710
711   return TRUE;
712 }
713
714 static GList *
715 empathy_dispatcher_find_channel_classes (EmpathyDispatcher *self,
716                                          TpConnection *connection,
717                                          const gchar *channel_type,
718                                          guint handle_type,
719                                          GArray *fixed_properties)
720 {
721   EmpathyDispatcherPriv *priv = GET_PRIV (self);
722   GValueArray *class;
723   GPtrArray *classes;
724   GList *matching_classes;
725   guint i;
726   ConnectionData *cd;
727
728   g_return_val_if_fail (channel_type != NULL, NULL);
729
730   cd = g_hash_table_lookup (priv->connections, connection);
731
732   if (cd == NULL)
733     return NULL;
734
735   classes = cd->requestable_channels;
736   if (classes == NULL)
737     return NULL;
738
739   matching_classes = NULL;
740
741   for (i = 0; i < classes->len; i++)
742     {
743       class = g_ptr_array_index (classes, i);
744
745       if (!channel_class_matches
746           (class, channel_type, handle_type, fixed_properties))
747         continue;
748
749       matching_classes = g_list_prepend (matching_classes, class);
750     }
751
752   return matching_classes;
753 }
754
755 static gboolean
756 find_channel_class_idle_cb (gpointer user_data)
757 {
758   GList *retval;
759   GList *requests;
760   FindChannelRequest *request = user_data;
761   ConnectionData *cd;
762   gboolean is_ready = TRUE;
763   EmpathyDispatcherPriv *priv = GET_PRIV (request->dispatcher);
764
765   g_hash_table_remove (priv->request_channel_class_async_ids, request);
766
767   cd = g_hash_table_lookup (priv->connections, request->connection);
768
769   if (cd == NULL)
770     is_ready = FALSE;
771   else if (cd->requestable_channels == NULL)
772     is_ready = FALSE;
773
774   if (is_ready)
775     {
776       retval = empathy_dispatcher_find_channel_classes (request->dispatcher,
777           request->connection, request->channel_type, request->handle_type,
778           request->properties);
779
780       request->callback (retval, request->user_data);
781       free_find_channel_request (request);
782       g_list_free (retval);
783
784       return FALSE;
785     }
786
787   requests = g_hash_table_lookup (priv->outstanding_classes_requests,
788       request->connection);
789   requests = g_list_prepend (requests, request);
790
791   g_hash_table_insert (priv->outstanding_classes_requests,
792       request->connection, requests);
793
794   return FALSE;
795 }
796
797 static GArray *
798 setup_varargs (va_list var_args,
799                const char *channel_namespace,
800                const char *first_property_name)
801 {
802   const char *name;
803   char *name_full;
804   GArray *properties;
805
806   if (first_property_name == NULL)
807     return NULL;
808
809   name = first_property_name;
810   properties = g_array_new (TRUE, TRUE, sizeof (char *));
811
812   while (name != NULL)
813     {
814       name_full = g_strdup (name);
815       properties = g_array_append_val (properties, name_full);
816       name = va_arg (var_args, char *);
817     }
818
819   return properties;
820 }
821
822 /**
823  * empathy_dispatcher_find_requestable_channel_classes:
824  * @dispatcher: an #EmpathyDispatcher
825  * @connection: a #TpConnection
826  * @channel_type: a string identifying the type of the channel to lookup
827  * @handle_type: the handle type for the channel, or %TP_UNKNOWN_HANDLE_TYPE
828  *               if you don't care about the channel's target handle type
829  * @first_property_name: %NULL, or the name of the first fixed property,
830  * followed optionally by more names, followed by %NULL.
831  *
832  * Returns all the channel classes that a client can request for the connection
833  * @connection, of the type identified by @channel_type, @handle_type and the
834  * fixed properties list.
835  * The classes which are compatible with a fixed properties list (i.e. those
836  * that will be returned by this function) are intended as those that do not
837  * contain any fixed property other than those in the list; note that this
838  * doesn't guarantee that all the classes compatible with the list will contain
839  * all the requested fixed properties, so the clients will have to filter
840  * the returned list themselves.
841  * If @first_property_name is %NULL, only the classes with no other fixed
842  * properties than ChannelType and TargetHandleType will be returned.
843  * Note that this function may return %NULL without performing any lookup if
844  * @connection is not ready. To ensure that @connection is always ready,
845  * use the empathy_dispatcher_find_requestable_channel_classes_async() variant.
846  *
847  * Return value: a #GList of #GValueArray objects, where the first element in
848  * the array is a #GHashTable of the fixed properties, and the second is
849  * a #GStrv of the allowed properties for the class. The list should be free'd
850  * with g_list_free() when done, but the objects inside the list are owned
851  * by the #EmpathyDispatcher and must not be modified.
852  */
853 GList *
854 empathy_dispatcher_find_requestable_channel_classes
855                                  (EmpathyDispatcher *self,
856                                   TpConnection *connection,
857                                   const gchar *channel_type,
858                                   guint handle_type,
859                                   const char *first_property_name,
860                                   ...)
861 {
862   va_list var_args;
863   GArray *properties;
864   EmpathyDispatcherPriv *priv;
865   GList *retval;
866   guint idx;
867   char *str;
868
869   g_return_val_if_fail (EMPATHY_IS_DISPATCHER (self), NULL);
870   g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL);
871   g_return_val_if_fail (channel_type != NULL, NULL);
872
873   priv = GET_PRIV (self);
874
875   va_start (var_args, first_property_name);
876
877   properties = setup_varargs (var_args, channel_type, first_property_name);
878
879   va_end (var_args);
880
881   retval = empathy_dispatcher_find_channel_classes (self, connection,
882     channel_type, handle_type, properties);
883
884   if (properties != NULL)
885     {
886       /* free the properties array */
887       for (idx = 0; idx < properties->len ; idx++)
888         {
889           str = g_array_index (properties, char *, idx);
890           g_free (str);
891         }
892
893       g_array_free (properties, TRUE);
894     }
895
896   return retval;
897 }
898
899 /**
900  * empathy_dispatcher_find_requestable_channel_classes_async:
901  * @dispatcher: an #EmpathyDispatcher
902  * @connection: a #TpConnection
903  * @channel_type: a string identifying the type of the channel to lookup
904  * @handle_type: the handle type for the channel
905  * @callback: the callback to call when @connection is ready
906  * @user_data: the user data to pass to @callback
907  * @first_property_name: %NULL, or the name of the first fixed property,
908  * followed optionally by more names, followed by %NULL.
909  *
910  * Please see the documentation of
911  * empathy_dispatcher_find_requestable_channel_classes() for a detailed
912  * description of this function.
913  */
914 void
915 empathy_dispatcher_find_requestable_channel_classes_async
916                                  (EmpathyDispatcher *self,
917                                   TpConnection *connection,
918                                   const gchar *channel_type,
919                                   guint handle_type,
920                                   EmpathyDispatcherFindChannelClassCb callback,
921                                   gpointer user_data,
922                                   const char *first_property_name,
923                                   ...)
924 {
925   va_list var_args;
926   GArray *properties;
927   FindChannelRequest *request;
928   EmpathyDispatcherPriv *priv;
929   guint source_id;
930
931   g_return_if_fail (EMPATHY_IS_DISPATCHER (self));
932   g_return_if_fail (TP_IS_CONNECTION (connection));
933   g_return_if_fail (channel_type != NULL);
934   g_return_if_fail (handle_type != 0);
935
936   priv = GET_PRIV (self);
937
938   va_start (var_args, first_property_name);
939
940   properties = setup_varargs (var_args, channel_type, first_property_name);
941
942   va_end (var_args);
943
944   /* append another request for this connection */
945   request = g_slice_new0 (FindChannelRequest);
946   request->dispatcher = g_object_ref (self);
947   request->channel_type = g_strdup (channel_type);
948   request->handle_type = handle_type;
949   request->connection = connection;
950   request->callback = callback;
951   request->user_data = user_data;
952   request->properties = properties;
953
954   source_id = g_idle_add (find_channel_class_idle_cb, request);
955
956   g_hash_table_insert (priv->request_channel_class_async_ids,
957     request, GUINT_TO_POINTER (source_id));
958 }