]> git.0d.be Git - empathy.git/blob - src/empathy-chat-manager.c
chat-mgr: Use GDBus client side API
[empathy.git] / src / empathy-chat-manager.c
1 /*
2  * empathy-chat-manager.c - Source for EmpathyChatManager
3  * Copyright (C) 2010 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
20 #include "config.h"
21 #include "empathy-chat-manager.h"
22
23 #include <telepathy-glib/proxy-subclass.h>
24 #include <telepathy-glib/telepathy-glib-dbus.h>
25
26 #include "empathy-chatroom-manager.h"
27 #include "empathy-chat-window.h"
28 #include "empathy-request-util.h"
29 #include "empathy-ui-utils.h"
30 #include "extensions.h"
31 #include "chat-manager-interface.h"
32
33 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
34 #include "empathy-debug.h"
35
36 #define CHAT_MANAGER_PATH "/org/gnome/Empathy/ChatManager"
37
38 enum {
39   CLOSED_CHATS_CHANGED,
40   DISPLAYED_CHATS_CHANGED,
41   LAST_SIGNAL
42 };
43
44 static guint signals[LAST_SIGNAL];
45
46 static void svc_iface_init (gpointer, gpointer);
47
48 G_DEFINE_TYPE_WITH_CODE (EmpathyChatManager, empathy_chat_manager,
49     G_TYPE_OBJECT,
50     G_IMPLEMENT_INTERFACE (EMP_TYPE_SVC_CHAT_MANAGER,
51         svc_iface_init)
52     )
53
54 /* private structure */
55 typedef struct _EmpathyChatManagerPriv EmpathyChatManagerPriv;
56
57 struct _EmpathyChatManagerPriv
58 {
59   EmpathyChatroomManager *chatroom_mgr;
60   /* Queue of (ChatData *) representing the closed chats */
61   GQueue *closed_queue;
62
63   guint num_displayed_chat;
64
65   /* account path -> (GHashTable<(owned gchar *) contact ID
66    *                  -> (owned gchar *) non-NULL message>)
67    */
68   GHashTable *messages;
69
70   TpBaseClient *handler;
71
72   /* Cached to keep Folks in memory while empathy-chat is running; we don't
73    * want to reload it each time the last chat window is closed
74    * and re-opened. */
75   EmpathyIndividualManager *individual_mgr;
76 };
77
78 #define GET_PRIV(o) \
79   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CHAT_MANAGER, \
80     EmpathyChatManagerPriv))
81
82 static EmpathyChatManager *chat_manager_singleton = NULL;
83
84 typedef struct
85 {
86   TpAccount *account;
87   gchar *id;
88   gboolean room;
89   gboolean sms;
90 } ChatData;
91
92 static ChatData *
93 chat_data_new (EmpathyChat *chat)
94 {
95   ChatData *data = NULL;
96
97   data = g_slice_new0 (ChatData);
98
99   data->account = g_object_ref (empathy_chat_get_account (chat));
100   data->id = g_strdup (empathy_chat_get_id (chat));
101   data->room = empathy_chat_is_room (chat);
102   data->sms = empathy_chat_is_sms_channel (chat);
103
104   return data;
105 }
106
107 static void
108 chat_data_free (ChatData *data)
109 {
110   if (data->account != NULL)
111     {
112       g_object_unref (data->account);
113       data->account = NULL;
114     }
115
116   if (data->id != NULL)
117     {
118       g_free (data->id);
119       data->id = NULL;
120     }
121
122   g_slice_free (ChatData, data);
123 }
124
125 static void
126 chat_destroyed_cb (gpointer data,
127     GObject *object)
128 {
129   EmpathyChatManager *self = data;
130   EmpathyChatManagerPriv *priv = GET_PRIV (self);
131
132   priv->num_displayed_chat--;
133
134   DEBUG ("Chat destroyed; we are now displaying %u chats",
135       priv->num_displayed_chat);
136
137   g_signal_emit (self, signals[DISPLAYED_CHATS_CHANGED], 0,
138       priv->num_displayed_chat);
139 }
140
141 static void
142 join_cb (GObject *source,
143     GAsyncResult *result,
144     gpointer user_data)
145 {
146   TpChannel *channel = TP_CHANNEL (source);
147   GError *error = NULL;
148
149   if (!tp_channel_join_finish (channel, result, &error))
150     {
151       DEBUG ("Failed to join chat (%s): %s",
152           tp_channel_get_identifier (channel), error->message);
153       g_error_free (error);
154     }
155 }
156
157 static void
158 individual_mgr_cb (EmpathyChatWindow *window,
159     GParamSpec *spec,
160     EmpathyChatManager *self)
161 {
162   EmpathyChatManagerPriv *priv = GET_PRIV (self);
163
164   if (priv->individual_mgr != NULL)
165     return;
166
167   priv->individual_mgr = empathy_chat_window_get_individual_manager (window);
168   g_object_ref (priv->individual_mgr);
169 }
170
171 static void
172 process_tp_chat (EmpathyChatManager *self,
173     EmpathyTpChat *tp_chat,
174     TpAccount *account,
175     gint64 user_action_time)
176 {
177   EmpathyChatManagerPriv *priv = GET_PRIV (self);
178   EmpathyChat *chat = NULL;
179   const gchar *id;
180   EmpathyChatWindow *window;
181
182   id = empathy_tp_chat_get_id (tp_chat);
183   if (!tp_str_empty (id))
184     {
185       chat = empathy_chat_window_find_chat (account, id,
186           tp_text_channel_is_sms_channel ((TpTextChannel *) tp_chat));
187     }
188
189   if (chat != NULL)
190     {
191       empathy_chat_set_tp_chat (chat, tp_chat);
192     }
193   else
194     {
195       GHashTable *chats = NULL;
196
197       chat = empathy_chat_new (tp_chat);
198       /* empathy_chat_new returns a floating reference as EmpathyChat is
199        * a GtkWidget. This reference will be taken by a container
200        * (a GtkNotebook) when we'll call empathy_chat_window_present_chat */
201
202       priv->num_displayed_chat++;
203
204       DEBUG ("Chat displayed; we are now displaying %u chat",
205           priv->num_displayed_chat);
206
207       g_signal_emit (self, signals[DISPLAYED_CHATS_CHANGED], 0,
208           priv->num_displayed_chat);
209
210       /* Set the saved message in the channel if we have one. */
211       chats = g_hash_table_lookup (priv->messages,
212           tp_proxy_get_object_path (account));
213
214       if (chats != NULL)
215         {
216           const gchar *msg = g_hash_table_lookup (chats, id);
217
218           if (msg != NULL)
219             empathy_chat_set_text (chat, msg);
220         }
221
222       g_object_weak_ref ((GObject *) chat, chat_destroyed_cb, self);
223     }
224
225   window = empathy_chat_window_present_chat (chat, user_action_time);
226
227   if (priv->individual_mgr == NULL)
228     {
229       /* We want to cache it as soon it's created */
230       tp_g_signal_connect_object (window, "notify::individual-manager",
231         G_CALLBACK (individual_mgr_cb), self, 0);
232     }
233
234   if (empathy_tp_chat_is_invited (tp_chat, NULL))
235     {
236       /* We have been invited to the room. Add ourself as member as this
237        * channel has been approved. */
238       tp_channel_join_async (TP_CHANNEL (tp_chat), "", join_cb, self);
239     }
240 }
241
242 static void
243 handle_channels (TpSimpleHandler *handler,
244     TpAccount *account,
245     TpConnection *connection,
246     GList *channels,
247     GList *requests_satisfied,
248     gint64 user_action_time,
249     TpHandleChannelsContext *context,
250     gpointer user_data)
251 {
252   EmpathyChatManager *self = (EmpathyChatManager *) user_data;
253   GList *l;
254
255   for (l = channels; l != NULL; l = g_list_next (l))
256     {
257       EmpathyTpChat *tp_chat = l->data;
258
259       if (tp_proxy_get_invalidated (tp_chat) != NULL)
260         continue;
261
262       if (!EMPATHY_IS_TP_CHAT (tp_chat))
263         {
264           DEBUG ("Channel %s doesn't implement Messages; can't handle it",
265               tp_proxy_get_object_path (tp_chat));
266           continue;
267         }
268
269       DEBUG ("Now handling channel %s", tp_proxy_get_object_path (tp_chat));
270
271       process_tp_chat (self, tp_chat, account, user_action_time);
272     }
273
274   tp_handle_channels_context_accept (context);
275 }
276
277 static void
278 empathy_chat_manager_init (EmpathyChatManager *self)
279 {
280   EmpathyChatManagerPriv *priv = GET_PRIV (self);
281   TpAccountManager *am;
282   GError *error = NULL;
283
284   priv->closed_queue = g_queue_new ();
285   priv->messages = g_hash_table_new_full (g_str_hash, g_str_equal,
286       g_free, (GDestroyNotify) g_hash_table_unref);
287
288   am = tp_account_manager_dup ();
289
290   priv->chatroom_mgr = empathy_chatroom_manager_dup_singleton (NULL);
291
292   /* Text channels handler */
293   priv->handler = tp_simple_handler_new_with_am (am, FALSE, FALSE,
294       EMPATHY_CHAT_TP_BUS_NAME_SUFFIX, FALSE, handle_channels, self, NULL);
295
296   g_object_unref (am);
297
298   tp_base_client_take_handler_filter (priv->handler, tp_asv_new (
299         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
300         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
301         NULL));
302
303   tp_base_client_take_handler_filter (priv->handler, tp_asv_new (
304         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
305         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_ROOM,
306         NULL));
307
308   tp_base_client_take_handler_filter (priv->handler, tp_asv_new (
309         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
310         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_NONE,
311         NULL));
312
313   if (!tp_base_client_register (priv->handler, &error))
314     {
315       g_critical ("Failed to register text handler: %s", error->message);
316       g_error_free (error);
317     }
318 }
319
320 static void
321 empathy_chat_manager_finalize (GObject *object)
322 {
323   EmpathyChatManager *self = EMPATHY_CHAT_MANAGER (object);
324   EmpathyChatManagerPriv *priv = GET_PRIV (self);
325
326   if (priv->closed_queue != NULL)
327     {
328       g_queue_foreach (priv->closed_queue, (GFunc) chat_data_free, NULL);
329       g_queue_free (priv->closed_queue);
330       priv->closed_queue = NULL;
331     }
332
333   tp_clear_pointer (&priv->messages, g_hash_table_unref);
334
335   tp_clear_object (&priv->handler);
336   tp_clear_object (&priv->chatroom_mgr);
337   tp_clear_object (&priv->individual_mgr);
338
339   G_OBJECT_CLASS (empathy_chat_manager_parent_class)->finalize (object);
340 }
341
342 static GObject *
343 empathy_chat_manager_constructor (GType type,
344     guint n_construct_params,
345     GObjectConstructParam *construct_params)
346 {
347   GObject *retval;
348
349   if (!chat_manager_singleton)
350     {
351       retval = G_OBJECT_CLASS (empathy_chat_manager_parent_class)->constructor
352         (type, n_construct_params, construct_params);
353
354       chat_manager_singleton = EMPATHY_CHAT_MANAGER (retval);
355       g_object_add_weak_pointer (retval, (gpointer) &chat_manager_singleton);
356     }
357   else
358     {
359       retval = g_object_ref (chat_manager_singleton);
360     }
361
362   return retval;
363 }
364
365 static void
366 empathy_chat_manager_constructed (GObject *obj)
367 {
368   TpDBusDaemon *dbus_daemon;
369
370   dbus_daemon = tp_dbus_daemon_dup (NULL);
371
372   if (dbus_daemon != NULL)
373     {
374       tp_dbus_daemon_register_object (dbus_daemon,
375           CHAT_MANAGER_PATH, obj);
376
377       g_object_unref (dbus_daemon);
378     }
379 }
380
381 static void
382 empathy_chat_manager_class_init (
383   EmpathyChatManagerClass *empathy_chat_manager_class)
384 {
385   GObjectClass *object_class = G_OBJECT_CLASS (empathy_chat_manager_class);
386
387   object_class->finalize = empathy_chat_manager_finalize;
388   object_class->constructor = empathy_chat_manager_constructor;
389   object_class->constructed = empathy_chat_manager_constructed;
390
391   signals[CLOSED_CHATS_CHANGED] =
392     g_signal_new ("closed-chats-changed",
393         G_TYPE_FROM_CLASS (object_class),
394         G_SIGNAL_RUN_LAST,
395         0,
396         NULL, NULL,
397         g_cclosure_marshal_generic,
398         G_TYPE_NONE,
399         1, G_TYPE_UINT, NULL);
400
401   signals[DISPLAYED_CHATS_CHANGED] =
402     g_signal_new ("displayed-chats-changed",
403         G_TYPE_FROM_CLASS (object_class),
404         G_SIGNAL_RUN_LAST,
405         0,
406         NULL, NULL,
407         g_cclosure_marshal_generic,
408         G_TYPE_NONE,
409         1, G_TYPE_UINT, NULL);
410
411   g_type_class_add_private (empathy_chat_manager_class,
412     sizeof (EmpathyChatManagerPriv));
413 }
414
415 EmpathyChatManager *
416 empathy_chat_manager_dup_singleton (void)
417 {
418   return g_object_new (EMPATHY_TYPE_CHAT_MANAGER, NULL);
419 }
420
421 void
422 empathy_chat_manager_closed_chat (EmpathyChatManager *self,
423     EmpathyChat *chat)
424 {
425   EmpathyChatManagerPriv *priv = GET_PRIV (self);
426   ChatData *data;
427   GHashTable *chats;
428   gchar *message;
429
430   data = chat_data_new (chat);
431
432   DEBUG ("Adding %s to closed queue: %s",
433       data->room ? "room" : "contact", data->id);
434
435   g_queue_push_tail (priv->closed_queue, data);
436
437   g_signal_emit (self, signals[CLOSED_CHATS_CHANGED], 0,
438       g_queue_get_length (priv->closed_queue));
439
440   /* If there was a message saved from last time it was closed
441    * (perhaps by accident?) save it to our hash table so it can be
442    * used again when the same chat pops up. Hot. */
443   message = empathy_chat_dup_text (chat);
444
445   chats = g_hash_table_lookup (priv->messages,
446       tp_proxy_get_object_path (data->account));
447
448   /* Don't create a new hash table if we don't already have one and we
449    * don't actually have a message to save. */
450   if (chats == NULL && tp_str_empty (message))
451     {
452       g_free (message);
453       return;
454     }
455   else if (chats == NULL && !tp_str_empty (message))
456     {
457       chats = g_hash_table_new_full (g_str_hash, g_str_equal,
458           g_free, g_free);
459
460       g_hash_table_insert (priv->messages,
461           g_strdup (tp_proxy_get_object_path (data->account)),
462           chats);
463     }
464
465   if (tp_str_empty (message))
466     {
467       g_hash_table_remove (chats, data->id);
468       /* might be '\0' */
469       g_free (message);
470     }
471   else
472     {
473       /* takes ownership of message */
474       g_hash_table_insert (chats, g_strdup (data->id), message);
475     }
476 }
477
478 void
479 empathy_chat_manager_undo_closed_chat (EmpathyChatManager *self,
480     gint64 timestamp)
481 {
482   EmpathyChatManagerPriv *priv = GET_PRIV (self);
483   ChatData *data;
484
485   data = g_queue_pop_tail (priv->closed_queue);
486
487   if (data == NULL)
488     return;
489
490   DEBUG ("Removing %s from closed queue and starting a chat with: %s",
491       data->room ? "room" : "contact", data->id);
492
493   if (data->room)
494     empathy_join_muc (data->account, data->id, timestamp);
495   else if (data->sms)
496     empathy_sms_contact_id (data->account, data->id, timestamp,
497         NULL, NULL);
498   else
499     empathy_chat_with_contact_id (data->account, data->id, timestamp,
500         NULL, NULL);
501
502   g_signal_emit (self, signals[CLOSED_CHATS_CHANGED], 0,
503       g_queue_get_length (priv->closed_queue));
504
505   chat_data_free (data);
506 }
507
508 guint
509 empathy_chat_manager_get_num_closed_chats (EmpathyChatManager *self)
510 {
511   EmpathyChatManagerPriv *priv = GET_PRIV (self);
512
513   return g_queue_get_length (priv->closed_queue);
514 }
515
516 static void
517 empathy_chat_manager_dbus_undo_closed_chat (EmpSvcChatManager *manager,
518     gint64 timestamp,
519     DBusGMethodInvocation *context)
520 {
521   empathy_chat_manager_undo_closed_chat ((EmpathyChatManager *) manager,
522       timestamp);
523
524   emp_svc_chat_manager_return_from_undo_closed_chat (context);
525 }
526
527 static void
528 svc_iface_init (gpointer g_iface,
529     gpointer iface_data)
530 {
531   EmpSvcChatManagerClass *klass = (EmpSvcChatManagerClass *) g_iface;
532
533 #define IMPLEMENT(x) emp_svc_chat_manager_implement_##x (\
534     klass, empathy_chat_manager_dbus_##x)
535   IMPLEMENT(undo_closed_chat);
536 #undef IMPLEMENT
537 }
538
539 static void
540 undo_closed_cb (GObject *source,
541     GAsyncResult *result,
542     gpointer user_data)
543 {
544   GError *error = NULL;
545
546   if (!empathy_gen_chat_manager_call_undo_closed_chat_finish (
547         EMPATHY_GEN_CHAT_MANAGER (source), result,
548         &error))
549     {
550       DEBUG ("UndoClosedChat failed: %s", error->message);
551       g_error_free (error);
552     }
553 }
554
555 static void
556 chat_mgr_proxy_cb (GObject *source,
557     GAsyncResult *result,
558     gpointer user_data)
559 {
560   EmpathyGenChatManager *proxy;
561   GError *error = NULL;
562   GVariant *action_time = user_data;
563
564   proxy = empathy_gen_chat_manager_proxy_new_for_bus_finish (result, &error);
565   if (proxy == NULL)
566     {
567       DEBUG ("Failed to create ChatManager proxy: %s", error->message);
568       g_error_free (error);
569       goto finally;
570     }
571
572   empathy_gen_chat_manager_call_undo_closed_chat (proxy,
573       g_variant_get_int64 (action_time), NULL, undo_closed_cb, NULL);
574
575   g_object_unref (proxy);
576
577 finally:
578   g_variant_unref (action_time);
579 }
580
581 void
582 empathy_chat_manager_call_undo_closed_chat (void)
583 {
584   gint64 action_time;
585
586   action_time = empathy_get_current_action_time ();
587
588   empathy_gen_chat_manager_proxy_new_for_bus (G_BUS_TYPE_SESSION,
589       G_DBUS_PROXY_FLAGS_NONE, EMPATHY_CHAT_TP_BUS_NAME, CHAT_MANAGER_PATH,
590       NULL, chat_mgr_proxy_cb,
591       /* We can't use GINT_TO_POINTER as we won't be able to store a 64 bits
592        * integer on a 32 bits plateform. Use a GVariant for its convenient
593        * API. */
594       g_variant_new_int64 (action_time));
595 }