]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-chat.c
When a new text channel arrives, check if there is no existing GossipChat
[empathy.git] / libempathy / empathy-tp-chat.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program 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  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  * 
20  * Authors: Xavier Claessens <xclaesse@gmail.com>
21  */
22
23 #include <config.h>
24
25 #include <libtelepathy/tp-chan-type-text-gen.h>
26 #include <libtelepathy/tp-chan-iface-chat-state-gen.h>
27 #include <libtelepathy/tp-conn.h>
28 #include <libtelepathy/tp-helpers.h>
29
30 #include "empathy-tp-chat.h"
31 #include "empathy-contact-manager.h"
32 #include "empathy-contact-list.h"
33 #include "empathy-marshal.h"
34 #include "gossip-debug.h"
35 #include "gossip-time.h"
36 #include "gossip-utils.h"
37
38 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
39                        EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv))
40
41 #define DEBUG_DOMAIN "TpChat"
42
43 struct _EmpathyTpChatPriv {
44         EmpathyContactList    *list;
45         EmpathyContactManager *manager;
46         McAccount             *account;
47         gchar                 *id;
48         MissionControl        *mc;
49
50         TpChan                *tp_chan;
51         DBusGProxy            *text_iface;
52         DBusGProxy            *chat_state_iface;
53 };
54
55 static void empathy_tp_chat_class_init (EmpathyTpChatClass        *klass);
56 static void empathy_tp_chat_init       (EmpathyTpChat             *chat);
57 static void tp_chat_finalize           (GObject                   *object);
58 static void tp_chat_destroy_cb         (TpChan                    *text_chan,
59                                         EmpathyTpChat             *chat);
60 static void tp_chat_closed_cb          (TpChan                    *text_chan,
61                                         EmpathyTpChat             *chat);
62 static void tp_chat_received_cb        (DBusGProxy                *text_iface,
63                                         guint                      message_id,
64                                         guint                      timestamp,
65                                         guint                      from_handle,
66                                         guint                      message_type,
67                                         guint                      message_flags,
68                                         gchar                     *message_body,
69                                         EmpathyTpChat             *chat);
70 static void tp_chat_sent_cb            (DBusGProxy                *text_iface,
71                                         guint                      timestamp,
72                                         guint                      message_type,
73                                         gchar                     *message_body,
74                                         EmpathyTpChat             *chat);
75 static void tp_chat_state_changed_cb   (DBusGProxy                *chat_state_iface,
76                                         guint                      handle,
77                                         TelepathyChannelChatState  state,
78                                         EmpathyTpChat             *chat);
79 static void tp_chat_emit_message       (EmpathyTpChat             *chat,
80                                         guint                      type,
81                                         guint                      timestamp,
82                                         guint                      from_handle,
83                                         const gchar               *message_body);
84
85 enum {
86         MESSAGE_RECEIVED,
87         CHAT_STATE_CHANGED,
88         DESTROY,
89         LAST_SIGNAL
90 };
91
92 static guint signals[LAST_SIGNAL];
93
94 G_DEFINE_TYPE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT);
95
96 static void
97 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
98 {
99         GObjectClass *object_class = G_OBJECT_CLASS (klass);
100
101         object_class->finalize = tp_chat_finalize;
102
103         signals[MESSAGE_RECEIVED] =
104                 g_signal_new ("message-received",
105                               G_TYPE_FROM_CLASS (klass),
106                               G_SIGNAL_RUN_LAST,
107                               0,
108                               NULL, NULL,
109                               g_cclosure_marshal_VOID__OBJECT,
110                               G_TYPE_NONE,
111                               1, GOSSIP_TYPE_MESSAGE);
112
113         signals[CHAT_STATE_CHANGED] =
114                 g_signal_new ("chat-state-changed",
115                               G_TYPE_FROM_CLASS (klass),
116                               G_SIGNAL_RUN_LAST,
117                               0,
118                               NULL, NULL,
119                               empathy_marshal_VOID__OBJECT_UINT,
120                               G_TYPE_NONE,
121                               2, GOSSIP_TYPE_CONTACT, G_TYPE_UINT);
122
123         signals[DESTROY] =
124                 g_signal_new ("destroy",
125                               G_TYPE_FROM_CLASS (klass),
126                               G_SIGNAL_RUN_LAST,
127                               0,
128                               NULL, NULL,
129                               g_cclosure_marshal_VOID__VOID,
130                               G_TYPE_NONE,
131                               0);
132
133         g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
134 }
135
136 static void
137 empathy_tp_chat_init (EmpathyTpChat *chat)
138 {
139 }
140
141
142 static void
143 tp_chat_finalize (GObject *object)
144 {
145         EmpathyTpChatPriv *priv;
146         EmpathyTpChat     *chat;
147         GError            *error = NULL;
148
149         chat = EMPATHY_TP_CHAT (object);
150         priv = GET_PRIV (chat);
151
152         if (priv->tp_chan) {
153                 gossip_debug (DEBUG_DOMAIN, "Closing channel...");
154
155                 g_signal_handlers_disconnect_by_func (priv->tp_chan,
156                                                       tp_chat_destroy_cb,
157                                                       object);
158
159                 if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) {
160                         gossip_debug (DEBUG_DOMAIN, 
161                                       "Error closing text channel: %s",
162                                       error ? error->message : "No error given");
163                         g_clear_error (&error);
164                 }
165                 g_object_unref (priv->tp_chan);
166         }
167
168         if (priv->manager) {
169                 g_object_unref (priv->manager);
170         }
171         if (priv->list) {
172                 g_object_unref (priv->list);
173         }
174         if (priv->account) {
175                 g_object_unref (priv->account);
176         }
177         if (priv->mc) {
178                 g_object_unref (priv->mc);
179         }
180         g_free (priv->id);
181
182         G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
183 }
184
185 EmpathyTpChat *
186 empathy_tp_chat_new (McAccount *account,
187                      TpChan    *tp_chan)
188 {
189         EmpathyTpChatPriv     *priv;
190         EmpathyTpChat         *chat;
191
192         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
193         g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
194
195         chat = g_object_new (EMPATHY_TYPE_TP_CHAT, NULL);
196         priv = GET_PRIV (chat);
197
198         priv->manager = empathy_contact_manager_new ();
199         priv->list = empathy_contact_manager_get_list (priv->manager, account);
200         priv->tp_chan = g_object_ref (tp_chan);
201         priv->account = g_object_ref (account);
202         priv->mc = gossip_mission_control_new ();
203         g_object_ref (priv->list);
204
205         priv->text_iface = tp_chan_get_interface (tp_chan,
206                                                   TELEPATHY_CHAN_IFACE_TEXT_QUARK);
207         priv->chat_state_iface = tp_chan_get_interface (tp_chan,
208                                                         TELEPATHY_CHAN_IFACE_CHAT_STATE_QUARK);
209
210         g_signal_connect (priv->tp_chan, "destroy",
211                           G_CALLBACK (tp_chat_destroy_cb),
212                           chat);
213         dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_chan), "Closed",
214                                      G_CALLBACK (tp_chat_closed_cb),
215                                      chat, NULL);
216         dbus_g_proxy_connect_signal (priv->text_iface, "Received",
217                                      G_CALLBACK (tp_chat_received_cb),
218                                      chat, NULL);
219         dbus_g_proxy_connect_signal (priv->text_iface, "Sent",
220                                      G_CALLBACK (tp_chat_sent_cb),
221                                      chat, NULL);
222
223         if (priv->chat_state_iface != NULL) {
224                 dbus_g_proxy_connect_signal (priv->chat_state_iface,
225                                              "ChatStateChanged",
226                                              G_CALLBACK (tp_chat_state_changed_cb),
227                                              chat, NULL);
228         }
229
230         return chat;
231 }
232
233 EmpathyTpChat *
234 empathy_tp_chat_new_with_contact (GossipContact *contact)
235 {
236         EmpathyTpChat  *chat;
237         MissionControl *mc;
238         McAccount      *account;
239         TpConn         *tp_conn;
240         TpChan         *text_chan;
241         const gchar    *bus_name;
242         guint           handle;
243
244         g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
245
246         mc = gossip_mission_control_new ();
247         account = gossip_contact_get_account (contact);
248
249         if (mission_control_get_connection_status (mc, account, NULL) != 0) {
250                 /* The account is not connected, nothing to do. */
251                 return NULL;
252         }
253
254         tp_conn = mission_control_get_connection (mc, account, NULL);
255         g_return_val_if_fail (tp_conn != NULL, NULL);
256         bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
257         handle = gossip_contact_get_handle (contact);
258
259         text_chan = tp_conn_new_channel (tp_get_bus (),
260                                          tp_conn,
261                                          bus_name,
262                                          TP_IFACE_CHANNEL_TYPE_TEXT,
263                                          TP_HANDLE_TYPE_CONTACT,
264                                          handle,
265                                          TRUE);
266
267         chat = empathy_tp_chat_new (account, text_chan);
268
269         g_object_unref (tp_conn);
270         g_object_unref (text_chan);
271         g_object_unref (mc);
272
273         return chat;
274 }
275
276 void
277 empathy_tp_chat_request_pending (EmpathyTpChat *chat)
278 {
279         EmpathyTpChatPriv *priv;
280         GPtrArray         *messages_list;
281         guint              i;
282         GError            *error = NULL;
283
284         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
285
286         priv = GET_PRIV (chat);
287
288         /* If we do this call async, don't forget to ignore Received signal
289          * until we get the answer */
290         if (!tp_chan_type_text_list_pending_messages (priv->text_iface,
291                                                       TRUE,
292                                                       &messages_list,
293                                                       &error)) {
294                 gossip_debug (DEBUG_DOMAIN, 
295                               "Error retrieving pending messages: %s",
296                               error ? error->message : "No error given");
297                 g_clear_error (&error);
298                 return;
299         }
300
301         for (i = 0; i < messages_list->len; i++) {
302                 GValueArray *message_struct;
303                 const gchar *message_body;
304                 guint        message_id;
305                 guint        timestamp;
306                 guint        from_handle;
307                 guint        message_type;
308                 guint        message_flags;
309
310                 message_struct = g_ptr_array_index (messages_list, i);
311
312                 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
313                 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
314                 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
315                 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
316                 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
317                 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
318
319                 gossip_debug (DEBUG_DOMAIN, "Message pending: %s", message_body);
320
321                 tp_chat_emit_message (chat,
322                                       message_type,
323                                       timestamp,
324                                       from_handle,
325                                       message_body);
326
327                 g_value_array_free (message_struct);
328         }
329
330         g_ptr_array_free (messages_list, TRUE);
331 }
332
333 void
334 empathy_tp_chat_send (EmpathyTpChat *chat,
335                       GossipMessage *message)
336 {
337         EmpathyTpChatPriv *priv;
338         const gchar       *message_body;
339         GossipMessageType  message_type;
340         GError            *error = NULL;
341
342         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
343         g_return_if_fail (GOSSIP_IS_MESSAGE (message));
344
345         priv = GET_PRIV (chat);
346
347         message_body = gossip_message_get_body (message);
348         message_type = gossip_message_get_type (message);
349
350         gossip_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
351         if (!tp_chan_type_text_send (priv->text_iface,
352                                      message_type,
353                                      message_body,
354                                      &error)) {
355                 gossip_debug (DEBUG_DOMAIN, 
356                               "Send Error: %s", 
357                               error ? error->message : "No error given");
358                 g_clear_error (&error);
359         }
360 }
361
362 void
363 empathy_tp_chat_set_state (EmpathyTpChat             *chat,
364                            TelepathyChannelChatState  state)
365 {
366         EmpathyTpChatPriv *priv;
367         GError            *error = NULL;
368
369         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
370
371         priv = GET_PRIV (chat);
372
373         if (priv->chat_state_iface) {
374                 gossip_debug (DEBUG_DOMAIN, "Set state: %d", state);
375                 if (!tp_chan_iface_chat_state_set_chat_state (priv->chat_state_iface,
376                                                               state,
377                                                               &error)) {
378                         gossip_debug (DEBUG_DOMAIN,
379                                       "Set Chat State Error: %s",
380                                       error ? error->message : "No error given");
381                         g_clear_error (&error);
382                 }
383         }
384 }
385
386 const gchar *
387 empathy_tp_chat_get_id (EmpathyTpChat *chat)
388 {
389         EmpathyTpChatPriv *priv;
390
391         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
392
393         priv = GET_PRIV (chat);
394
395         if (priv->id) {
396                 return priv->id;
397         }
398
399         priv->id = empathy_tp_chat_build_id (priv->account, priv->tp_chan);
400
401         return priv->id;
402 }
403
404 gchar *
405 empathy_tp_chat_build_id (McAccount *account,
406                           TpChan    *tp_chan)
407 {
408         MissionControl *mc;
409         TpConn         *tp_conn;
410         GArray         *handles;
411         gchar         **names;
412         gchar          *id;
413         GError         *error = NULL;
414         
415         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
416         g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
417
418         mc = gossip_mission_control_new ();
419         tp_conn = mission_control_get_connection (mc, account, NULL);
420         g_object_unref (mc);
421
422         handles = g_array_new (FALSE, FALSE, sizeof (guint));
423         g_array_append_val (handles, tp_chan->handle);
424         if (!tp_conn_inspect_handles (DBUS_G_PROXY (tp_conn),
425                                       tp_chan->handle_type,
426                                       handles,
427                                       &names,
428                                       &error)) {
429                 gossip_debug (DEBUG_DOMAIN, 
430                               "Couldn't get id: %s",
431                               error ? error->message : "No error given");
432                 g_clear_error (&error);
433                 g_array_free (handles, TRUE);
434                 g_object_unref (tp_conn);
435                 
436                 return NULL;
437         }
438
439         /* A handle name is unique only for a specific account */
440         id = g_strdup_printf ("%s/%s",
441                               mc_account_get_unique_name (account),
442                               *names);
443
444         g_strfreev (names);
445         g_object_unref (tp_conn);
446
447         return id;
448 }
449
450 static void
451 tp_chat_destroy_cb (TpChan        *text_chan,
452                     EmpathyTpChat *chat)
453 {
454         EmpathyTpChatPriv *priv;
455
456         priv = GET_PRIV (chat);
457
458         gossip_debug (DEBUG_DOMAIN, "Channel Closed or CM crashed");
459
460         g_object_unref  (priv->tp_chan);
461         priv->tp_chan = NULL;
462         priv->text_iface = NULL;
463         priv->chat_state_iface = NULL;
464
465         g_signal_emit (chat, signals[DESTROY], 0);
466 }
467
468 static void
469 tp_chat_closed_cb (TpChan        *text_chan,
470                    EmpathyTpChat *chat)
471 {
472         EmpathyTpChatPriv *priv;
473
474         priv = GET_PRIV (chat);
475
476         /* The channel is closed, do just like if the proxy was destroyed */
477         g_signal_handlers_disconnect_by_func (priv->tp_chan,
478                                               tp_chat_destroy_cb,
479                                               chat);
480         tp_chat_destroy_cb (text_chan, chat);
481
482 }
483
484 static void
485 tp_chat_received_cb (DBusGProxy    *text_iface,
486                      guint          message_id,
487                      guint          timestamp,
488                      guint          from_handle,
489                      guint          message_type,
490                      guint          message_flags,
491                      gchar         *message_body,
492                      EmpathyTpChat *chat)
493 {
494         EmpathyTpChatPriv *priv;
495         GArray            *message_ids;
496
497         priv = GET_PRIV (chat);
498
499         gossip_debug (DEBUG_DOMAIN, "Message received: %s", message_body);
500
501         tp_chat_emit_message (chat,
502                               message_type,
503                               timestamp,
504                               from_handle,
505                               message_body);
506
507         message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
508         g_array_append_val (message_ids, message_id);
509         tp_chan_type_text_acknowledge_pending_messages (priv->text_iface,
510                                                         message_ids, NULL);
511         g_array_free (message_ids, TRUE);
512 }
513
514 static void
515 tp_chat_sent_cb (DBusGProxy    *text_iface,
516                  guint          timestamp,
517                  guint          message_type,
518                  gchar         *message_body,
519                  EmpathyTpChat *chat)
520 {
521         gossip_debug (DEBUG_DOMAIN, "Message sent: %s", message_body);
522
523         tp_chat_emit_message (chat,
524                               message_type,
525                               timestamp,
526                               0,
527                               message_body);
528 }
529
530 static void
531 tp_chat_state_changed_cb (DBusGProxy                *chat_state_iface,
532                           guint                      handle,
533                           TelepathyChannelChatState  state,
534                           EmpathyTpChat             *chat)
535 {
536         EmpathyTpChatPriv *priv;
537         GossipContact     *contact;
538
539         priv = GET_PRIV (chat);
540
541         contact = empathy_contact_list_get_from_handle (priv->list, handle);
542
543         gossip_debug (DEBUG_DOMAIN, "Chat state changed for %s (%d): %d",
544                       gossip_contact_get_name (contact),
545                       handle,
546                       state);
547
548         g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
549
550         g_object_unref (contact);
551 }
552
553 static void
554 tp_chat_emit_message (EmpathyTpChat *chat,
555                       guint          type,
556                       guint          timestamp,
557                       guint          from_handle,
558                       const gchar   *message_body)
559 {
560         EmpathyTpChatPriv *priv;
561         GossipMessage     *message;
562         GossipContact     *sender;
563
564         priv = GET_PRIV (chat);
565
566         if (from_handle == 0) {
567                 sender = empathy_contact_list_get_own (priv->list);
568                 g_object_ref (sender);
569         } else {
570                 sender = empathy_contact_list_get_from_handle (priv->list,
571                                                                from_handle);
572         }
573
574         message = gossip_message_new (message_body);
575         gossip_message_set_type (message, type);
576         gossip_message_set_sender (message, sender);
577         gossip_message_set_timestamp (message, (GossipTime) timestamp);
578
579         g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
580
581         g_object_unref (message);
582         g_object_unref (sender);
583 }
584