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