]> git.0d.be Git - empathy.git/blob - libempathy/empathy-dispatcher.c
9b8c997bd29a22c61c0de1dab8c1c337ce71569e
[empathy.git] / libempathy / empathy-dispatcher.c
1 /* * Copyright (C) 2007-2008 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  */
19
20 #include <config.h>
21
22 #include <string.h>
23
24 #include <glib/gi18n-lib.h>
25
26 #include <telepathy-glib/enums.h>
27 #include <telepathy-glib/connection.h>
28 #include <telepathy-glib/util.h>
29 #include <telepathy-glib/dbus.h>
30 #include <telepathy-glib/proxy-subclass.h>
31 #include <telepathy-glib/gtypes.h>
32
33 #include <libmissioncontrol/mission-control.h>
34
35 #include <extensions/extensions.h>
36
37 #include "empathy-dispatcher.h"
38 #include "empathy-utils.h"
39 #include "empathy-tube-handler.h"
40 #include "empathy-account-manager.h"
41 #include "empathy-tp-contact-factory.h"
42 #include "empathy-chatroom-manager.h"
43 #include "empathy-utils.h"
44
45 #define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER
46 #include <libempathy/empathy-debug.h>
47
48 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyDispatcher)
49 typedef struct
50 {
51   EmpathyAccountManager *account_manager;
52   MissionControl *mc;
53   /* connection to connection data mapping */
54   GHashTable *connections;
55   GHashTable *outstanding_classes_requests;
56   gpointer token;
57   GSList *tubes;
58
59   /* channels which the dispatcher is listening "invalidated" */
60   GList *channels;
61
62   GHashTable *request_channel_class_async_ids;
63 } EmpathyDispatcherPriv;
64
65 G_DEFINE_TYPE (EmpathyDispatcher, empathy_dispatcher, G_TYPE_OBJECT);
66
67 enum
68 {
69   OBSERVE,
70   APPROVE,
71   DISPATCH,
72   LAST_SIGNAL
73 };
74
75 static guint signals[LAST_SIGNAL];
76 static EmpathyDispatcher *dispatcher = NULL;
77
78 static GList * empathy_dispatcher_find_channel_classes
79   (EmpathyDispatcher *dispatcher, TpConnection *connection,
80    const gchar *channel_type, guint handle_type, GArray *fixed_properties);
81
82
83 typedef struct
84 {
85   EmpathyDispatcher *dispatcher;
86   EmpathyDispatchOperation *operation;
87   TpConnection *connection;
88   gchar *channel_type;
89   guint handle_type;
90   guint handle;
91   EmpathyContact *contact;
92
93   /* Properties to pass to the channel when requesting it */
94   GHashTable *request;
95   EmpathyDispatcherRequestCb *cb;
96   gpointer user_data;
97   gpointer *request_data;
98 } DispatcherRequestData;
99
100 typedef struct
101 {
102   TpChannel *channel;
103   /* Channel type specific wrapper object */
104   GObject *channel_wrapper;
105 } DispatchData;
106
107 typedef struct
108 {
109   /* ObjectPath => DispatchData.. */
110   GHashTable *dispatched_channels;
111   /* ObjectPath -> EmpathyDispatchOperations */
112   GHashTable *dispatching_channels;
113   /* ObjectPath -> EmpathyDispatchOperations */
114   GHashTable *outstanding_channels;
115   /* List of DispatcherRequestData */
116   GList *outstanding_requests;
117   /* List of requestable channel classes */
118   GPtrArray *requestable_channels;
119 } ConnectionData;
120
121 typedef struct
122 {
123   EmpathyDispatcher *dispatcher;
124   TpConnection *connection;
125   char *channel_type;
126   guint handle_type;
127   GArray *properties;
128   EmpathyDispatcherFindChannelClassCb *callback;
129   gpointer user_data;
130 } FindChannelRequest;
131
132 static DispatchData *
133 new_dispatch_data (TpChannel *channel,
134                    GObject *channel_wrapper)
135 {
136   DispatchData *d = g_slice_new0 (DispatchData);
137   d->channel = g_object_ref (channel);
138   if (channel_wrapper != NULL)
139     d->channel_wrapper = g_object_ref (channel_wrapper);
140
141   return d;
142 }
143
144 static void
145 free_dispatch_data (DispatchData *data)
146 {
147   g_object_unref (data->channel);
148   if (data->channel_wrapper != NULL)
149     g_object_unref (data->channel_wrapper);
150
151   g_slice_free (DispatchData, data);
152 }
153
154 static DispatcherRequestData *
155 new_dispatcher_request_data (EmpathyDispatcher *dispatcher,
156                              TpConnection *connection,
157                              const gchar *channel_type,
158                              guint handle_type,
159                              guint handle,
160                              GHashTable *request,
161                              EmpathyContact *contact,
162                              EmpathyDispatcherRequestCb *cb,
163                              gpointer user_data)
164 {
165   DispatcherRequestData *result = g_slice_new0 (DispatcherRequestData);
166
167   result->dispatcher = g_object_ref (dispatcher);
168   result->connection = connection;
169
170   result->channel_type = g_strdup (channel_type);
171   result->handle_type = handle_type;
172   result->handle = handle;
173   result->request = request;
174
175   if (contact != NULL)
176     result->contact = g_object_ref (contact);
177
178   result->cb = cb;
179   result->user_data = user_data;
180
181   return result;
182 }
183
184 static void
185 free_dispatcher_request_data (DispatcherRequestData *r)
186 {
187   g_free (r->channel_type);
188
189   if (r->dispatcher != NULL)
190     g_object_unref (r->dispatcher);
191
192   if (r->contact != NULL)
193     g_object_unref (r->contact);
194
195   if (r->request != NULL)
196     g_hash_table_unref (r->request);
197
198   g_slice_free (DispatcherRequestData, r);
199 }
200
201 static ConnectionData *
202 new_connection_data (void)
203 {
204   ConnectionData *cd = g_slice_new0 (ConnectionData);
205
206   cd->dispatched_channels = g_hash_table_new_full (g_str_hash, g_str_equal,
207       g_free, (GDestroyNotify) free_dispatch_data);
208
209   cd->dispatching_channels = g_hash_table_new_full (g_str_hash, g_str_equal,
210       g_free, g_object_unref);
211
212   cd->outstanding_channels = g_hash_table_new_full (g_str_hash, g_str_equal,
213       g_free, NULL);
214
215   return cd;
216 }
217
218 static void
219 free_connection_data (ConnectionData *cd)
220 {
221   GList *l;
222
223   g_hash_table_destroy (cd->dispatched_channels);
224   g_hash_table_destroy (cd->dispatching_channels);
225   int i;
226
227   for (l = cd->outstanding_requests ; l != NULL; l = g_list_delete_link (l,l))
228     {
229       free_dispatcher_request_data (l->data);
230     }
231
232   if (cd->requestable_channels  != NULL)
233     {
234       for (i = 0 ; i < cd->requestable_channels->len ; i++)
235           g_value_array_free (
236             g_ptr_array_index (cd->requestable_channels, i));
237       g_ptr_array_free (cd->requestable_channels, TRUE);
238     }
239 }
240
241 static void
242 free_find_channel_request (FindChannelRequest *r)
243 {
244   int idx;
245   char *str;
246
247   g_object_unref (r->dispatcher);
248   g_free (r->channel_type);
249
250   if (r->properties != NULL)
251     {
252       for (idx = 0; idx < r->properties->len ; idx++)
253         {
254           str = g_array_index (r->properties, char *, idx);
255           g_free (str);
256         }
257
258       g_array_free (r->properties, TRUE);
259     }
260
261   g_slice_free (FindChannelRequest, r);
262 }
263
264 static void
265 dispatcher_connection_invalidated_cb (TpConnection *connection,
266                                       guint domain,
267                                       gint code,
268                                       gchar *message,
269                                       EmpathyDispatcher *dispatcher)
270 {
271   EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
272
273   DEBUG ("Error: %s", message);
274   g_hash_table_remove (priv->connections, connection);
275 }
276
277 static gboolean
278 dispatcher_operation_can_start (EmpathyDispatcher *self,
279                                 EmpathyDispatchOperation *operation,
280                                 ConnectionData *cd)
281 {
282   GList *l;
283   const gchar *channel_type =
284     empathy_dispatch_operation_get_channel_type (operation);
285
286   for (l = cd->outstanding_requests; l != NULL; l = g_list_next (l))
287     {
288       DispatcherRequestData *d = (DispatcherRequestData *) l->data;
289
290       if (d->operation == NULL && !tp_strdiff (d->channel_type, channel_type))
291         {
292           return FALSE;
293         }
294     }
295
296   return TRUE;
297 }
298
299 static void
300 dispatch_operation_flush_requests (EmpathyDispatcher *dispatcher,
301                                    EmpathyDispatchOperation *operation,
302                                    GError *error,
303                                    ConnectionData *cd)
304 {
305   GList *l;
306
307   l = cd->outstanding_requests;
308   while (l != NULL)
309     {
310       DispatcherRequestData *d = (DispatcherRequestData *) l->data;
311       GList *lt = l;
312
313       l = g_list_next (l);
314
315       if (d->operation == operation)
316         {
317           if (d->cb != NULL)
318             {
319               if (error != NULL)
320                 d->cb (NULL, error, d->user_data);
321               else
322                 d->cb (operation, NULL, d->user_data);
323             }
324
325           cd->outstanding_requests = g_list_delete_link
326             (cd->outstanding_requests, lt);
327
328           free_dispatcher_request_data (d);
329         }
330     }
331 }
332
333 static void
334 dispatcher_channel_invalidated_cb (TpProxy *proxy,
335                                    guint domain,
336                                    gint code,
337                                    gchar *message,
338                                    EmpathyDispatcher *dispatcher)
339 {
340   /* Channel went away... */
341   EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
342   TpConnection *connection;
343   EmpathyDispatchOperation *operation;
344   ConnectionData *cd;
345   const gchar *object_path;
346
347   connection = tp_channel_borrow_connection (TP_CHANNEL (proxy));
348
349   cd = g_hash_table_lookup (priv->connections, connection);
350   /* Connection itself invalidated? */
351   if (cd == NULL)
352     return;
353
354   object_path = tp_proxy_get_object_path (proxy);
355
356   DEBUG ("Channel %s invalidated", object_path);
357
358   g_hash_table_remove (cd->dispatched_channels, object_path);
359   g_hash_table_remove (cd->dispatching_channels, object_path);
360
361   priv->channels = g_list_remove (priv->channels, proxy);
362
363   operation = g_hash_table_lookup (cd->outstanding_channels, object_path);
364   if (operation != NULL)
365     {
366       GError error = { domain, code, message };
367       dispatch_operation_flush_requests (dispatcher, operation, &error, cd);
368       g_hash_table_remove (cd->outstanding_channels, object_path);
369       g_object_unref (operation);
370     }
371 }
372
373 static void
374 dispatch_operation_approved_cb (EmpathyDispatchOperation *operation,
375                                 EmpathyDispatcher *dispatcher)
376 {
377   g_assert (empathy_dispatch_operation_is_incoming (operation));
378   DEBUG ("Send of for dispatching: %s",
379     empathy_dispatch_operation_get_object_path (operation));
380   g_signal_emit (dispatcher, signals[DISPATCH], 0, operation);
381 }
382
383 static void
384 dispatch_operation_claimed_cb (EmpathyDispatchOperation *operation,
385                                EmpathyDispatcher *dispatcher)
386 {
387   /* Our job is done, remove the dispatch operation and mark the channel as
388    * dispatched */
389   EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
390   TpConnection *connection;
391   ConnectionData *cd;
392   const gchar *object_path;
393
394   connection = empathy_dispatch_operation_get_tp_connection (operation);
395   cd = g_hash_table_lookup (priv->connections, connection);
396   g_assert (cd != NULL);
397
398   object_path = empathy_dispatch_operation_get_object_path (operation);
399
400   if (g_hash_table_lookup (cd->dispatched_channels, object_path) == NULL)
401     {
402       DispatchData *d;
403       d = new_dispatch_data (
404         empathy_dispatch_operation_get_channel (operation),
405         empathy_dispatch_operation_get_channel_wrapper (operation));
406       g_hash_table_insert (cd->dispatched_channels,
407         g_strdup (object_path), d);
408     }
409   g_hash_table_remove (cd->dispatching_channels, object_path);
410
411   DEBUG ("Channel claimed: %s", object_path);
412 }
413
414 static void
415 dispatch_operation_ready_cb (EmpathyDispatchOperation *operation,
416                              EmpathyDispatcher *dispatcher)
417 {
418   EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
419   TpConnection *connection;
420   ConnectionData *cd;
421   EmpathyDispatchOperationState status;
422
423   g_signal_connect (operation, "approved",
424     G_CALLBACK (dispatch_operation_approved_cb), dispatcher);
425
426   g_signal_connect (operation, "claimed",
427     G_CALLBACK (dispatch_operation_claimed_cb), dispatcher);
428
429   /* Signal the observers */
430   DEBUG ("Send to observers: %s",
431     empathy_dispatch_operation_get_object_path (operation));
432   g_signal_emit (dispatcher, signals[OBSERVE], 0, operation);
433
434   empathy_dispatch_operation_start (operation);
435
436   /* Signal potential requestors */
437   connection =  empathy_dispatch_operation_get_tp_connection (operation);
438   cd = g_hash_table_lookup (priv->connections, connection);
439   g_assert (cd != NULL);
440
441   g_object_ref (operation);
442   g_object_ref (dispatcher);
443
444   dispatch_operation_flush_requests (dispatcher, operation, NULL, cd);
445   status = empathy_dispatch_operation_get_status (operation);
446   g_object_unref (operation);
447
448   if (status == EMPATHY_DISPATCHER_OPERATION_STATE_CLAIMED)
449     return;
450
451   if (status == EMPATHY_DISPATCHER_OPERATION_STATE_APPROVING)
452     {
453       DEBUG ("Send to approvers: %s",
454         empathy_dispatch_operation_get_object_path (operation));
455       g_signal_emit (dispatcher, signals[APPROVE], 0, operation);
456     }
457   else
458     {
459       g_assert (status == EMPATHY_DISPATCHER_OPERATION_STATE_DISPATCHING);
460       DEBUG ("Send of for dispatching: %s",
461         empathy_dispatch_operation_get_object_path (operation));
462       g_signal_emit (dispatcher, signals[DISPATCH], 0, operation);
463     }
464
465   g_object_unref (dispatcher);
466 }
467
468 static void
469 dispatcher_start_dispatching (EmpathyDispatcher *self,
470                               EmpathyDispatchOperation *operation,
471                               ConnectionData *cd)
472 {
473   const gchar *object_path =
474     empathy_dispatch_operation_get_object_path (operation);
475
476   DEBUG ("Dispatching process started for %s", object_path);
477
478   if (g_hash_table_lookup (cd->dispatching_channels, object_path) == NULL)
479     {
480       g_assert (g_hash_table_lookup (cd->outstanding_channels,
481         object_path) == NULL);
482
483       g_hash_table_insert (cd->dispatching_channels,
484         g_strdup (object_path), operation);
485
486       switch (empathy_dispatch_operation_get_status (operation))
487         {
488           case EMPATHY_DISPATCHER_OPERATION_STATE_PREPARING:
489             g_signal_connect (operation, "ready",
490               G_CALLBACK (dispatch_operation_ready_cb), dispatcher);
491             break;
492           case EMPATHY_DISPATCHER_OPERATION_STATE_PENDING:
493             dispatch_operation_ready_cb (operation, dispatcher);
494             break;
495           default:
496             g_assert_not_reached ();
497         }
498
499     }
500   else if (empathy_dispatch_operation_get_status (operation) >=
501       EMPATHY_DISPATCHER_OPERATION_STATE_PENDING)
502     {
503       /* Already dispatching and the operation is pending, thus the observers
504        * have seen it (if applicable), so we can flush the request right away.
505        */
506       dispatch_operation_flush_requests (self, operation, NULL, cd);
507     }
508 }
509
510 static void
511 dispatcher_flush_outstanding_operations (EmpathyDispatcher *self,
512                                          ConnectionData *cd)
513 {
514   GHashTableIter iter;
515   gpointer value;
516
517   g_hash_table_iter_init (&iter, cd->outstanding_channels);
518   while (g_hash_table_iter_next (&iter, NULL, &value))
519     {
520       EmpathyDispatchOperation *operation = EMPATHY_DISPATCH_OPERATION (value);
521
522       if (dispatcher_operation_can_start (self, operation, cd))
523         {
524           dispatcher_start_dispatching (dispatcher, operation, cd);
525           g_hash_table_iter_remove (&iter);
526         }
527     }
528 }
529
530 static void
531 dispatcher_connection_new_channel (EmpathyDispatcher *dispatcher,
532                                    TpConnection *connection,
533                                    const gchar *object_path,
534                                    const gchar *channel_type,
535                                    guint handle_type,
536                                    guint handle,
537                                    GHashTable *properties,
538                                    gboolean incoming)
539 {
540   EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
541   TpChannel         *channel;
542   ConnectionData *cd;
543   EmpathyDispatchOperation *operation;
544   int i;
545   /* Channel types we never want to dispatch because they're either deprecated
546    * or can't sensibly be dispatch (e.g. channels that should always be
547    * requested) */
548   const char *blacklist[] = {
549     TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
550     TP_IFACE_CHANNEL_TYPE_TUBES,
551     TP_IFACE_CHANNEL_TYPE_ROOM_LIST,
552     NULL
553   };
554
555   cd = g_hash_table_lookup (priv->connections, connection);
556
557   /* Don't bother with channels we have already dispatched or are dispatching
558    * currently. This can happen when NewChannel(s) is fired after
559    * RequestChannel/CreateChannel/EnsureChannel */
560   if (g_hash_table_lookup (cd->dispatched_channels, object_path) != NULL)
561     return;
562
563   if (g_hash_table_lookup (cd->dispatching_channels, object_path) != NULL)
564     return;
565
566   /* Should never occur, but just in case a CM fires spurious NewChannel(s)
567    * signals */
568   if (g_hash_table_lookup (cd->outstanding_channels, object_path) != NULL)
569     return;
570
571   /* Only pick up non-requested text and file channels. For all other it
572    * doesn't make sense to handle it if we didn't request it. The same goes
573    * for channels we discovered by the Channels property or ListChannels */
574   if (!incoming && tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT)
575         && tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
576     {
577       DEBUG ("Ignoring incoming channel of type %s on %s",
578         channel_type, object_path);
579       return;
580     }
581
582   for (i = 0 ; blacklist[i] != NULL; i++)
583     {
584       if (!tp_strdiff (channel_type, blacklist[i]))
585         {
586           DEBUG ("Ignoring blacklisted channel type %s on %s",
587             channel_type, object_path);
588           return;
589         }
590     }
591
592   DEBUG ("New channel of type %s on %s", channel_type, object_path);
593
594   if (properties == NULL)
595     channel = tp_channel_new (connection, object_path, channel_type,
596       handle_type, handle, NULL);
597   else
598     channel = tp_channel_new_from_properties (connection, object_path,
599       properties, NULL);
600
601   g_signal_connect (channel, "invalidated",
602     G_CALLBACK (dispatcher_channel_invalidated_cb),
603     dispatcher);
604
605   priv->channels = g_list_prepend (priv->channels, channel);
606
607   operation = empathy_dispatch_operation_new (connection, channel, NULL,
608     incoming);
609
610   g_object_unref (channel);
611
612   if (incoming)
613     {
614       /* Request could either be by us or by a remote party. If there are no
615        * outstanding requests for this channel type we can assume it's remote.
616        * Otherwise we wait untill they are all satisfied */
617       if (dispatcher_operation_can_start (dispatcher, operation, cd))
618         dispatcher_start_dispatching (dispatcher, operation, cd);
619       else
620         g_hash_table_insert (cd->outstanding_channels,
621           g_strdup (object_path), operation);
622     }
623   else
624     {
625       dispatcher_start_dispatching (dispatcher, operation, cd);
626     }
627 }
628
629 static void
630 dispatcher_connection_new_channel_cb (TpConnection *connection,
631                                       const gchar *object_path,
632                                       const gchar  *channel_type,
633                                       guint handle_type,
634                                       guint handle,
635                                       gboolean suppress_handler,
636                                       gpointer user_data,
637                                       GObject *object)
638 {
639   EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (object);
640
641   /* Empathy heavily abuses surpress handler (don't try this at home), if
642    * surpress handler is true then it is an outgoing channel, which is
643    * requested either by us or some other party (like the megaphone applet).
644    * Otherwise it's an incoming channel */
645   dispatcher_connection_new_channel (dispatcher, connection,
646     object_path, channel_type, handle_type, handle, NULL, !suppress_handler);
647 }
648
649 static void
650 dispatcher_connection_new_channel_with_properties (EmpathyDispatcher *dispatcher,
651                                                    TpConnection *connection,
652                                                    const gchar *object_path,
653                                                    GHashTable *properties)
654 {
655   const gchar *channel_type;
656   guint handle_type;
657   guint handle;
658   gboolean requested;
659   gboolean valid;
660
661
662   channel_type = tp_asv_get_string (properties,
663     TP_IFACE_CHANNEL ".ChannelType");
664   if (channel_type == NULL)
665     {
666       g_message ("%s had an invalid ChannelType property", object_path);
667       return;
668     }
669
670   handle_type = tp_asv_get_uint32 (properties,
671     TP_IFACE_CHANNEL ".TargetHandleType", &valid);
672   if (!valid)
673     {
674       g_message ("%s had an invalid TargetHandleType property", object_path);
675       return;
676     }
677
678   handle = tp_asv_get_uint32 (properties,
679     TP_IFACE_CHANNEL ".TargetHandle", &valid);
680   if (!valid)
681     {
682       g_message ("%s had an invalid TargetHandle property", object_path);
683       return;
684     }
685
686   /* We assume there is no channel dispather, so we're the only one dispatching
687    * it. Which means that a requested channel it is outgoing one */
688   requested = tp_asv_get_boolean (properties,
689     TP_IFACE_CHANNEL ".Requested", &valid);
690   if (!valid)
691     {
692       g_message ("%s had an invalid Requested property", object_path);
693       return;
694     }
695
696   dispatcher_connection_new_channel (dispatcher, connection,
697     object_path, channel_type, handle_type, handle, properties, !requested);
698 }
699
700 static void
701 dispatcher_connection_new_channels_cb (TpConnection *connection,
702                                        const GPtrArray *channels,
703                                        gpointer user_data,
704                                        GObject *object)
705 {
706   EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (object);
707   int i;
708
709   for (i = 0; i < channels->len ; i++)
710     {
711       GValueArray *arr = g_ptr_array_index (channels, i);
712       const gchar *object_path;
713       GHashTable *properties;
714
715       object_path = g_value_get_boxed (g_value_array_get_nth (arr, 0));
716       properties = g_value_get_boxed (g_value_array_get_nth (arr, 1));
717
718       dispatcher_connection_new_channel_with_properties (dispatcher,
719         connection, object_path, properties);
720     }
721 }
722
723 static void
724 dispatcher_connection_got_all (TpProxy *proxy,
725                                GHashTable *properties,
726                                const GError *error,
727                                gpointer user_data,
728                                GObject *object)
729 {
730   EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (object);
731   EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
732   GPtrArray *channels;
733   GPtrArray *requestable_channels;
734
735   if (error) {
736     DEBUG ("Error: %s", error->message);
737     return;
738   }
739
740   channels = tp_asv_get_boxed (properties, "Channels",
741     TP_ARRAY_TYPE_CHANNEL_DETAILS_LIST);
742
743   if (channels == NULL)
744     DEBUG ("No Channels property !?! on connection");
745   else
746     dispatcher_connection_new_channels_cb (TP_CONNECTION (proxy),
747       channels, NULL, object);
748
749   requestable_channels = tp_asv_get_boxed (properties,
750     "RequestableChannelClasses", TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST);
751
752   if (requestable_channels == NULL)
753     DEBUG ("No RequestableChannelClasses property !?! on connection");
754   else
755     {
756       ConnectionData *cd;
757       GList *requests, *l;
758       FindChannelRequest *request;
759       GList *retval;
760
761       cd = g_hash_table_lookup (priv->connections, proxy);
762       g_assert (cd != NULL);
763
764       cd->requestable_channels = g_boxed_copy (
765         TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, requestable_channels);
766
767       requests = g_hash_table_lookup (priv->outstanding_classes_requests,
768           proxy);
769
770       for (l = requests; l != NULL; l = l->next)
771         {
772           request = l->data;
773
774           retval = empathy_dispatcher_find_channel_classes (dispatcher,
775               TP_CONNECTION (proxy), request->channel_type,
776               request->handle_type, request->properties);
777           request->callback (retval, request->user_data);
778
779           free_find_channel_request (request);
780           g_list_free (retval);
781         }
782
783       g_list_free (requests);
784
785       g_hash_table_remove (priv->outstanding_classes_requests, proxy);
786     }
787 }
788
789 static void
790 dispatcher_connection_list_channels_cb (TpConnection    *connection,
791                                         const GPtrArray *channels,
792                                         const GError    *error,
793                                         gpointer         user_data,
794                                         GObject         *dispatcher)
795 {
796   int i;
797
798   if (error)
799     {
800       DEBUG ("Error: %s", error->message);
801       return;
802     }
803
804   for (i = 0; i < channels->len; i++)
805     {
806       GValueArray *values;
807
808       values = g_ptr_array_index (channels, i);
809       /* We don't have any extra info, so assume already existing channels are
810        * incoming... */
811       dispatcher_connection_new_channel (EMPATHY_DISPATCHER (dispatcher),
812         connection,
813         g_value_get_boxed (g_value_array_get_nth (values, 0)),
814         g_value_get_string (g_value_array_get_nth (values, 1)),
815         g_value_get_uint (g_value_array_get_nth (values, 2)),
816         g_value_get_uint (g_value_array_get_nth (values, 3)),
817         NULL, TRUE);
818     }
819 }
820
821 static void
822 dispatcher_connection_advertise_capabilities_cb (TpConnection    *connection,
823                                                  const GPtrArray *capabilities,
824                                                  const GError    *error,
825                                                  gpointer         user_data,
826                                                  GObject         *dispatcher)
827 {
828   if (error)
829     DEBUG ("Error: %s", error->message);
830 }
831
832 static void
833 dispatcher_new_connection_cb (EmpathyAccountManager *manager,
834                               TpConnection *connection,
835                               EmpathyDispatcher *dispatcher)
836 {
837   EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
838   GPtrArray   *capabilities;
839   GType        cap_type;
840   GValue       cap = {0, };
841   const gchar *remove = NULL;
842
843   if (g_hash_table_lookup (priv->connections, connection) != NULL)
844     return;
845
846   g_hash_table_insert (priv->connections, g_object_ref (connection),
847     new_connection_data ());
848
849   g_signal_connect (connection, "invalidated",
850     G_CALLBACK (dispatcher_connection_invalidated_cb), dispatcher);
851
852   if (tp_proxy_has_interface_by_id (TP_PROXY (connection),
853       TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS))
854     {
855       tp_cli_connection_interface_requests_connect_to_new_channels (connection,
856         dispatcher_connection_new_channels_cb,
857         NULL, NULL, G_OBJECT (dispatcher), NULL);
858
859       tp_cli_dbus_properties_call_get_all (connection, -1,
860         TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
861         dispatcher_connection_got_all,
862         NULL, NULL, G_OBJECT (dispatcher));
863     }
864   else
865     {
866       tp_cli_connection_connect_to_new_channel (connection,
867         dispatcher_connection_new_channel_cb,
868         NULL, NULL, G_OBJECT (dispatcher), NULL);
869
870       tp_cli_connection_call_list_channels (connection, -1,
871         dispatcher_connection_list_channels_cb, NULL, NULL,
872         G_OBJECT (dispatcher));
873
874     }
875
876   /* Advertise VoIP capabilities */
877   capabilities = g_ptr_array_sized_new (1);
878   cap_type = dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING,
879     G_TYPE_UINT, G_TYPE_INVALID);
880   g_value_init (&cap, cap_type);
881   g_value_take_boxed (&cap, dbus_g_type_specialized_construct (cap_type));
882   dbus_g_type_struct_set (&cap,
883         0, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
884         1, TP_CHANNEL_MEDIA_CAPABILITY_AUDIO |
885            TP_CHANNEL_MEDIA_CAPABILITY_VIDEO |
886            TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_STUN  |
887            TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_GTALK_P2P, G_MAXUINT);
888   g_ptr_array_add (capabilities, g_value_get_boxed (&cap));
889
890   tp_cli_connection_interface_capabilities_call_advertise_capabilities (
891     connection, -1, capabilities, &remove,
892     dispatcher_connection_advertise_capabilities_cb,
893     NULL, NULL, G_OBJECT (dispatcher));
894
895   g_value_unset (&cap);
896   g_ptr_array_free (capabilities, TRUE);
897 }
898
899 static void
900 remove_idle_handlers (gpointer key,
901                       gpointer value,
902                       gpointer user_data)
903 {
904   guint source_id;
905
906   source_id = GPOINTER_TO_UINT (value);
907   g_source_remove (source_id);
908 }
909
910 static GObject *
911 dispatcher_constructor (GType type,
912                         guint n_construct_params,
913                         GObjectConstructParam *construct_params)
914 {
915   GObject *retval;
916
917   if (dispatcher == NULL)
918     {
919       retval = G_OBJECT_CLASS (empathy_dispatcher_parent_class)->constructor
920           (type, n_construct_params, construct_params);
921
922       dispatcher = EMPATHY_DISPATCHER (retval);
923       g_object_add_weak_pointer (retval, (gpointer) &dispatcher);
924     }
925   else
926     {
927       retval = g_object_ref (dispatcher);
928     }
929
930   return retval;
931 }
932
933 static void
934 dispatcher_finalize (GObject *object)
935 {
936   EmpathyDispatcherPriv *priv = GET_PRIV (object);
937   GList *l;
938   GHashTableIter iter;
939   gpointer connection;
940   GList *list;
941
942   if (priv->request_channel_class_async_ids != NULL)
943     {
944       g_hash_table_foreach (priv->request_channel_class_async_ids,
945         remove_idle_handlers, NULL);
946       g_hash_table_destroy (priv->request_channel_class_async_ids);
947     }
948
949   g_signal_handlers_disconnect_by_func (priv->account_manager,
950       dispatcher_new_connection_cb, object);
951
952   for (l = priv->channels; l; l = l->next)
953     {
954       g_signal_handlers_disconnect_by_func (l->data,
955           dispatcher_channel_invalidated_cb, object);
956     }
957
958   g_list_free (priv->channels);
959
960   g_hash_table_iter_init (&iter, priv->connections);
961   while (g_hash_table_iter_next (&iter, &connection, NULL))
962     {
963       g_signal_handlers_disconnect_by_func (connection,
964           dispatcher_connection_invalidated_cb, object);
965     }
966
967   g_hash_table_iter_init (&iter, priv->outstanding_classes_requests);
968   while (g_hash_table_iter_next (&iter, &connection, (gpointer *) &list))
969     {
970       g_list_foreach (list, (GFunc) free_find_channel_request, NULL);
971       g_list_free (list);
972     }
973
974   g_object_unref (priv->account_manager);
975   g_object_unref (priv->mc);
976
977   g_hash_table_destroy (priv->connections);
978   g_hash_table_destroy (priv->outstanding_classes_requests);
979 }
980
981 static void
982 empathy_dispatcher_class_init (EmpathyDispatcherClass *klass)
983 {
984   GObjectClass *object_class = G_OBJECT_CLASS (klass);
985
986   object_class->finalize = dispatcher_finalize;
987   object_class->constructor = dispatcher_constructor;
988
989   signals[OBSERVE] =
990     g_signal_new ("observe",
991       G_TYPE_FROM_CLASS (klass),
992       G_SIGNAL_RUN_LAST,
993       0,
994       NULL, NULL,
995       g_cclosure_marshal_VOID__OBJECT,
996       G_TYPE_NONE,
997       1, EMPATHY_TYPE_DISPATCH_OPERATION);
998
999   signals[APPROVE] =
1000     g_signal_new ("approve",
1001       G_TYPE_FROM_CLASS (klass),
1002       G_SIGNAL_RUN_LAST,
1003       0,
1004       NULL, NULL,
1005       g_cclosure_marshal_VOID__OBJECT,
1006       G_TYPE_NONE,
1007       1, EMPATHY_TYPE_DISPATCH_OPERATION);
1008
1009   signals[DISPATCH] =
1010     g_signal_new ("dispatch",
1011       G_TYPE_FROM_CLASS (klass),
1012       G_SIGNAL_RUN_LAST,
1013       0,
1014       NULL, NULL,
1015       g_cclosure_marshal_VOID__OBJECT,
1016       G_TYPE_NONE,
1017       1, EMPATHY_TYPE_DISPATCH_OPERATION);
1018
1019   g_type_class_add_private (object_class, sizeof (EmpathyDispatcherPriv));
1020
1021 }
1022
1023 static void
1024 empathy_dispatcher_init (EmpathyDispatcher *dispatcher)
1025 {
1026   GList *connections, *l;
1027   EmpathyDispatcherPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (dispatcher,
1028     EMPATHY_TYPE_DISPATCHER, EmpathyDispatcherPriv);
1029
1030   dispatcher->priv = priv;
1031   priv->mc = empathy_mission_control_dup_singleton ();
1032   priv->account_manager = empathy_account_manager_dup_singleton ();
1033
1034   g_signal_connect (priv->account_manager, "new-connection",
1035     G_CALLBACK (dispatcher_new_connection_cb),
1036     dispatcher);
1037
1038   priv->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal,
1039     g_object_unref, (GDestroyNotify) free_connection_data);
1040
1041   priv->outstanding_classes_requests = g_hash_table_new_full (g_direct_hash,
1042     g_direct_equal, g_object_unref, NULL);
1043
1044   priv->channels = NULL;
1045
1046   connections = empathy_account_manager_dup_connections (priv->account_manager);
1047   for (l = connections; l; l = l->next)
1048     {
1049       dispatcher_new_connection_cb (priv->account_manager, l->data, dispatcher);
1050       g_object_unref (l->data);
1051     }
1052   g_list_free (connections);
1053
1054   priv->request_channel_class_async_ids = g_hash_table_new (g_direct_hash,
1055     g_direct_equal);
1056 }
1057
1058 EmpathyDispatcher *
1059 empathy_dispatcher_dup_singleton (void)
1060 {
1061   return EMPATHY_DISPATCHER (g_object_new (EMPATHY_TYPE_DISPATCHER, NULL));
1062 }
1063
1064 static void
1065 dispatcher_request_failed (EmpathyDispatcher *dispatcher,
1066                            DispatcherRequestData *request_data,
1067                            const GError *error)
1068 {
1069   EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
1070   ConnectionData *conn_data;
1071
1072   conn_data = g_hash_table_lookup (priv->connections, request_data->connection);
1073   if (request_data->cb != NULL)
1074     request_data->cb (NULL, error, request_data->user_data);
1075
1076   conn_data->outstanding_requests =
1077       g_list_remove (conn_data->outstanding_requests, request_data);
1078   free_dispatcher_request_data (request_data);
1079 }
1080
1081 static void
1082 dispatcher_connection_new_requested_channel (EmpathyDispatcher *dispatcher,
1083                                              DispatcherRequestData *request_data,
1084                                              const gchar *object_path,
1085                                              GHashTable *properties,
1086                                              const GError *error)
1087 {
1088   EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
1089   EmpathyDispatchOperation *operation = NULL;
1090   ConnectionData *conn_data;
1091
1092   conn_data = g_hash_table_lookup (priv->connections,
1093     request_data->connection);
1094
1095   if (error)
1096     {
1097       DEBUG ("Channel request failed: %s", error->message);
1098
1099       dispatcher_request_failed (dispatcher, request_data, error);
1100
1101       goto out;
1102     }
1103
1104   operation = g_hash_table_lookup (conn_data->outstanding_channels,
1105     object_path);
1106
1107   if (operation != NULL)
1108     g_hash_table_remove (conn_data->outstanding_channels, object_path);
1109   else
1110     operation = g_hash_table_lookup (conn_data->dispatching_channels,
1111         object_path);
1112
1113   if (operation == NULL)
1114     {
1115       DispatchData *data = g_hash_table_lookup (conn_data->dispatched_channels,
1116         object_path);
1117
1118       if (data != NULL)
1119         {
1120           operation = empathy_dispatch_operation_new_with_wrapper (
1121             request_data->connection,
1122             data->channel, request_data->contact, FALSE,
1123             data->channel_wrapper);
1124         }
1125       else
1126         {
1127           TpChannel *channel;
1128
1129           if (properties != NULL)
1130             channel = tp_channel_new_from_properties (request_data->connection,
1131               object_path, properties, NULL);
1132           else
1133             channel = tp_channel_new (request_data->connection, object_path,
1134               request_data->channel_type, request_data->handle_type,
1135               request_data->handle, NULL);
1136
1137           g_signal_connect (channel, "invalidated",
1138             G_CALLBACK (dispatcher_channel_invalidated_cb),
1139             request_data->dispatcher);
1140
1141           priv->channels = g_list_prepend (priv->channels, channel);
1142
1143           operation = empathy_dispatch_operation_new (request_data->connection,
1144              channel, request_data->contact, FALSE);
1145           g_object_unref (channel);
1146         }
1147     }
1148   else
1149     {
1150       /* Already existed set potential extra information */
1151       g_object_set (G_OBJECT (operation),
1152         "contact", request_data->contact,
1153         NULL);
1154     }
1155
1156   request_data->operation = operation;
1157
1158   /* (pre)-approve this right away as we requested it
1159    * This might cause the channel to be claimed, in which case the operation
1160    * will disappear. So ref it, and check the status before starting the
1161    * dispatching */
1162
1163   g_object_ref (operation);
1164   empathy_dispatch_operation_approve (operation);
1165
1166    if (empathy_dispatch_operation_get_status (operation) <
1167      EMPATHY_DISPATCHER_OPERATION_STATE_APPROVING)
1168       dispatcher_start_dispatching (request_data->dispatcher, operation,
1169           conn_data);
1170
1171   g_object_unref (operation);
1172
1173 out:
1174   dispatcher_flush_outstanding_operations (request_data->dispatcher,
1175     conn_data);
1176 }
1177
1178 static void
1179 dispatcher_request_channel_cb (TpConnection *connection,
1180                                const gchar  *object_path,
1181                                const GError *error,
1182                                gpointer user_data,
1183                                GObject *weak_object)
1184 {
1185   EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (weak_object);
1186   DispatcherRequestData *request_data = (DispatcherRequestData *) user_data;
1187
1188   dispatcher_connection_new_requested_channel (dispatcher,
1189     request_data, object_path, NULL, error);
1190 }
1191
1192 static void
1193 dispatcher_request_channel (DispatcherRequestData *request_data)
1194 {
1195   tp_cli_connection_call_request_channel (request_data->connection, -1,
1196     request_data->channel_type,
1197     request_data->handle_type,
1198     request_data->handle,
1199     TRUE, dispatcher_request_channel_cb,
1200     request_data, NULL, G_OBJECT (request_data->dispatcher));
1201 }
1202
1203 void
1204 empathy_dispatcher_chat_with_contact (EmpathyContact *contact,
1205                                       EmpathyDispatcherRequestCb *callback,
1206                                       gpointer user_data)
1207 {
1208   EmpathyDispatcher *dispatcher;
1209   EmpathyDispatcherPriv *priv;
1210   TpConnection *connection;
1211   ConnectionData *connection_data;
1212   DispatcherRequestData *request_data;
1213
1214   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1215
1216   dispatcher = empathy_dispatcher_dup_singleton ();
1217   priv = GET_PRIV (dispatcher);
1218
1219   connection = empathy_contact_get_connection (contact);
1220   connection_data = g_hash_table_lookup (priv->connections, connection);
1221
1222   /* The contact handle might not be known yet */
1223   request_data  = new_dispatcher_request_data (dispatcher, connection,
1224     TP_IFACE_CHANNEL_TYPE_TEXT, TP_HANDLE_TYPE_CONTACT,
1225     empathy_contact_get_handle (contact), NULL, contact, callback, user_data);
1226
1227   connection_data->outstanding_requests = g_list_prepend
1228     (connection_data->outstanding_requests, request_data);
1229
1230   dispatcher_request_channel (request_data);
1231
1232   g_object_unref (dispatcher);
1233 }
1234
1235 typedef struct
1236 {
1237   EmpathyDispatcher *dispatcher;
1238   EmpathyDispatcherRequestCb *callback;
1239   gpointer user_data;
1240 } ChatWithContactIdData;
1241
1242 static void
1243 dispatcher_chat_with_contact_id_cb (EmpathyTpContactFactory *factory,
1244                                     EmpathyContact          *contact,
1245                                     const GError            *error,
1246                                     gpointer                 user_data,
1247                                     GObject                 *weak_object)
1248 {
1249   ChatWithContactIdData *data = user_data;
1250
1251   if (error)
1252     {
1253       /* FIXME: Should call data->callback with the error */
1254       DEBUG ("Error: %s", error->message);
1255     }
1256   else
1257     {
1258       empathy_dispatcher_chat_with_contact (contact, data->callback, data->user_data);
1259     }
1260
1261   g_object_unref (data->dispatcher);
1262   g_slice_free (ChatWithContactIdData, data);
1263 }
1264
1265 void
1266 empathy_dispatcher_chat_with_contact_id (TpConnection *connection,
1267                                          const gchar *contact_id,
1268                                          EmpathyDispatcherRequestCb *callback,
1269                                          gpointer user_data)
1270 {
1271   EmpathyDispatcher *dispatcher;
1272   EmpathyTpContactFactory *factory;
1273   ChatWithContactIdData *data;
1274
1275   g_return_if_fail (TP_IS_CONNECTION (connection));
1276   g_return_if_fail (!EMP_STR_EMPTY (contact_id));
1277
1278   dispatcher = empathy_dispatcher_dup_singleton ();
1279   factory = empathy_tp_contact_factory_dup_singleton (connection);
1280   data = g_slice_new0 (ChatWithContactIdData);
1281   data->dispatcher = dispatcher;
1282   data->callback = callback;
1283   data->user_data = user_data;
1284   empathy_tp_contact_factory_get_from_id (factory, contact_id,
1285       dispatcher_chat_with_contact_id_cb, data, NULL, NULL);
1286
1287   g_object_unref (factory);
1288 }
1289
1290 static void
1291 dispatcher_request_handles_cb (TpConnection *connection,
1292                                const GArray *handles,
1293                                const GError *error,
1294                                gpointer user_data,
1295                                GObject *object)
1296 {
1297   DispatcherRequestData *request_data = (DispatcherRequestData *) user_data;
1298
1299   if (error != NULL)
1300     {
1301       EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (object);
1302       EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
1303       ConnectionData *cd;
1304
1305       cd = g_hash_table_lookup (priv->connections, request_data->connection);
1306
1307       if (request_data->cb)
1308         request_data->cb (NULL, error, request_data->user_data);
1309
1310       cd->outstanding_requests = g_list_remove (cd->outstanding_requests,
1311         request_data);
1312
1313       free_dispatcher_request_data (request_data);
1314
1315       dispatcher_flush_outstanding_operations (dispatcher, cd);
1316       return;
1317     }
1318
1319   request_data->handle = g_array_index (handles, guint, 0);
1320   dispatcher_request_channel (request_data);
1321 }
1322
1323 void
1324 empathy_dispatcher_join_muc (TpConnection *connection,
1325                              const gchar *roomname,
1326                              EmpathyDispatcherRequestCb *callback,
1327                              gpointer user_data)
1328 {
1329   EmpathyDispatcher *dispatcher;
1330   EmpathyDispatcherPriv *priv;
1331   DispatcherRequestData *request_data;
1332   ConnectionData *connection_data;
1333   const gchar *names[] = { roomname, NULL };
1334
1335   g_return_if_fail (TP_IS_CONNECTION (connection));
1336   g_return_if_fail (!EMP_STR_EMPTY (roomname));
1337
1338   dispatcher = empathy_dispatcher_dup_singleton ();
1339   priv = GET_PRIV (dispatcher);
1340
1341   connection_data = g_hash_table_lookup (priv->connections, connection);
1342
1343   /* Don't know the room handle yet */
1344   request_data  = new_dispatcher_request_data (dispatcher, connection,
1345     TP_IFACE_CHANNEL_TYPE_TEXT, TP_HANDLE_TYPE_ROOM, 0, NULL,
1346     NULL, callback, user_data);
1347
1348   connection_data->outstanding_requests = g_list_prepend
1349     (connection_data->outstanding_requests, request_data);
1350
1351   tp_cli_connection_call_request_handles (connection, -1,
1352     TP_HANDLE_TYPE_ROOM, names,
1353     dispatcher_request_handles_cb, request_data, NULL,
1354     G_OBJECT (dispatcher));
1355
1356   g_object_unref (dispatcher);
1357 }
1358
1359 static void
1360 dispatcher_create_channel_cb (TpConnection *connect,
1361                               const gchar *object_path,
1362                               GHashTable *properties,
1363                               const GError *error,
1364                               gpointer user_data,
1365                               GObject *weak_object)
1366 {
1367   EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (weak_object);
1368   DispatcherRequestData *request_data = (DispatcherRequestData *) user_data;
1369
1370   dispatcher_connection_new_requested_channel (dispatcher,
1371     request_data, object_path, properties, error);
1372 }
1373
1374 void
1375 empathy_dispatcher_create_channel (EmpathyDispatcher *dispatcher,
1376                                    TpConnection *connection,
1377                                    GHashTable *request,
1378                                    EmpathyDispatcherRequestCb *callback,
1379                                    gpointer user_data)
1380 {
1381   EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
1382   ConnectionData *connection_data;
1383   DispatcherRequestData *request_data;
1384   const gchar *channel_type;
1385   guint handle_type;
1386   guint handle;
1387   gboolean valid;
1388
1389   g_return_if_fail (EMPATHY_IS_DISPATCHER (dispatcher));
1390   g_return_if_fail (TP_IS_CONNECTION (connection));
1391   g_return_if_fail (request != NULL);
1392
1393   connection_data = g_hash_table_lookup (priv->connections, connection);
1394   g_assert (connection_data != NULL);
1395
1396   channel_type = tp_asv_get_string (request, TP_IFACE_CHANNEL ".ChannelType");
1397
1398   handle_type = tp_asv_get_uint32 (request,
1399     TP_IFACE_CHANNEL ".TargetHandleType", &valid);
1400   if (!valid)
1401     handle_type = TP_UNKNOWN_HANDLE_TYPE;
1402
1403   handle = tp_asv_get_uint32 (request, TP_IFACE_CHANNEL ".TargetHandle", NULL);
1404
1405   request_data  = new_dispatcher_request_data (dispatcher, connection,
1406     channel_type, handle_type, handle, request,
1407     NULL, callback, user_data);
1408
1409   connection_data->outstanding_requests = g_list_prepend
1410     (connection_data->outstanding_requests, request_data);
1411
1412   tp_cli_connection_interface_requests_call_create_channel (
1413     request_data->connection, -1,
1414     request_data->request, dispatcher_create_channel_cb, request_data, NULL,
1415     G_OBJECT (request_data->dispatcher));
1416 }
1417
1418 typedef struct {
1419   gboolean mismatch;
1420   gboolean not_generic;
1421   const char *channel_namespace;
1422   GArray *properties;
1423 } PropertiesMatcherData;
1424
1425 static void
1426 match_with_properties (gpointer key,
1427                        gpointer val,
1428                        gpointer user_data)
1429 {
1430   int idx;
1431   PropertiesMatcherData *data = user_data;
1432
1433   if (data->mismatch)
1434     return;
1435
1436   /* discard generic properties, as we already checked them */
1437   if (!g_str_has_prefix ((char *) key, data->channel_namespace))
1438     return;
1439
1440   data->not_generic = TRUE;
1441
1442   for (idx = 0; idx < data->properties->len; idx++)
1443     {
1444       /* if |key| exists in the properties, it's fine */
1445       if (!tp_strdiff ((char *) key, g_array_index (data->properties,
1446           char *, idx)))
1447         return;
1448     }
1449 }
1450
1451 static GList *
1452 empathy_dispatcher_find_channel_classes (EmpathyDispatcher *dispatcher,
1453                                          TpConnection *connection,
1454                                          const gchar *channel_type,
1455                                          guint handle_type,
1456                                          GArray *fixed_properties)
1457 {
1458   EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
1459   GValueArray *class;
1460   GHashTable *fixed_props;
1461   GValue *val;
1462   GPtrArray *classes;
1463   GList *matching_classes;
1464   const gchar *c_type;
1465   guint32 h_type;
1466   gboolean valid, found;
1467   int i;
1468   ConnectionData *cd;
1469
1470   g_return_val_if_fail (channel_type != NULL, NULL);
1471   g_return_val_if_fail (handle_type != 0, NULL);
1472
1473   cd = g_hash_table_lookup (priv->connections, connection);
1474
1475   if (cd == NULL)
1476     return NULL;
1477
1478   classes = cd->requestable_channels;
1479   if (classes == NULL)
1480     return NULL;
1481
1482   found = TRUE;
1483   matching_classes = NULL;
1484
1485   for (i = 0; i < classes->len; i++)
1486     {
1487       class = g_ptr_array_index (classes, i);
1488       val = g_value_array_get_nth (class, 0);
1489
1490       /* if the class doesn't match channel type discard it. */
1491       fixed_props = g_value_get_boxed (val);
1492       c_type = tp_asv_get_string (fixed_props,
1493         TP_IFACE_CHANNEL ".ChannelType");
1494
1495       if (tp_strdiff (channel_type, c_type))
1496         continue;
1497
1498       /* we have the right channel type, see if the handle type matches */
1499       h_type = tp_asv_get_uint32 (fixed_props,
1500         TP_IFACE_CHANNEL ".TargetHandleType", &valid);
1501
1502       if (!valid || handle_type != h_type)
1503         continue;
1504
1505       /* now we should ensure that the fixed props that we specified
1506        * are the only values inside the hash table.
1507        */
1508       if (fixed_properties != NULL)
1509         {
1510           PropertiesMatcherData *data;
1511
1512           data = g_slice_new0 (PropertiesMatcherData);
1513           data->mismatch = FALSE;
1514           data->not_generic = FALSE;
1515           data->channel_namespace = channel_type;
1516           data->properties = fixed_properties;
1517           g_hash_table_foreach (fixed_props, match_with_properties, data);
1518
1519           found = (!data->mismatch && data->not_generic);
1520
1521           g_slice_free (PropertiesMatcherData, data);
1522
1523           if (!found)
1524             continue;
1525         }
1526
1527       if (found)
1528         matching_classes = g_list_prepend (matching_classes, class);
1529     }
1530
1531   if (matching_classes != NULL)
1532     return g_list_reverse (matching_classes);
1533   else
1534     return NULL;
1535 }
1536
1537 static gboolean
1538 find_channel_class_idle_cb (gpointer user_data)
1539 {
1540   GList *retval;
1541   GList *requests;
1542   FindChannelRequest *request = user_data;
1543   ConnectionData *cd;
1544   gboolean is_ready = TRUE;
1545   EmpathyDispatcherPriv *priv = GET_PRIV (request->dispatcher);
1546
1547   g_hash_table_remove (priv->request_channel_class_async_ids, request);
1548
1549   cd = g_hash_table_lookup (priv->connections, request->connection);
1550
1551   if (cd == NULL)
1552     is_ready = FALSE;
1553   else if (cd->requestable_channels == NULL)
1554     is_ready = FALSE;
1555
1556   if (is_ready)
1557     {
1558       retval = empathy_dispatcher_find_channel_classes (request->dispatcher,
1559           request->connection, request->channel_type, request->handle_type,
1560           request->properties);
1561
1562       request->callback (retval, request->user_data);
1563       free_find_channel_request (request);
1564       g_list_free (retval);
1565
1566       return FALSE;
1567     }
1568
1569   requests = g_hash_table_lookup (priv->outstanding_classes_requests,
1570       request->connection);
1571   requests = g_list_prepend (requests, request);
1572
1573   g_hash_table_insert (priv->outstanding_classes_requests,
1574       request->connection, requests);
1575
1576   return FALSE;
1577 }
1578
1579 static GArray *
1580 setup_varargs (va_list var_args,
1581                const char *channel_namespace,
1582                const char *first_property_name)
1583 {
1584   const char *name;
1585   char *name_full;
1586   GArray *properties;
1587
1588   if (first_property_name == NULL)
1589     return NULL;
1590
1591   name = first_property_name;
1592   properties = g_array_new (TRUE, TRUE, sizeof (char *));
1593
1594   while (name != NULL)
1595     {
1596       name_full = g_strconcat (channel_namespace, ".", name, NULL);
1597       properties = g_array_append_val (properties, name_full);
1598       name = va_arg (var_args, char *);
1599     }
1600
1601   return properties;
1602 }
1603
1604 /**
1605  * empathy_dispatcher_find_requestable_channel_classes:
1606  * @dispatcher: an #EmpathyDispatcher
1607  * @connection: a #TpConnection
1608  * @channel_type: a string identifying the type of the channel to lookup
1609  * @handle_type: the handle type for the channel
1610  * @first_property_name: %NULL, or the name of the first fixed property,
1611  * followed optionally by more names, followed by %NULL.
1612  *
1613  * Returns all the channel classes that a client can request for the connection
1614  * @connection, of the type identified by @channel_type, @handle_type and the
1615  * fixed properties list.
1616  * If @first_property_name is %NULL, no additional fixed properties will be
1617  * specified and the function will return all the requestable classes for
1618  * the specified channel type and handle type.
1619  * Note that this function may return %NULL without performing any lookup if
1620  * @connection is not ready. To ensure that @connection is always ready,
1621  * use the empathy_dispatcher_find_requestable_channel_classes_async() variant.
1622  *
1623  * Return value: a #GList of #GValueArray objects, where the first element in
1624  * the array is a #GHashTable of the fixed properties, and the second is
1625  * a #GStrv of the allowed properties for the class.
1626  */
1627 GList *
1628 empathy_dispatcher_find_requestable_channel_classes
1629                                  (EmpathyDispatcher *dispatcher,
1630                                   TpConnection *connection,
1631                                   const gchar *channel_type,
1632                                   guint handle_type,
1633                                   const char *first_property_name,
1634                                   ...)
1635 {
1636   va_list var_args;
1637   GArray *properties;
1638   EmpathyDispatcherPriv *priv;
1639   GList *retval;
1640   int idx;
1641   char *str;
1642
1643   g_return_val_if_fail (EMPATHY_IS_DISPATCHER (dispatcher), NULL);
1644   g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL);
1645   g_return_val_if_fail (channel_type != NULL, NULL);
1646   g_return_val_if_fail (handle_type != 0, NULL);
1647
1648   priv = GET_PRIV (dispatcher);
1649
1650   va_start (var_args, first_property_name);
1651
1652   properties = setup_varargs (var_args, channel_type, first_property_name);
1653
1654   va_end (var_args);
1655
1656   retval = empathy_dispatcher_find_channel_classes (dispatcher, connection,
1657     channel_type, handle_type, properties);
1658
1659   /* free the properties array */
1660   for (idx = 0; idx < properties->len ; idx++)
1661     {
1662       str = g_array_index (properties, char *, idx);
1663       g_free (str);
1664     }
1665
1666   g_array_free (properties, TRUE);
1667
1668   return retval;
1669 }
1670
1671 /**
1672  * empathy_dispatcher_find_requestable_channel_classes_async:
1673  * @dispatcher: an #EmpathyDispatcher
1674  * @connection: a #TpConnection
1675  * @channel_type: a string identifying the type of the channel to lookup
1676  * @handle_type: the handle type for the channel
1677  * @callback: the callback to call when @connection is ready
1678  * @user_data: the user data to pass to @callback
1679  * @first_property_name: %NULL, or the name of the first fixed property,
1680  * followed optionally by more names, followed by %NULL.
1681  *
1682  * Please see the documentation of
1683  * empathy_dispatcher_find_requestable_channel_classes() for a detailed
1684  * description of this function.
1685  */
1686 void
1687 empathy_dispatcher_find_requestable_channel_classes_async
1688                                  (EmpathyDispatcher *dispatcher,
1689                                   TpConnection *connection,
1690                                   const gchar *channel_type,
1691                                   guint handle_type,
1692                                   EmpathyDispatcherFindChannelClassCb callback,
1693                                   gpointer user_data,
1694                                   const char *first_property_name,
1695                                   ...)
1696 {
1697   va_list var_args;
1698   GArray *properties;
1699   FindChannelRequest *request;
1700   EmpathyDispatcherPriv *priv;
1701   guint source_id;
1702
1703   g_return_if_fail (EMPATHY_IS_DISPATCHER (dispatcher));
1704   g_return_if_fail (TP_IS_CONNECTION (connection));
1705   g_return_if_fail (channel_type != NULL);
1706   g_return_if_fail (handle_type != 0);
1707
1708   priv = GET_PRIV (dispatcher);
1709
1710   va_start (var_args, first_property_name);
1711
1712   properties = setup_varargs (var_args, channel_type, first_property_name);
1713
1714   va_end (var_args);
1715
1716   /* append another request for this connection */
1717   request = g_slice_new0 (FindChannelRequest);
1718   request->dispatcher = dispatcher;
1719   request->channel_type = g_strdup (channel_type);
1720   request->handle_type = handle_type;
1721   request->connection = connection;
1722   request->callback = callback;
1723   request->user_data = user_data;
1724   request->properties = properties;
1725
1726   source_id = g_idle_add (find_channel_class_idle_cb, request);
1727
1728   g_hash_table_insert (priv->request_channel_class_async_ids,
1729     request, GUINT_TO_POINTER (source_id));
1730 }