]> git.0d.be Git - empathy.git/blob - libempathy/empathy-dispatcher.c
dispatcher_tubes_new_tube_cb: add some tube handling debug messages
[empathy.git] / libempathy / empathy-dispatcher.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007-2008 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  * 
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  */
21
22 #include <config.h>
23
24 #include <string.h>
25
26 #include <glib/gi18n-lib.h>
27
28 #include <telepathy-glib/enums.h>
29 #include <telepathy-glib/connection.h>
30 #include <telepathy-glib/util.h>
31 #include <telepathy-glib/dbus.h>
32 #include <telepathy-glib/proxy-subclass.h>
33
34 #include <libmissioncontrol/mission-control.h>
35 #include <libmissioncontrol/mc-account.h>
36
37 #include <extensions/extensions.h>
38
39 #include "empathy-dispatcher.h"
40 #include "empathy-utils.h"
41 #include "empathy-tube-handler.h"
42 #include "empathy-account-manager.h"
43 #include "empathy-contact-factory.h"
44 #include "empathy-tp-group.h"
45 #include "empathy-tp-file.h"
46 #include "empathy-chatroom-manager.h"
47 #include "empathy-utils.h"
48
49 #define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER
50 #include <libempathy/empathy-debug.h>
51
52 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyDispatcher)
53 typedef struct {
54         GHashTable     *connections;
55         EmpathyAccountManager *account_manager;
56         MissionControl *mc;
57         GSList         *tubes;
58         EmpathyChatroomManager *chatroom_mgr;
59 } EmpathyDispatcherPriv;
60
61 G_DEFINE_TYPE (EmpathyDispatcher, empathy_dispatcher, G_TYPE_OBJECT);
62
63 enum {
64         DISPATCH_CHANNEL,
65         FILTER_CHANNEL,
66         FILTER_TUBE,
67         LAST_SIGNAL
68 };
69
70 static guint signals[LAST_SIGNAL];
71 static EmpathyDispatcher *dispatcher = NULL;
72
73 void
74 empathy_dispatcher_channel_process (EmpathyDispatcher *dispatcher,
75                                     TpChannel         *channel)
76 {
77         g_signal_emit (dispatcher, signals[DISPATCH_CHANNEL], 0, channel);
78 }
79
80 typedef struct {
81         EmpathyDispatcherTube  public;
82         EmpathyContactFactory *factory;
83         gchar                 *bus_name;
84         gchar                 *object_path;
85         guint                  ref_count;
86         gboolean               handled;
87 } DispatcherTube;
88
89 GType
90 empathy_dispatcher_tube_get_type (void)
91 {
92         static GType type_id = 0;
93
94         if (!type_id) {
95                 type_id = g_boxed_type_register_static ("EmpathyDispatcherTube",
96                                                         (GBoxedCopyFunc) empathy_dispatcher_tube_ref,
97                                                         (GBoxedFreeFunc) empathy_dispatcher_tube_unref);
98         }
99
100         return type_id;
101 }
102
103 EmpathyDispatcherTube *
104 empathy_dispatcher_tube_ref (EmpathyDispatcherTube *data)
105 {
106         DispatcherTube *tube = (DispatcherTube*) data;
107
108         g_return_val_if_fail (tube != NULL, NULL);
109
110         tube->ref_count++;
111
112         return data;
113 }
114
115 void
116 empathy_dispatcher_tube_unref (EmpathyDispatcherTube *data)
117 {
118         DispatcherTube *tube = (DispatcherTube*) data;
119
120         g_return_if_fail (tube != NULL);
121
122         if (--tube->ref_count == 0) {
123                 if (!tube->handled) {
124                         DEBUG ("Tube can't be handled, closing");
125                         tp_cli_channel_type_tubes_call_close_tube (tube->public.channel, -1,
126                                                                    tube->public.id,
127                                                                    NULL, NULL, NULL,
128                                                                    NULL);
129                 }
130
131                 g_free (tube->bus_name);
132                 g_free (tube->object_path);
133                 g_object_unref (tube->factory);
134                 g_object_unref (tube->public.channel);
135                 g_object_unref (tube->public.initiator);
136                 g_slice_free (DispatcherTube, tube);
137         }
138 }
139
140 static void
141 dispatcher_tubes_handle_tube_cb (TpProxy      *channel,
142                                  const GError *error,
143                                  gpointer      user_data,
144                                  GObject      *dispatcher)
145 {
146         DispatcherTube *tube = user_data;
147
148         if (error) {
149                 DEBUG ("Error: %s", error->message);
150         } else {
151                 tube->handled = TRUE;
152         }
153 }
154
155 void
156 empathy_dispatcher_tube_process (EmpathyDispatcher     *dispatcher,
157                                  EmpathyDispatcherTube *user_data)
158 {
159         DispatcherTube *tube = (DispatcherTube*) user_data;
160
161         if (tube->public.activatable) {
162                 TpProxy *connection;
163                 TpProxy *thandler;
164                 gchar   *object_path;
165                 guint    handle_type;
166                 guint    handle;
167
168                 /* Create the proxy for the tube handler */
169                 thandler = g_object_new (TP_TYPE_PROXY,
170                                          "dbus-connection", tp_get_bus (),
171                                          "bus-name", tube->bus_name,
172                                          "object-path", tube->object_path,
173                                          NULL);
174                 tp_proxy_add_interface_by_id (thandler, EMP_IFACE_QUARK_TUBE_HANDLER);
175
176                 /* Give the tube to the handler */
177                 g_object_get (tube->public.channel,
178                               "connection", &connection,
179                               "object-path", &object_path,
180                               "handle_type", &handle_type,
181                               "handle", &handle,
182                               NULL);
183
184                 DEBUG ("Dispatching tube");
185                 emp_cli_tube_handler_call_handle_tube (thandler, -1,
186                                                        connection->bus_name,
187                                                        connection->object_path,
188                                                        object_path, handle_type,
189                                                        handle, tube->public.id,
190                                                        dispatcher_tubes_handle_tube_cb,
191                                                        empathy_dispatcher_tube_ref (user_data),
192                                                        (GDestroyNotify) empathy_dispatcher_tube_unref,
193                                                        G_OBJECT (dispatcher));
194
195                 g_object_unref (thandler);
196                 g_object_unref (connection);
197                 g_free (object_path);
198         }
199 }
200
201 static void
202 dispatcher_tubes_new_tube_cb (TpChannel   *channel,
203                               guint        id,
204                               guint        initiator,
205                               guint        type,
206                               const gchar *service,
207                               GHashTable  *parameters,
208                               guint        state,
209                               gpointer     user_data,
210                               GObject     *dispatcher)
211 {
212         static TpDBusDaemon   *daemon = NULL;
213         DispatcherTube        *tube;
214         McAccount             *account;
215         guint                  number;
216         gchar                **names;
217         gboolean               running = FALSE;
218         GError                *error = NULL;
219
220         /* Increase tube count */
221         number = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (channel), "tube-count"));
222         g_object_set_data (G_OBJECT (channel), "tube-count", GUINT_TO_POINTER (++number));
223         DEBUG ("Increased tube count for channel %p: %d", channel, number);
224
225         /* We dispatch only local pending tubes */
226         if (state != TP_TUBE_STATE_LOCAL_PENDING) {
227                 return;
228         }
229
230         if (!daemon) {
231                 daemon = tp_dbus_daemon_new (tp_get_bus ());
232         }
233
234         account = empathy_channel_get_account (channel);
235         tube = g_slice_new (DispatcherTube);
236         tube->ref_count = 1;
237         tube->handled = FALSE;
238         tube->factory = empathy_contact_factory_dup_singleton ();
239         tube->bus_name = empathy_tube_handler_build_bus_name (type, service);
240         tube->object_path = empathy_tube_handler_build_object_path (type, service);
241         tube->public.activatable = FALSE;
242         tube->public.id = id;
243         tube->public.channel = g_object_ref (channel);
244         tube->public.initiator = empathy_contact_factory_get_from_handle (tube->factory,
245                                                                           account,
246                                                                           initiator);
247         g_object_unref (account);
248
249   DEBUG ("Looking for tube handler: %s", tube->bus_name);
250         /* Check if that bus-name has an owner, if it has one that means the
251          * app is already running and we can directly give the channel. */
252         tp_cli_dbus_daemon_run_name_has_owner (daemon, -1, tube->bus_name,
253                                                &running, NULL, NULL);
254         if (running) {
255                 DEBUG ("Tube handler running");
256                 tube->public.activatable = TRUE;
257                 empathy_dispatcher_tube_process (EMPATHY_DISPATCHER (dispatcher),
258                                                  (EmpathyDispatcherTube*) tube);
259                 empathy_dispatcher_tube_unref ((EmpathyDispatcherTube*) tube);
260                 return;
261         }
262
263   DEBUG ("Tube handler is not running. Try to activate it");
264         /* Check if that bus-name is activatable, if not that means the
265          * application needed to handle this tube isn't installed. */
266         if (!tp_cli_dbus_daemon_run_list_activatable_names (daemon, -1,
267                                                             &names, &error,
268                                                             NULL)) {
269                 DEBUG ("Error listing activatable names: %s", error->message);
270                 g_clear_error (&error);
271         } else {
272                 gchar **name;
273
274                 for (name = names; *name; name++) {
275                         if (!tp_strdiff (*name, tube->bus_name)) {
276         DEBUG ("Found tube handler");
277                                 tube->public.activatable = TRUE;
278                                 break;
279                         }
280                 }
281                 g_strfreev (names);
282         }
283
284   if (!tube->public.activatable)
285     DEBUG ("Didn't find tube handler");
286
287         g_signal_emit (dispatcher, signals[FILTER_TUBE], 0, tube);
288         empathy_dispatcher_tube_unref ((EmpathyDispatcherTube*) tube);
289 }
290
291 static void
292 dispatcher_tubes_list_tubes_cb (TpChannel       *channel,
293                                 const GPtrArray *tubes,
294                                 const GError    *error,
295                                 gpointer         user_data,
296                                 GObject         *dispatcher)
297 {
298         guint i;
299
300         if (error) {
301                 DEBUG ("Error: %s", error->message);
302                 return;
303         }
304
305         for (i = 0; i < tubes->len; i++) {
306                 GValueArray *values;
307
308                 values = g_ptr_array_index (tubes, i);
309                 dispatcher_tubes_new_tube_cb (channel,
310                                               g_value_get_uint (g_value_array_get_nth (values, 0)),
311                                               g_value_get_uint (g_value_array_get_nth (values, 1)),
312                                               g_value_get_uint (g_value_array_get_nth (values, 2)),
313                                               g_value_get_string (g_value_array_get_nth (values, 3)),
314                                               g_value_get_boxed (g_value_array_get_nth (values, 4)),
315                                               g_value_get_uint (g_value_array_get_nth (values, 5)),
316                                               user_data, dispatcher);
317         }
318 }
319
320 static void
321 dispatcher_tubes_channel_invalidated_cb (TpProxy           *proxy,
322                                          guint              domain,
323                                          gint               code,
324                                          gchar             *message,
325                                          EmpathyDispatcher *dispatcher)
326 {
327         EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
328
329         DEBUG ("%s", message);
330
331         priv->tubes = g_slist_remove (priv->tubes, proxy);
332         g_object_unref (proxy);
333 }
334
335 static void
336 dispatcher_tubes_tube_closed_cb (TpChannel *channel,
337                                  guint      id,
338                                  gpointer   user_data,
339                                  GObject   *dispatcher)
340 {
341         guint number;
342
343         number = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (channel), "tube-count"));
344         if (number == 1) {
345                 DEBUG ("No more tube, closing channel");
346                 tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
347         }
348         else if (number > 1) {
349                 DEBUG ("Decrease tube count: %d", number);
350                 g_object_set_data (G_OBJECT (channel), "tube-count", GUINT_TO_POINTER (--number));
351         }
352 }
353
354 static void
355 dispatcher_tubes_handle_channel (EmpathyDispatcher *dispatcher,
356                                  TpChannel         *channel)
357 {
358         EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
359
360         DEBUG ("Called");
361
362         priv->tubes = g_slist_prepend (priv->tubes, g_object_ref (channel));
363         g_signal_connect (channel, "invalidated",
364                           G_CALLBACK (dispatcher_tubes_channel_invalidated_cb),
365                           dispatcher);
366
367         tp_cli_channel_type_tubes_connect_to_tube_closed (channel,
368                                                           dispatcher_tubes_tube_closed_cb,
369                                                           NULL, NULL,
370                                                           G_OBJECT (dispatcher), NULL);
371         tp_cli_channel_type_tubes_connect_to_new_tube (channel,
372                                                        dispatcher_tubes_new_tube_cb,
373                                                        NULL, NULL,
374                                                        G_OBJECT (dispatcher), NULL);
375         tp_cli_channel_type_tubes_call_list_tubes (channel, -1,
376                                                    dispatcher_tubes_list_tubes_cb,
377                                                    NULL, NULL,
378                                                    G_OBJECT (dispatcher));
379 }
380
381 static void
382 dispatcher_connection_invalidated_cb (TpConnection  *connection,
383                                       guint          domain,
384                                       gint           code,
385                                       gchar         *message,
386                                       EmpathyDispatcher *dispatcher)
387 {
388         EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
389         GHashTableIter         iter;
390         gpointer               key, value;
391
392         DEBUG ("Error: %s", message);
393
394         g_hash_table_iter_init (&iter, priv->connections);
395         while (g_hash_table_iter_next (&iter, &key, &value)) {
396                 if (value == connection) {
397                         g_hash_table_remove (priv->connections, key);
398                         break;
399                 }
400         }
401 }
402
403 typedef struct
404 {
405   EmpathyDispatcher *self;
406   EmpathyChatroom *chatroom;
407 } dispatcher_connection_invalidated_cb_ctx;
408
409 static dispatcher_connection_invalidated_cb_ctx *
410 dispatcher_connection_invalidated_cb_ctx_new (EmpathyDispatcher *dispatcher,
411                                               EmpathyChatroom *chatroom)
412 {
413   dispatcher_connection_invalidated_cb_ctx *ctx;
414
415   ctx = g_slice_new (dispatcher_connection_invalidated_cb_ctx);
416
417   ctx->self = g_object_ref (dispatcher);
418   ctx->chatroom = g_object_ref (chatroom);
419
420   return ctx;
421 }
422
423 static void
424 dispatcher_connection_invalidated_cb_ctx_free (
425     dispatcher_connection_invalidated_cb_ctx *ctx)
426 {
427   g_object_unref (ctx->self);
428   g_object_unref (ctx->chatroom);
429
430   g_slice_free (dispatcher_connection_invalidated_cb_ctx, ctx);
431 }
432
433 static void dispatcher_chatroom_invalidated_cb (
434     TpProxy *channel,
435     guint domain,
436     gint code,
437     gchar *message,
438     dispatcher_connection_invalidated_cb_ctx *ctx)
439 {
440   EmpathyDispatcherPriv *priv = GET_PRIV (ctx->self);
441   gboolean favorite;
442
443   g_object_get (ctx->chatroom, "favorite", &favorite, NULL);
444
445   if (favorite)
446     {
447       /* Chatroom is in favorites so don't remove it from the manager */
448       g_object_set (ctx->chatroom, "tp-channel", NULL, NULL);
449     }
450   else
451     {
452       empathy_chatroom_manager_remove (priv->chatroom_mgr, ctx->chatroom);
453     }
454 }
455
456 static void
457 dispatcher_connection_new_channel_cb (TpConnection *connection,
458                                       const gchar  *object_path,
459                                       const gchar  *channel_type,
460                                       guint         handle_type,
461                                       guint         handle,
462                                       gboolean      suppress_handler,
463                                       gpointer      user_data,
464                                       GObject      *object)
465 {
466         EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (object);
467   EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
468         TpChannel         *channel;
469         gpointer           had_channels;
470
471         had_channels = g_object_get_data (G_OBJECT (connection), "had-channels");
472         if (had_channels == NULL) {
473                 /* ListChannels didn't return yet, return to avoid duplicate
474                  * dispatching */
475                 return;
476         }
477
478         channel = tp_channel_new (connection, object_path, channel_type,
479                                   handle_type, handle, NULL);
480         tp_channel_run_until_ready (channel, NULL, NULL);
481
482         if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES)) {
483                 dispatcher_tubes_handle_channel (dispatcher, channel);
484         }
485
486   if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT) &&
487       handle_type == TP_HANDLE_TYPE_ROOM)
488     {
489       /* Add the chatroom to the chatroom manager */
490       EmpathyChatroom *chatroom;
491       GArray *handles;
492       gchar **room_ids;
493       MissionControl *mc;
494       McAccount *account;
495       dispatcher_connection_invalidated_cb_ctx *ctx;
496
497       handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
498       g_array_append_val (handles, handle);
499
500       tp_cli_connection_run_inspect_handles (connection, -1,
501           TP_HANDLE_TYPE_ROOM, handles, &room_ids, NULL, NULL);
502
503       mc = empathy_mission_control_new ();
504       account = mission_control_get_account_for_tpconnection (mc, connection,
505           NULL);
506
507       chatroom = empathy_chatroom_manager_find (priv->chatroom_mgr, account,
508           room_ids[0]);
509       if (chatroom == NULL)
510         {
511           chatroom = empathy_chatroom_new (account);
512           empathy_chatroom_set_name (chatroom, room_ids[0]);
513           empathy_chatroom_set_room (chatroom, room_ids[0]);
514           empathy_chatroom_manager_add (priv->chatroom_mgr, chatroom);
515         }
516       else
517         {
518           g_object_ref (chatroom);
519         }
520
521       g_object_set (chatroom, "tp-channel", channel, NULL);
522
523       ctx = dispatcher_connection_invalidated_cb_ctx_new (dispatcher, chatroom);
524
525       g_signal_connect_data (channel, "invalidated",
526           G_CALLBACK (dispatcher_chatroom_invalidated_cb), ctx,
527           (GClosureNotify) dispatcher_connection_invalidated_cb_ctx_free, 0);
528
529       g_free (room_ids[0]);
530       g_free (room_ids);
531       g_array_free (handles, TRUE);
532       g_object_unref (mc);
533       g_object_unref (account);
534       g_object_unref (chatroom);
535     }
536
537         if (suppress_handler) {
538                 g_signal_emit (dispatcher, signals[DISPATCH_CHANNEL], 0, channel);
539         } else {
540                 g_signal_emit (dispatcher, signals[FILTER_CHANNEL], 0, channel);
541         }
542
543         g_object_unref (channel);
544 }
545
546 static void
547 dispatcher_connection_list_channels_cb (TpConnection    *connection,
548                                         const GPtrArray *channels,
549                                         const GError    *error,
550                                         gpointer         user_data,
551                                         GObject         *dispatcher)
552 {
553         guint i;
554
555         if (error) {
556                 DEBUG ("Error: %s", error->message);
557                 return;
558         }
559
560         g_object_set_data (G_OBJECT (connection), "had-channels",
561                            GUINT_TO_POINTER (1));
562
563         for (i = 0; i < channels->len; i++) {
564                 GValueArray *values;
565
566                 values = g_ptr_array_index (channels, i);
567                 dispatcher_connection_new_channel_cb (connection,
568                         g_value_get_boxed (g_value_array_get_nth (values, 0)),
569                         g_value_get_string (g_value_array_get_nth (values, 1)),
570                         g_value_get_uint (g_value_array_get_nth (values, 2)),
571                         g_value_get_uint (g_value_array_get_nth (values, 3)),
572                         FALSE, user_data, dispatcher);
573         }
574 }
575
576 static void
577 dispatcher_connection_advertise_capabilities_cb (TpConnection    *connection,
578                                                  const GPtrArray *capabilities,
579                                                  const GError    *error,
580                                                  gpointer         user_data,
581                                                  GObject         *dispatcher)
582 {
583         if (error) {
584                 DEBUG ("Error: %s", error->message);
585         }
586 }
587
588 static void
589 dispatcher_connection_ready_cb (TpConnection  *connection,
590                                 const GError  *error,
591                                 gpointer       dispatcher)
592 {
593         GPtrArray   *capabilities;
594         GType        cap_type;
595         GValue       cap = {0, };
596         const gchar *remove = NULL;
597
598         if (error) {
599                 dispatcher_connection_invalidated_cb (connection,
600                                                       error->domain,
601                                                       error->code,
602                                                       error->message,
603                                                       dispatcher);
604                 return;
605         }
606
607         g_signal_connect (connection, "invalidated",
608                           G_CALLBACK (dispatcher_connection_invalidated_cb),
609                           dispatcher);
610         tp_cli_connection_connect_to_new_channel (connection,
611                                                   dispatcher_connection_new_channel_cb,
612                                                   NULL, NULL,
613                                                   G_OBJECT (dispatcher), NULL);
614         tp_cli_connection_call_list_channels (connection, -1,
615                                               dispatcher_connection_list_channels_cb,
616                                               NULL, NULL,
617                                               G_OBJECT (dispatcher));
618
619         /* Advertise VoIP capabilities */
620         capabilities = g_ptr_array_sized_new (1);
621         cap_type = dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING,
622                                            G_TYPE_UINT, G_TYPE_INVALID);
623         g_value_init (&cap, cap_type);
624         g_value_take_boxed (&cap, dbus_g_type_specialized_construct (cap_type));
625         dbus_g_type_struct_set (&cap,
626                                 0, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
627                                 1, TP_CHANNEL_MEDIA_CAPABILITY_AUDIO |
628                                    TP_CHANNEL_MEDIA_CAPABILITY_VIDEO |
629                                    TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_STUN  |
630                                    TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_GTALK_P2P,
631                                 G_MAXUINT);
632         g_ptr_array_add (capabilities, g_value_get_boxed (&cap));
633
634         tp_cli_connection_interface_capabilities_call_advertise_capabilities (
635                 connection, -1,
636                 capabilities, &remove,
637                 dispatcher_connection_advertise_capabilities_cb,
638                 NULL, NULL, G_OBJECT (dispatcher));
639         /* FIXME: Is that leaked? */
640 }
641
642 static void
643 dispatcher_update_account (EmpathyDispatcher *dispatcher,
644                            McAccount         *account)
645 {
646         EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
647         TpConnection          *connection;
648
649         connection = g_hash_table_lookup (priv->connections, account);
650         if (connection) {
651                 return;
652         }
653
654         connection = mission_control_get_tpconnection (priv->mc, account, NULL);
655         if (!connection) {
656                 return;
657         }
658
659         g_hash_table_insert (priv->connections, g_object_ref (account), connection);
660         tp_connection_call_when_ready (connection,
661                                        dispatcher_connection_ready_cb,
662                                        dispatcher);
663 }
664
665 static void
666 dispatcher_account_connection_cb (EmpathyAccountManager *manager,
667                                   McAccount *account,
668                                   TpConnectionStatusReason reason,
669                                   TpConnectionStatus status,
670                                   TpConnectionStatus previous,
671                                   EmpathyDispatcher *dispatcher)
672 {
673         dispatcher_update_account (dispatcher, account);
674 }
675
676 static void
677 dispatcher_finalize (GObject *object)
678 {
679         EmpathyDispatcherPriv *priv = GET_PRIV (object);
680         GSList                *l;
681
682         g_signal_handlers_disconnect_by_func (priv->account_manager,
683                                               dispatcher_account_connection_cb,
684                                               object);
685         g_object_unref (priv->account_manager);
686         g_object_unref (priv->mc);
687
688         for (l = priv->tubes; l; l = l->next) {
689                 g_signal_handlers_disconnect_by_func (l->data,
690                                                       dispatcher_tubes_channel_invalidated_cb,
691                                                       object);
692                 g_object_unref (l->data);
693         }
694         g_slist_free (priv->tubes);
695
696         g_hash_table_destroy (priv->connections);
697
698         g_object_unref (priv->chatroom_mgr);
699 }
700
701 static void
702 empathy_dispatcher_class_init (EmpathyDispatcherClass *klass)
703 {
704         GObjectClass *object_class = G_OBJECT_CLASS (klass);
705
706         object_class->finalize = dispatcher_finalize;
707
708         signals[DISPATCH_CHANNEL] =
709                 g_signal_new ("dispatch-channel",
710                               G_TYPE_FROM_CLASS (klass),
711                               G_SIGNAL_RUN_LAST,
712                               0,
713                               NULL, NULL,
714                               g_cclosure_marshal_VOID__OBJECT,
715                               G_TYPE_NONE,
716                               1, TP_TYPE_CHANNEL);
717         signals[FILTER_CHANNEL] =
718                 g_signal_new ("filter-channel",
719                               G_TYPE_FROM_CLASS (klass),
720                               G_SIGNAL_RUN_LAST,
721                               0,
722                               NULL, NULL,
723                               g_cclosure_marshal_VOID__OBJECT,
724                               G_TYPE_NONE,
725                               1, TP_TYPE_CHANNEL);
726         signals[FILTER_TUBE] =
727                 g_signal_new ("filter-tube",
728                               G_TYPE_FROM_CLASS (klass),
729                               G_SIGNAL_RUN_LAST,
730                               0,
731                               NULL, NULL,
732                               g_cclosure_marshal_VOID__BOXED,
733                               G_TYPE_NONE,
734                               1, EMPATHY_TYPE_DISPATCHER_TUBE);
735
736         g_type_class_add_private (object_class, sizeof (EmpathyDispatcherPriv));
737 }
738
739 static void
740 empathy_dispatcher_init (EmpathyDispatcher *dispatcher)
741 {
742         GList                 *accounts, *l;
743         EmpathyDispatcherPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (dispatcher,
744                 EMPATHY_TYPE_DISPATCHER, EmpathyDispatcherPriv);
745
746         dispatcher->priv = priv;
747         priv->mc = empathy_mission_control_new ();
748         priv->account_manager = empathy_account_manager_dup_singleton ();
749
750         g_signal_connect (priv->account_manager,
751                           "account-connection-changed",
752                           G_CALLBACK (dispatcher_account_connection_cb),
753                           dispatcher);
754
755         priv->connections = g_hash_table_new_full (empathy_account_hash,
756                                                    empathy_account_equal,
757                                                    g_object_unref,
758                                                    g_object_unref);
759         accounts = mc_accounts_list_by_enabled (TRUE);
760         for (l = accounts; l; l = l->next) {
761                 dispatcher_update_account (dispatcher, l->data);
762                 g_object_unref (l->data);
763         }
764         g_list_free (accounts);
765
766         priv->chatroom_mgr = empathy_chatroom_manager_new (NULL);
767 }
768
769 EmpathyDispatcher *
770 empathy_dispatcher_new (void)
771 {
772         if (!dispatcher) {
773                 dispatcher = g_object_new (EMPATHY_TYPE_DISPATCHER, NULL);
774                 g_object_add_weak_pointer (G_OBJECT (dispatcher), (gpointer) &dispatcher);
775         } else {
776                 g_object_ref (dispatcher);
777         }
778
779         return dispatcher;
780 }
781
782 typedef struct {
783         const gchar *channel_type;
784         guint        handle_type;
785         guint        handle;
786 } DispatcherRequestData;
787
788 static void
789 dispatcher_request_channel_cb (TpConnection *connection,
790                                const gchar  *object_path,
791                                const GError *error,
792                                gpointer      user_data,
793                                GObject      *weak_object)
794 {
795         DispatcherRequestData *data = (DispatcherRequestData*) user_data;
796
797         if (error) {
798                 DEBUG ("Error: %s", error->message);
799                 return;
800         }
801
802         if (dispatcher) {
803                 TpChannel *channel;
804
805                 channel = tp_channel_new (connection, object_path,
806                                           data->channel_type,
807                                           data->handle_type,
808                                           data->handle, NULL);
809
810                 g_signal_emit (dispatcher, signals[DISPATCH_CHANNEL], 0, channel);
811         }
812 }
813
814 void
815 empathy_dispatcher_call_with_contact (EmpathyContact *contact)
816 {
817         MissionControl        *mc;
818         McAccount             *account;
819         TpConnection          *connection;
820         gchar                 *object_path;
821         TpChannel             *channel;
822         EmpathyContactFactory *factory;
823         EmpathyTpGroup        *group;
824         EmpathyContact        *self_contact;
825         GError                *error = NULL;
826
827         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
828
829         mc = empathy_mission_control_new ();
830         account = empathy_contact_get_account (contact);
831         connection = mission_control_get_tpconnection (mc, account, NULL);
832         tp_connection_run_until_ready (connection, FALSE, NULL, NULL);
833         g_object_unref (mc);
834
835         /* We abuse of suppress_handler, TRUE means OUTGOING. The channel
836          * will be catched in EmpathyFilter */
837         if (!tp_cli_connection_run_request_channel (connection, -1,
838                                                     TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
839                                                     TP_HANDLE_TYPE_NONE,
840                                                     0,
841                                                     TRUE,
842                                                     &object_path,
843                                                     &error,
844                                                     NULL)) {
845                 DEBUG ("Couldn't request channel: %s",
846                         error ? error->message : "No error given");
847                 g_clear_error (&error);
848                 g_object_unref (connection);
849                 return;
850         }
851
852         channel = tp_channel_new (connection,
853                                   object_path, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
854                                   TP_HANDLE_TYPE_NONE, 0, NULL);
855
856         group = empathy_tp_group_new (channel);
857         empathy_run_until_ready (group);
858
859         factory = empathy_contact_factory_dup_singleton ();
860         self_contact = empathy_contact_factory_get_user (factory, account);
861         empathy_contact_run_until_ready (self_contact,
862                                          EMPATHY_CONTACT_READY_HANDLE,
863                                          NULL);
864
865         empathy_tp_group_add_member (group, contact, "");
866         empathy_tp_group_add_member (group, self_contact, "");  
867
868         g_object_unref (factory);
869         g_object_unref (self_contact);
870         g_object_unref (group);
871         g_object_unref (connection);
872         g_object_unref (channel);
873         g_free (object_path);
874 }
875
876 void
877 empathy_dispatcher_call_with_contact_id (McAccount *account, const gchar *contact_id)
878 {
879         EmpathyContactFactory *factory;
880         EmpathyContact        *contact;
881
882         factory = empathy_contact_factory_dup_singleton ();
883         contact = empathy_contact_factory_get_from_id (factory, account, contact_id);
884         empathy_contact_run_until_ready (contact, EMPATHY_CONTACT_READY_HANDLE, NULL);
885
886         empathy_dispatcher_call_with_contact (contact);
887
888         g_object_unref (contact);
889         g_object_unref (factory);
890 }
891
892 void
893 empathy_dispatcher_chat_with_contact (EmpathyContact  *contact)
894 {
895         MissionControl        *mc;
896         McAccount             *account;
897         TpConnection          *connection;
898         DispatcherRequestData *data;
899
900         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
901
902         mc = empathy_mission_control_new ();
903         account = empathy_contact_get_account (contact);
904         connection = mission_control_get_tpconnection (mc, account, NULL);
905         tp_connection_run_until_ready (connection, FALSE, NULL, NULL);
906         g_object_unref (mc);
907
908         /* We abuse of suppress_handler, TRUE means OUTGOING. */
909         data = g_new (DispatcherRequestData, 1);
910         data->channel_type = TP_IFACE_CHANNEL_TYPE_TEXT;
911         data->handle_type = TP_HANDLE_TYPE_CONTACT;
912         data->handle = empathy_contact_get_handle (contact);
913         tp_cli_connection_call_request_channel (connection, -1,
914                                                 data->channel_type,
915                                                 data->handle_type,
916                                                 data->handle,
917                                                 TRUE,
918                                                 dispatcher_request_channel_cb,
919                                                 data, g_free,
920                                                 NULL);
921         g_object_unref (connection);
922 }
923
924 void
925 empathy_dispatcher_chat_with_contact_id (McAccount   *account,
926                                          const gchar *contact_id)
927 {
928         EmpathyContactFactory *factory;
929         EmpathyContact        *contact;
930
931         factory = empathy_contact_factory_dup_singleton ();
932         contact = empathy_contact_factory_get_from_id (factory, account, contact_id);
933         empathy_contact_run_until_ready (contact, EMPATHY_CONTACT_READY_HANDLE, NULL);
934
935         empathy_dispatcher_chat_with_contact (contact);
936
937         g_object_unref (contact);
938         g_object_unref (factory);
939 }
940
941 typedef struct {
942         GFile *gfile;
943         TpHandle handle;
944 } FileChannelRequest;
945
946 static void
947 tp_file_state_notify_cb (EmpathyTpFile *tp_file)
948 {
949         EmpFileTransferState state;
950
951         state = empathy_tp_file_get_state (tp_file, NULL);
952         if (state == EMP_FILE_TRANSFER_STATE_COMPLETED ||
953             state == EMP_FILE_TRANSFER_STATE_CANCELLED) {
954                 DEBUG ("Transfer is done, unref the object");
955                 g_object_unref (tp_file);
956         }
957 }
958
959 static void
960 file_channel_create_cb (TpConnection *connection,
961                         const gchar  *object_path,
962                         GHashTable   *properties,
963                         const GError *error,
964                         gpointer      user_data,
965                         GObject      *weak_object)
966 {
967         TpChannel *channel;
968         EmpathyTpFile *tp_file;
969         FileChannelRequest *request = (FileChannelRequest *) user_data;
970
971         if (error) {
972                 DEBUG ("Couldn't request channel: %s", error->message);
973                 return;
974         }
975
976         channel = tp_channel_new (connection,
977                                  object_path,
978                                  EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
979                                  TP_HANDLE_TYPE_CONTACT,
980                                  request->handle,
981                                  NULL);
982
983         /* We give the ref to the callback, it is responsible to unref the
984          * object once the transfer is done. */
985         tp_file = empathy_tp_file_new (channel);
986         empathy_tp_file_offer (tp_file, request->gfile, NULL);
987         g_signal_connect (tp_file, "notify::state",
988                           G_CALLBACK (tp_file_state_notify_cb),
989                           NULL);
990
991         g_object_unref (request->gfile);
992         g_slice_free (FileChannelRequest, request);
993         g_object_unref (channel);
994 }
995
996 void
997 empathy_dispatcher_send_file (EmpathyContact *contact,
998                               GFile          *gfile)
999 {
1000         MissionControl     *mc;
1001         McAccount          *account;
1002         TpConnection       *connection;
1003         guint               handle;
1004         FileChannelRequest *request;
1005         GHashTable         *args;
1006         GValue             *value;
1007         GFileInfo          *info;
1008         gchar              *filename;
1009         GTimeVal            last_modif;
1010         GError             *error = NULL;
1011
1012         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1013         g_return_if_fail (G_IS_FILE (gfile));
1014
1015         info = g_file_query_info (gfile,
1016                                   G_FILE_ATTRIBUTE_STANDARD_SIZE ","
1017                                   G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
1018                                   G_FILE_ATTRIBUTE_TIME_MODIFIED,
1019                                   0, NULL, &error);
1020
1021         if (error) {
1022                 DEBUG ("Can't get info about the file: %s", error->message);
1023                 g_clear_error (&error);
1024                 return;
1025         }
1026
1027         mc = empathy_mission_control_new ();
1028         account = empathy_contact_get_account (contact);
1029         connection = mission_control_get_tpconnection (mc, account, NULL);
1030         handle = empathy_contact_get_handle (contact);
1031
1032         request = g_slice_new0 (FileChannelRequest);
1033         request->gfile = g_object_ref (gfile);
1034         request->handle = handle;
1035
1036         filename = g_file_get_basename (request->gfile);
1037         tp_connection_run_until_ready (connection, FALSE, NULL, NULL);
1038
1039         DEBUG ("Sending %s from a stream to %s (size %"G_GINT64_FORMAT", content-type %s)",
1040                filename, empathy_contact_get_name (contact),
1041                g_file_info_get_size (info),
1042                g_file_info_get_content_type (info));
1043
1044         args = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
1045                                       (GDestroyNotify) tp_g_value_slice_free);
1046
1047         /* org.freedesktop.Telepathy.Channel.ChannelType */
1048         value = tp_g_value_slice_new (G_TYPE_STRING);
1049         g_value_set_string (value, EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
1050         g_hash_table_insert (args, TP_IFACE_CHANNEL ".ChannelType", value);
1051
1052         /* org.freedesktop.Telepathy.Channel.TargetHandleType */
1053         value = tp_g_value_slice_new (G_TYPE_UINT);
1054         g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
1055         g_hash_table_insert (args, TP_IFACE_CHANNEL ".TargetHandleType", value);
1056
1057         /* org.freedesktop.Telepathy.Channel.TargetHandle */
1058         value = tp_g_value_slice_new (G_TYPE_UINT);
1059         g_value_set_uint (value, handle);
1060         g_hash_table_insert (args, TP_IFACE_CHANNEL ".TargetHandle", value);
1061
1062         /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentType */
1063         value = tp_g_value_slice_new (G_TYPE_STRING);
1064         g_value_set_string (value, g_file_info_get_content_type (info));
1065         g_hash_table_insert (args,
1066                 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentType", value);
1067
1068         /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Filename */
1069         value = tp_g_value_slice_new (G_TYPE_STRING);
1070         g_value_set_string (value, g_filename_display_basename (filename));
1071         g_hash_table_insert (args,
1072                 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Filename", value);
1073
1074         /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Size */
1075         value = tp_g_value_slice_new (G_TYPE_UINT64);
1076         g_value_set_uint64 (value, g_file_info_get_size (info));
1077         g_hash_table_insert (args,
1078                 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Size", value);
1079
1080         /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Date */
1081         g_file_info_get_modification_time (info, &last_modif);
1082         value = tp_g_value_slice_new (G_TYPE_UINT64);
1083         g_value_set_uint64 (value, last_modif.tv_sec);
1084         g_hash_table_insert (args,
1085                 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Date", value);
1086
1087         /* FIXME: Description ? */
1088         /* FIXME: ContentHashType and ContentHash ? */
1089
1090         tp_cli_connection_interface_requests_call_create_channel (connection, -1,
1091                 args, file_channel_create_cb, request, NULL, NULL);
1092
1093         g_hash_table_destroy (args);
1094         g_free (filename);
1095         g_object_unref (mc);
1096         g_object_unref (connection);
1097 }
1098