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