]> git.0d.be Git - empathy.git/blob - src/empathy-chat-manager.c
chat-manager: keep a ref on the chatroom mgr
[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 <telepathy-glib/telepathy-glib.h>
21
22 #include <libempathy/empathy-chatroom-manager.h>
23 #include <libempathy/empathy-dispatcher.h>
24 #include <libempathy/empathy-utils.h>
25
26 #include "empathy-chat-window.h"
27
28 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
29 #include <libempathy/empathy-debug.h>
30
31 #include "empathy-chat-manager.h"
32
33 enum {
34   CLOSED_CHATS_CHANGED,
35   HANDLED_CHATS_CHANGED,
36   LAST_SIGNAL
37 };
38
39 static guint signals[LAST_SIGNAL];
40
41 G_DEFINE_TYPE(EmpathyChatManager, empathy_chat_manager, G_TYPE_OBJECT)
42
43 /* private structure */
44 typedef struct _EmpathyChatManagerPriv EmpathyChatManagerPriv;
45
46 struct _EmpathyChatManagerPriv
47 {
48   EmpathyChatroomManager *chatroom_mgr;
49   /* Queue of (ChatData *) representing the closed chats */
50   GQueue *closed_queue;
51
52   guint num_handled_channels;
53
54   TpBaseClient *handler;
55 };
56
57 #define GET_PRIV(o) \
58   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CHAT_MANAGER, \
59     EmpathyChatManagerPriv))
60
61 static EmpathyChatManager *chat_manager_singleton = NULL;
62
63 typedef struct
64 {
65   TpAccount *account;
66   gchar *id;
67   gboolean room;
68 } ChatData;
69
70 static ChatData *
71 chat_data_new (EmpathyChat *chat)
72 {
73   ChatData *data = NULL;
74
75   data = g_slice_new0 (ChatData);
76
77   data->account = g_object_ref (empathy_chat_get_account (chat));
78   data->id = g_strdup (empathy_chat_get_id (chat));
79   data->room = empathy_chat_is_room (chat);
80
81   return data;
82 }
83
84 static void
85 chat_data_free (ChatData *data)
86 {
87   if (data->account != NULL)
88     {
89       g_object_unref (data->account);
90       data->account = NULL;
91     }
92
93   if (data->id != NULL)
94     {
95       g_free (data->id);
96       data->id = NULL;
97     }
98
99   g_slice_free (ChatData, data);
100 }
101
102 static void
103 tell_chatroom_manager_if_needed (EmpathyChatManager *self,
104     TpAccount *account,
105     EmpathyTpChat *chat)
106 {
107   EmpathyChatManagerPriv *priv = GET_PRIV (self);
108   TpHandleType type;
109
110   tp_channel_get_handle (empathy_tp_chat_get_channel (chat), &type);
111
112   if (type == TP_HANDLE_TYPE_ROOM)
113     {
114       empathy_chatroom_manager_chat_handled (priv->chatroom_mgr, chat, account);
115     }
116 }
117
118 static void
119 process_tp_chat (EmpathyChatManager *self,
120     EmpathyTpChat *tp_chat,
121     TpAccount *account,
122     gint64 user_action_time)
123 {
124   EmpathyChat *chat = NULL;
125   const gchar *id;
126
127   tell_chatroom_manager_if_needed (self, account, tp_chat);
128
129   id = empathy_tp_chat_get_id (tp_chat);
130   if (!tp_str_empty (id))
131     {
132       chat = empathy_chat_window_find_chat (account, id);
133     }
134
135   if (chat != NULL)
136     {
137       empathy_chat_set_tp_chat (chat, tp_chat);
138     }
139   else
140     {
141       chat = empathy_chat_new (tp_chat);
142       /* empathy_chat_new returns a floating reference as EmpathyChat is
143        * a GtkWidget. This reference will be taken by a container
144        * (a GtkNotebook) when we'll call empathy_chat_window_present_chat */
145     }
146   empathy_chat_window_present_chat (chat, user_action_time);
147
148   if (empathy_tp_chat_is_invited (tp_chat, NULL))
149     {
150       /* We have been invited to the room. Add ourself as member as this
151        * channel has been approved. */
152       empathy_tp_chat_join (tp_chat);
153     }
154
155   g_object_unref (tp_chat);
156 }
157
158 typedef struct
159 {
160   EmpathyChatManager *self;
161   EmpathyTpChat *tp_chat;
162   TpAccount *account;
163   gint64 user_action_time;
164   gulong sig_id;
165 } chat_ready_ctx;
166
167 static chat_ready_ctx *
168 chat_ready_ctx_new (EmpathyChatManager *self,
169     EmpathyTpChat *tp_chat,
170     TpAccount *account,
171     gint64 user_action_time)
172 {
173   chat_ready_ctx *ctx = g_slice_new0 (chat_ready_ctx);
174
175   ctx->self = g_object_ref (self);
176   ctx->tp_chat = g_object_ref (tp_chat);
177   ctx->account = g_object_ref (account);
178   ctx->user_action_time = user_action_time;
179   return ctx;
180 }
181
182 static void
183 chat_ready_ctx_free (chat_ready_ctx *ctx)
184 {
185   g_object_unref (ctx->self);
186   g_object_unref (ctx->tp_chat);
187   g_object_unref (ctx->account);
188
189   if (ctx->sig_id != 0)
190     g_signal_handler_disconnect (ctx->tp_chat, ctx->sig_id);
191
192   g_slice_free (chat_ready_ctx, ctx);
193 }
194
195 static void
196 tp_chat_ready_cb (GObject *object,
197   GParamSpec *spec,
198   gpointer user_data)
199 {
200   EmpathyTpChat *tp_chat = EMPATHY_TP_CHAT (object);
201   chat_ready_ctx *ctx = user_data;
202
203   if (!empathy_tp_chat_is_ready (tp_chat))
204     return;
205
206   process_tp_chat (ctx->self, tp_chat, ctx->account, ctx->user_action_time);
207
208   chat_ready_ctx_free (ctx);
209 }
210
211 static void
212 channel_invalidated (TpChannel *channel,
213     guint domain,
214     gint code,
215     gchar *message,
216     EmpathyChatManager *self)
217 {
218   EmpathyChatManagerPriv *priv = GET_PRIV (self);
219
220   priv->num_handled_channels--;
221
222   DEBUG ("Channel closed; we are now handling %u text channels",
223       priv->num_handled_channels);
224
225   g_signal_emit (self, signals[HANDLED_CHATS_CHANGED], 0,
226       priv->num_handled_channels);
227 }
228
229 static void
230 handle_channels (TpSimpleHandler *handler,
231     TpAccount *account,
232     TpConnection *connection,
233     GList *channels,
234     GList *requests_satisfied,
235     gint64 user_action_time,
236     TpHandleChannelsContext *context,
237     gpointer user_data)
238 {
239   EmpathyChatManager *self = (EmpathyChatManager *) user_data;
240   EmpathyChatManagerPriv *priv = GET_PRIV (self);
241   GList *l;
242   gboolean handling = FALSE;
243
244   for (l = channels; l != NULL; l = g_list_next (l))
245     {
246       TpChannel *channel = l->data;
247       EmpathyTpChat *tp_chat;
248
249       if (tp_proxy_get_invalidated (channel) != NULL)
250         continue;
251
252       handling = TRUE;
253
254       tp_chat = empathy_tp_chat_new (account, channel);
255
256       if (empathy_tp_chat_is_ready (tp_chat))
257         {
258           process_tp_chat (self, tp_chat, account, user_action_time);
259         }
260       else
261         {
262           chat_ready_ctx *ctx = chat_ready_ctx_new (self, tp_chat, account,
263               user_action_time);
264
265           ctx->sig_id = g_signal_connect (tp_chat, "notify::ready",
266               G_CALLBACK (tp_chat_ready_cb), ctx);
267         }
268
269       priv->num_handled_channels++;
270
271       g_signal_connect (channel, "invalidated",
272           G_CALLBACK (channel_invalidated), self);
273     }
274
275   tp_handle_channels_context_accept (context);
276
277   if (handling)
278     {
279       DEBUG ("Channels handled; we are now handling %u text channels",
280           priv->num_handled_channels);
281
282       g_signal_emit (self, signals[HANDLED_CHATS_CHANGED], 0,
283           priv->num_handled_channels);
284     }
285 }
286
287 static void
288 empathy_chat_manager_init (EmpathyChatManager *self)
289 {
290   EmpathyChatManagerPriv *priv = GET_PRIV (self);
291   TpDBusDaemon *dbus;
292   GError *error = NULL;
293
294   priv->closed_queue = g_queue_new ();
295
296   dbus = tp_dbus_daemon_dup (&error);
297   if (dbus == NULL)
298     {
299       g_critical ("Failed to get D-Bus daemon: %s", error->message);
300       g_error_free (error);
301       return;
302     }
303
304   priv->chatroom_mgr = empathy_chatroom_manager_dup_singleton (NULL);
305
306   /* Text channels handler */
307   priv->handler = tp_simple_handler_new (dbus, FALSE, FALSE, "Empathy.Chat",
308       FALSE, handle_channels, self, NULL);
309
310   /* EmpathyTpChat relies on this feature being prepared */
311   tp_base_client_add_connection_features_varargs (priv->handler,
312     TP_CONNECTION_FEATURE_CAPABILITIES, 0);
313
314   g_object_unref (dbus);
315
316   tp_base_client_take_handler_filter (priv->handler, tp_asv_new (
317         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
318         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
319         NULL));
320
321   tp_base_client_take_handler_filter (priv->handler, tp_asv_new (
322         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
323         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_ROOM,
324         NULL));
325
326   tp_base_client_take_handler_filter (priv->handler, tp_asv_new (
327         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
328         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_NONE,
329         NULL));
330
331   if (!tp_base_client_register (priv->handler, &error))
332     {
333       g_critical ("Failed to register text handler: %s", error->message);
334       g_error_free (error);
335     }
336 }
337
338 static void
339 empathy_chat_manager_finalize (GObject *object)
340 {
341   EmpathyChatManager *self = EMPATHY_CHAT_MANAGER (object);
342   EmpathyChatManagerPriv *priv = GET_PRIV (self);
343
344   if (priv->closed_queue != NULL)
345     {
346       g_queue_foreach (priv->closed_queue, (GFunc) chat_data_free, NULL);
347       g_queue_free (priv->closed_queue);
348       priv->closed_queue = NULL;
349     }
350
351   tp_clear_object (&priv->handler);
352   tp_clear_object (&priv->chatroom_mgr);
353
354   G_OBJECT_CLASS (empathy_chat_manager_parent_class)->finalize (object);
355 }
356
357 static GObject *
358 empathy_chat_manager_constructor (GType type,
359     guint n_construct_params,
360     GObjectConstructParam *construct_params)
361 {
362   GObject *retval;
363
364   if (!chat_manager_singleton)
365     {
366       retval = G_OBJECT_CLASS (empathy_chat_manager_parent_class)->constructor
367         (type, n_construct_params, construct_params);
368
369       chat_manager_singleton = EMPATHY_CHAT_MANAGER (retval);
370       g_object_add_weak_pointer (retval, (gpointer) &chat_manager_singleton);
371     }
372   else
373     {
374       retval = g_object_ref (chat_manager_singleton);
375     }
376
377   return retval;
378 }
379
380 static void
381 empathy_chat_manager_class_init (
382   EmpathyChatManagerClass *empathy_chat_manager_class)
383 {
384   GObjectClass *object_class = G_OBJECT_CLASS (empathy_chat_manager_class);
385
386   object_class->finalize = empathy_chat_manager_finalize;
387   object_class->constructor = empathy_chat_manager_constructor;
388
389   signals[CLOSED_CHATS_CHANGED] =
390     g_signal_new ("closed-chats-changed",
391         G_TYPE_FROM_CLASS (object_class),
392         G_SIGNAL_RUN_LAST,
393         0,
394         NULL, NULL,
395         g_cclosure_marshal_VOID__UINT,
396         G_TYPE_NONE,
397         1, G_TYPE_UINT, NULL);
398
399   signals[HANDLED_CHATS_CHANGED] =
400     g_signal_new ("handled-chats-changed",
401         G_TYPE_FROM_CLASS (object_class),
402         G_SIGNAL_RUN_LAST,
403         0,
404         NULL, NULL,
405         g_cclosure_marshal_VOID__UINT,
406         G_TYPE_NONE,
407         1, G_TYPE_UINT, NULL);
408
409   g_type_class_add_private (empathy_chat_manager_class,
410     sizeof (EmpathyChatManagerPriv));
411 }
412
413 EmpathyChatManager *
414 empathy_chat_manager_dup_singleton (void)
415 {
416   return g_object_new (EMPATHY_TYPE_CHAT_MANAGER, NULL);
417 }
418
419 void
420 empathy_chat_manager_closed_chat (EmpathyChatManager *self,
421     EmpathyChat *chat)
422 {
423   EmpathyChatManagerPriv *priv = GET_PRIV (self);
424   ChatData *data;
425
426   data = chat_data_new (chat);
427
428   DEBUG ("Adding %s to closed queue: %s",
429       data->room ? "room" : "contact", data->id);
430
431   g_queue_push_tail (priv->closed_queue, data);
432
433   g_signal_emit (self, signals[CLOSED_CHATS_CHANGED], 0,
434       g_queue_get_length (priv->closed_queue));
435 }
436
437 void
438 empathy_chat_manager_undo_closed_chat (EmpathyChatManager *self)
439 {
440   EmpathyChatManagerPriv *priv = GET_PRIV (self);
441   ChatData *data;
442
443   data = g_queue_pop_tail (priv->closed_queue);
444
445   if (data == NULL)
446     return;
447
448   DEBUG ("Removing %s from closed queue and starting a chat with: %s",
449       data->room ? "room" : "contact", data->id);
450
451   if (data->room)
452     empathy_dispatcher_join_muc (data->account, data->id,
453         TP_USER_ACTION_TIME_NOT_USER_ACTION);
454   else
455     empathy_dispatcher_chat_with_contact_id (data->account, data->id,
456         TP_USER_ACTION_TIME_NOT_USER_ACTION);
457
458   g_signal_emit (self, signals[CLOSED_CHATS_CHANGED], 0,
459       g_queue_get_length (priv->closed_queue));
460
461   chat_data_free (data);
462 }
463
464 guint
465 empathy_chat_manager_get_num_closed_chats (EmpathyChatManager *self)
466 {
467   EmpathyChatManagerPriv *priv = GET_PRIV (self);
468
469   return g_queue_get_length (priv->closed_queue);
470 }
471
472 guint
473 empathy_chat_manager_get_num_handled_chats (EmpathyChatManager *self)
474 {
475   EmpathyChatManagerPriv *priv = GET_PRIV (self);
476
477   return priv->num_handled_channels;
478 }