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