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