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