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