]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-chat.c
Do not ack pending messages until they are displayed
[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-2008 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  * 
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  */
21
22 #include <config.h>
23
24 #include <string.h>
25
26 #include <telepathy-glib/channel.h>
27 #include <telepathy-glib/dbus.h>
28 #include <telepathy-glib/util.h>
29
30 #include "empathy-tp-chat.h"
31 #include "empathy-contact-factory.h"
32 #include "empathy-contact-list.h"
33 #include "empathy-marshal.h"
34 #include "empathy-debug.h"
35 #include "empathy-time.h"
36 #include "empathy-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         EmpathyContactFactory *factory;
45         EmpathyContact        *user;
46         EmpathyContact        *remote_contact;
47         EmpathyTpGroup        *group;
48         McAccount             *account;
49         TpChannel             *channel;
50         gchar                 *id;
51         MissionControl        *mc;
52         gboolean               acknowledge;
53         TpChan                *tp_chan;
54         gboolean               had_pending_messages;
55         GSList                *message_queue;
56         gboolean               had_properties_list;
57         GPtrArray             *properties;
58 };
59
60 typedef struct {
61         gchar          *name;
62         guint           id;
63         TpPropertyFlags flags;
64         GValue         *value;
65 } TpChatProperty;
66
67 static void empathy_tp_chat_class_init (EmpathyTpChatClass      *klass);
68 static void empathy_tp_chat_init       (EmpathyTpChat           *chat);
69 static void tp_chat_iface_init         (EmpathyContactListIface *iface);
70
71 enum {
72         PROP_0,
73         PROP_ACCOUNT,
74         PROP_TP_CHAN,
75         PROP_CHANNEL,
76         PROP_ACKNOWLEDGE,
77         PROP_REMOTE_CONTACT,
78 };
79
80 enum {
81         MESSAGE_RECEIVED,
82         SEND_ERROR,
83         CHAT_STATE_CHANGED,
84         PROPERTY_CHANGED,
85         DESTROY,
86         LAST_SIGNAL
87 };
88
89 static guint signals[LAST_SIGNAL];
90
91 G_DEFINE_TYPE_WITH_CODE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT,
92                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST,
93                                                 tp_chat_iface_init));
94
95 static void
96 tp_chat_invalidated_cb (TpProxy       *proxy,
97                         guint          domain,
98                         gint           code,
99                         gchar         *message,
100                         EmpathyTpChat *chat)
101 {
102         EmpathyTpChatPriv *priv = GET_PRIV (chat);
103
104         empathy_debug (DEBUG_DOMAIN, "Channel invalidated: %s", message);
105
106         g_object_unref (priv->channel);
107         g_object_unref (priv->tp_chan);
108         priv->channel = NULL;
109         priv->tp_chan = NULL;
110
111         g_signal_emit (chat, signals[DESTROY], 0);
112 }
113
114 static void
115 tp_chat_async_cb (TpChannel *proxy,
116                   const GError *error,
117                   gpointer user_data,
118                   GObject *weak_object)
119 {
120         if (error) {
121                 empathy_debug (DEBUG_DOMAIN, "Error %s: %s",
122                                user_data, error->message);
123         }
124 }
125
126 static void
127 tp_chat_member_added_cb (EmpathyTpGroup *group,
128                          EmpathyContact *contact,
129                          EmpathyContact *actor,
130                          guint           reason,
131                          const gchar    *message,
132                          EmpathyTpChat  *chat)
133 {
134         g_signal_emit_by_name (chat, "members-changed",
135                                contact, actor, reason, message,
136                                TRUE);
137 }
138
139 static void
140 tp_chat_member_removed_cb (EmpathyTpGroup *group,
141                            EmpathyContact *contact,
142                            EmpathyContact *actor,
143                            guint           reason,
144                            const gchar    *message,
145                            EmpathyTpChat  *chat)
146 {
147         g_signal_emit_by_name (chat, "members-changed",
148                                contact, actor, reason, message,
149                                FALSE);
150 }
151 static void
152 tp_chat_local_pending_cb  (EmpathyTpGroup *group,
153                            EmpathyContact *contact,
154                            EmpathyContact *actor,
155                            guint           reason,
156                            const gchar    *message,
157                            EmpathyTpChat  *chat)
158 {
159         g_signal_emit_by_name (chat, "pendings-changed",
160                                contact, actor, reason, message,
161                                TRUE);
162 }
163
164 static void
165 tp_chat_add (EmpathyContactList *list,
166              EmpathyContact     *contact,
167              const gchar        *message)
168 {
169         EmpathyTpChatPriv *priv = GET_PRIV (list);
170
171         g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
172         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
173
174         if (priv->group) {
175                 empathy_tp_group_add_member (priv->group, contact, message);
176         }
177 }
178
179 static void
180 tp_chat_remove (EmpathyContactList *list,
181                 EmpathyContact     *contact,
182                 const gchar        *message)
183 {
184         EmpathyTpChatPriv *priv = GET_PRIV (list);
185
186         g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
187         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
188
189         if (priv->group) {
190                 empathy_tp_group_remove_member (priv->group, contact, message);
191         }
192 }
193
194 static GList *
195 tp_chat_get_members (EmpathyContactList *list)
196 {
197         EmpathyTpChatPriv *priv = GET_PRIV (list);
198         GList             *members = NULL;
199
200         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL);
201
202         if (priv->group) {
203                 members = empathy_tp_group_get_members (priv->group);
204         } else {
205                 members = g_list_prepend (members, g_object_ref (priv->user));
206                 members = g_list_prepend (members, g_object_ref (priv->remote_contact));
207         }
208
209         return members;
210 }
211
212 static EmpathyMessage *
213 tp_chat_build_message (EmpathyTpChat *chat,
214                        guint          type,
215                        guint          timestamp,
216                        guint          from_handle,
217                        const gchar   *message_body)
218 {
219         EmpathyTpChatPriv *priv;
220         EmpathyMessage    *message;
221         EmpathyContact    *sender;
222
223         priv = GET_PRIV (chat);
224
225         if (from_handle == 0) {
226                 sender = g_object_ref (priv->user);
227         } else {
228                 sender = empathy_contact_factory_get_from_handle (priv->factory,
229                                                                   priv->account,
230                                                                   from_handle);
231         }
232
233         message = empathy_message_new (message_body);
234         empathy_message_set_type (message, type);
235         empathy_message_set_sender (message, sender);
236         empathy_message_set_receiver (message, priv->user);
237         empathy_message_set_timestamp (message, timestamp);
238
239         g_object_unref (sender);
240
241         return message;
242 }
243
244 static void
245 tp_chat_sender_ready_notify_cb (EmpathyContact *contact,
246                                 GParamSpec     *param_spec,
247                                 EmpathyTpChat  *chat)
248 {
249         EmpathyTpChatPriv   *priv = GET_PRIV (chat);
250         EmpathyMessage      *message;
251         EmpathyContactReady  ready;
252         EmpathyContact      *sender;
253         gboolean             removed = FALSE;
254
255         /* Emit all messages queued until we find a message with not
256          * ready sender. When leaving this loop, sender is the first not ready
257          * contact queued and removed tells if at least one message got removed
258          * from the queue. */
259         while (priv->message_queue) {
260                 message = priv->message_queue->data;
261                 sender = empathy_message_get_sender (message);
262                 ready = empathy_contact_get_ready (sender);
263
264                 if (!(ready & EMPATHY_CONTACT_READY_NAME)) {
265                         break;
266                 }
267
268                 empathy_debug (DEBUG_DOMAIN, "Queued message ready");
269                 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
270                 priv->message_queue = g_slist_remove (priv->message_queue,
271                                                       message);
272                 g_object_unref (message);
273                 removed = TRUE;
274         }
275
276         if (removed) {
277                 g_signal_handlers_disconnect_by_func (contact,
278                                                       tp_chat_sender_ready_notify_cb,
279                                                       chat);
280
281                 if (priv->message_queue) {
282                         g_signal_connect (sender, "notify::ready",
283                                           G_CALLBACK (tp_chat_sender_ready_notify_cb),
284                                           chat);
285                 }
286         }
287 }
288
289 static void
290 tp_chat_emit_or_queue_message (EmpathyTpChat  *chat,
291                                EmpathyMessage *message)
292 {
293         EmpathyTpChatPriv   *priv = GET_PRIV (chat);
294         EmpathyContact      *sender;
295         EmpathyContactReady  ready;
296
297         if (priv->message_queue != NULL) {
298                 empathy_debug (DEBUG_DOMAIN, "Message queue not empty");
299                 priv->message_queue = g_slist_append (priv->message_queue,
300                                                       g_object_ref (message));
301                 return;
302         }
303
304         sender = empathy_message_get_sender (message);
305         ready = empathy_contact_get_ready (sender);
306         if (ready & EMPATHY_CONTACT_READY_NAME) {
307                 empathy_debug (DEBUG_DOMAIN, "Message queue empty and sender ready");
308                 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
309                 return;
310         }
311
312         empathy_debug (DEBUG_DOMAIN, "Sender not ready");
313         priv->message_queue = g_slist_append (priv->message_queue, 
314                                               g_object_ref (message));
315         g_signal_connect (sender, "notify::ready",
316                           G_CALLBACK (tp_chat_sender_ready_notify_cb),
317                           chat);
318 }
319
320 static void
321 tp_chat_received_cb (TpChannel   *channel,
322                      guint        message_id,
323                      guint        timestamp,
324                      guint        from_handle,
325                      guint        message_type,
326                      guint        message_flags,
327                      const gchar *message_body,
328                      gpointer     user_data,
329                      GObject     *chat)
330 {
331         EmpathyTpChatPriv *priv = GET_PRIV (chat);
332         EmpathyMessage    *message;
333
334         if (!priv->had_pending_messages) {
335                 return;
336         }
337  
338         empathy_debug (DEBUG_DOMAIN, "Message received: %s", message_body);
339
340         message = tp_chat_build_message (EMPATHY_TP_CHAT (chat),
341                                          message_type,
342                                          timestamp,
343                                          from_handle,
344                                          message_body);
345
346         tp_chat_emit_or_queue_message (EMPATHY_TP_CHAT (chat), message);
347         g_object_unref (message);
348
349         if (priv->acknowledge) {
350                 GArray *message_ids;
351
352                 message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
353                 g_array_append_val (message_ids, message_id);
354                 tp_cli_channel_type_text_call_acknowledge_pending_messages (priv->channel,
355                                                                             -1,
356                                                                             message_ids,
357                                                                             tp_chat_async_cb,
358                                                                             "acknowledging received message",
359                                                                             NULL,
360                                                                             chat);
361                 g_array_free (message_ids, TRUE);
362         }
363 }
364
365 static void
366 tp_chat_sent_cb (TpChannel   *channel,
367                  guint        timestamp,
368                  guint        message_type,
369                  const gchar *message_body,
370                  gpointer     user_data,
371                  GObject     *chat)
372 {
373         EmpathyMessage *message;
374
375         empathy_debug (DEBUG_DOMAIN, "Message sent: %s", message_body);
376
377         message = tp_chat_build_message (EMPATHY_TP_CHAT (chat),
378                                          message_type,
379                                          timestamp,
380                                          0,
381                                          message_body);
382
383         tp_chat_emit_or_queue_message (EMPATHY_TP_CHAT (chat), message);
384         g_object_unref (message);
385 }
386
387 static void
388 tp_chat_send_error_cb (TpChannel   *channel,
389                        guint        error_code,
390                        guint        timestamp,
391                        guint        message_type,
392                        const gchar *message_body,
393                        gpointer     user_data,
394                        GObject     *chat)
395 {
396         EmpathyMessage *message;
397
398         empathy_debug (DEBUG_DOMAIN, "Message sent error: %s (%d)",
399                        message_body, error_code);
400
401         message = tp_chat_build_message (EMPATHY_TP_CHAT (chat),
402                                          message_type,
403                                          timestamp,
404                                          0,
405                                          message_body);
406
407         g_signal_emit (chat, signals[SEND_ERROR], 0, message, error_code);
408         g_object_unref (message);
409 }
410
411 static void
412 tp_chat_state_changed_cb (TpChannel *channel,
413                           guint      handle,
414                           guint      state,
415                           gpointer   user_data,
416                           GObject   *chat)
417 {
418         EmpathyTpChatPriv *priv = GET_PRIV (chat);
419         EmpathyContact    *contact;
420
421         contact = empathy_contact_factory_get_from_handle (priv->factory,
422                                                            priv->account,
423                                                            handle);
424
425         empathy_debug (DEBUG_DOMAIN, "Chat state changed for %s (%d): %d",
426                       empathy_contact_get_name (contact),
427                       handle, state);
428
429         g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
430         g_object_unref (contact);
431 }
432
433 static void
434 tp_chat_list_pending_messages_cb (TpChannel       *channel,
435                                   const GPtrArray *messages_list,
436                                   const GError    *error,
437                                   gpointer         user_data,
438                                   GObject         *chat)
439 {
440         EmpathyTpChatPriv *priv = GET_PRIV (chat);
441         guint              i;
442         GArray            *message_ids = NULL;
443
444         priv->had_pending_messages = TRUE;
445
446         if (error) {
447                 empathy_debug (DEBUG_DOMAIN, "Error listing pending messages: %s",
448                                error->message);
449                 return;
450         }
451
452         if (priv->acknowledge) {
453                 message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint),
454                                                  messages_list->len);
455         }
456
457         for (i = 0; i < messages_list->len; i++) {
458                 EmpathyMessage *message;
459                 GValueArray    *message_struct;
460                 const gchar    *message_body;
461                 guint           message_id;
462                 guint           timestamp;
463                 guint           from_handle;
464                 guint           message_type;
465                 guint           message_flags;
466
467                 message_struct = g_ptr_array_index (messages_list, i);
468
469                 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
470                 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
471                 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
472                 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
473                 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
474                 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
475
476                 empathy_debug (DEBUG_DOMAIN, "Message pending: %s", message_body);
477
478                 if (message_ids) {
479                         g_array_append_val (message_ids, message_id);
480                 }
481
482                 message = tp_chat_build_message (EMPATHY_TP_CHAT (chat),
483                                                  message_type,
484                                                  timestamp,
485                                                  from_handle,
486                                                  message_body);
487
488                 tp_chat_emit_or_queue_message (EMPATHY_TP_CHAT (chat), message);
489                 g_object_unref (message);
490         }
491
492         if (message_ids) {
493                 tp_cli_channel_type_text_call_acknowledge_pending_messages (priv->channel,
494                                                                             -1,
495                                                                             message_ids,
496                                                                             tp_chat_async_cb,
497                                                                             "acknowledging pending messages",
498                                                                             NULL,
499                                                                             chat);
500                 g_array_free (message_ids, TRUE);
501         }
502 }
503
504 static void
505 tp_chat_property_flags_changed_cb (TpProxy         *proxy,
506                                    const GPtrArray *properties,
507                                    gpointer         user_data,
508                                    GObject         *chat)
509 {
510         EmpathyTpChatPriv *priv = GET_PRIV (chat);
511         guint              i, j;
512
513         if (!priv->had_properties_list || !properties) {
514                 return;
515         }
516
517         for (i = 0; i < properties->len; i++) {
518                 GValueArray    *prop_struct;
519                 TpChatProperty *property;
520                 guint           id;
521                 guint           flags;
522
523                 prop_struct = g_ptr_array_index (properties, i);
524                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
525                 flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 1));
526
527                 for (j = 0; j < priv->properties->len; j++) {
528                         property = g_ptr_array_index (priv->properties, j);
529                         if (property->id == id) {
530                                 property->flags = flags;
531                                 empathy_debug (DEBUG_DOMAIN,
532                                                "property %s flags changed: %d",
533                                                property->name, property->flags);
534                                 break;
535                         }
536                 }
537         }
538 }
539
540 static void
541 tp_chat_properties_changed_cb (TpProxy         *proxy,
542                                const GPtrArray *properties,
543                                gpointer         user_data,
544                                GObject         *chat)
545 {
546         EmpathyTpChatPriv *priv = GET_PRIV (chat);
547         guint              i, j;
548
549         if (!priv->had_properties_list || !properties) {
550                 return;
551         }
552
553         for (i = 0; i < properties->len; i++) {
554                 GValueArray    *prop_struct;
555                 TpChatProperty *property;
556                 guint           id;
557                 GValue         *src_value;
558
559                 prop_struct = g_ptr_array_index (properties, i);
560                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
561                 src_value = g_value_get_boxed (g_value_array_get_nth (prop_struct, 1));
562
563                 for (j = 0; j < priv->properties->len; j++) {
564                         property = g_ptr_array_index (priv->properties, j);
565                         if (property->id == id) {
566                                 if (property->value) {
567                                         g_value_copy (src_value, property->value);
568                                 } else {
569                                         property->value = tp_g_value_slice_dup (src_value);
570                                 }
571
572                                 empathy_debug (DEBUG_DOMAIN, "property %s changed",
573                                                property->name);
574                                 g_signal_emit (chat, signals[PROPERTY_CHANGED], 0,
575                                                property->name, property->value);
576                                 break;
577                         }
578                 }
579         }
580 }
581
582 static void
583 tp_chat_get_properties_cb (TpProxy         *proxy,
584                            const GPtrArray *properties,
585                            const GError    *error,
586                            gpointer         user_data,
587                            GObject         *chat)
588 {
589         if (error) {
590                 empathy_debug (DEBUG_DOMAIN, "Error getting properties: %s",
591                                error->message);
592                 return;
593         }
594
595         tp_chat_properties_changed_cb (proxy, properties, user_data, chat);
596 }
597
598 static void
599 tp_chat_list_properties_cb (TpProxy         *proxy,
600                             const GPtrArray *properties,
601                             const GError    *error,
602                             gpointer         user_data,
603                             GObject         *chat)
604 {
605         EmpathyTpChatPriv *priv = GET_PRIV (chat);
606         GArray            *ids;
607         guint              i;
608
609         priv->had_properties_list = TRUE;
610
611         if (error) {
612                 empathy_debug (DEBUG_DOMAIN, "Error listing properties: %s",
613                                error->message);
614                 return;
615         }
616
617         ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), properties->len);
618         priv->properties = g_ptr_array_sized_new (properties->len);
619         for (i = 0; i < properties->len; i++) {
620                 GValueArray    *prop_struct;
621                 TpChatProperty *property;
622
623                 prop_struct = g_ptr_array_index (properties, i);
624                 property = g_slice_new0 (TpChatProperty);
625                 property->id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
626                 property->name = g_value_dup_string (g_value_array_get_nth (prop_struct, 1));
627                 property->flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 3));
628
629                 empathy_debug (DEBUG_DOMAIN, "Adding property name=%s id=%d flags=%d",
630                                property->name, property->id, property->flags);
631                 g_ptr_array_add (priv->properties, property);
632                 if (property->flags & TP_PROPERTY_FLAG_READ) {
633                         g_array_append_val (ids, property->id);
634                 }
635         }
636
637         tp_cli_properties_interface_call_get_properties (proxy, -1,
638                                                          ids,
639                                                          tp_chat_get_properties_cb,
640                                                          NULL, NULL,
641                                                          chat);
642
643         g_array_free (ids, TRUE);
644 }
645
646 void
647 empathy_tp_chat_set_property (EmpathyTpChat *chat,
648                               const gchar   *name,
649                               const GValue  *value)
650 {
651         EmpathyTpChatPriv *priv = GET_PRIV (chat);
652         TpChatProperty    *property;
653         guint              i;
654
655         for (i = 0; i < priv->properties->len; i++) {
656                 property = g_ptr_array_index (priv->properties, i);
657                 if (!tp_strdiff (property->name, name)) {
658                         GPtrArray   *properties;
659                         GValueArray *prop;
660                         GValue       id = {0, };
661                         GValue       dest_value = {0, };
662
663                         if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) {
664                                 break;
665                         }
666
667                         g_value_init (&id, G_TYPE_UINT);
668                         g_value_init (&dest_value, G_TYPE_VALUE);
669                         g_value_set_uint (&id, property->id);
670                         g_value_set_boxed (&dest_value, value);
671
672                         prop = g_value_array_new (2);
673                         g_value_array_append (prop, &id);
674                         g_value_array_append (prop, &dest_value);
675
676                         properties = g_ptr_array_sized_new (1);
677                         g_ptr_array_add (properties, prop);
678
679                         empathy_debug (DEBUG_DOMAIN, "Set property %s", name);
680                         tp_cli_properties_interface_call_set_properties (priv->channel, -1,
681                                                                          properties,
682                                                                          (tp_cli_properties_interface_callback_for_set_properties)
683                                                                          tp_chat_async_cb,
684                                                                          "Seting property", NULL,
685                                                                          G_OBJECT (chat));
686
687                         g_ptr_array_free (properties, TRUE);
688                         g_value_array_free (prop);
689
690                         break;
691                 }
692         }
693 }
694
695 static void
696 tp_chat_channel_ready_cb (EmpathyTpChat *chat)
697 {
698         EmpathyTpChatPriv *priv = GET_PRIV (chat);
699
700         empathy_debug (DEBUG_DOMAIN, "Channel ready");
701
702         if (tp_proxy_has_interface_by_id (priv->channel,
703                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
704                 priv->group = empathy_tp_group_new (priv->account, priv->tp_chan);
705
706                 g_signal_connect (priv->group, "member-added",
707                                   G_CALLBACK (tp_chat_member_added_cb),
708                                   chat);
709                 g_signal_connect (priv->group, "member-removed",
710                                   G_CALLBACK (tp_chat_member_removed_cb),
711                                   chat);
712                 g_signal_connect (priv->group, "local-pending",
713                                   G_CALLBACK (tp_chat_local_pending_cb),
714                                   chat);
715         } else {
716                 priv->remote_contact = empathy_contact_factory_get_from_handle (priv->factory,
717                                                                                 priv->account,
718                                                                                 priv->tp_chan->handle);
719                 g_object_notify (G_OBJECT (chat), "remote-contact");
720         }
721         
722         if (tp_proxy_has_interface_by_id (priv->channel,
723                                           TP_IFACE_QUARK_PROPERTIES_INTERFACE)) {
724                 tp_cli_properties_interface_call_list_properties (priv->channel, -1,
725                                                                   tp_chat_list_properties_cb,
726                                                                   NULL, NULL,
727                                                                   G_OBJECT (chat));
728                 tp_cli_properties_interface_connect_to_properties_changed (priv->channel,
729                                                                            tp_chat_properties_changed_cb,
730                                                                            NULL, NULL,
731                                                                            G_OBJECT (chat), NULL);
732                 tp_cli_properties_interface_connect_to_property_flags_changed (priv->channel,
733                                                                                tp_chat_property_flags_changed_cb,
734                                                                                NULL, NULL,
735                                                                                G_OBJECT (chat), NULL);
736         }
737
738         tp_cli_channel_type_text_call_list_pending_messages (priv->channel, -1,
739                                                              priv->acknowledge,
740                                                              tp_chat_list_pending_messages_cb,
741                                                              NULL, NULL,
742                                                              G_OBJECT (chat));
743
744         tp_cli_channel_type_text_connect_to_received (priv->channel,
745                                                       tp_chat_received_cb,
746                                                       NULL, NULL,
747                                                       G_OBJECT (chat), NULL);
748         tp_cli_channel_type_text_connect_to_sent (priv->channel,
749                                                   tp_chat_sent_cb,
750                                                   NULL, NULL,
751                                                   G_OBJECT (chat), NULL);
752         tp_cli_channel_type_text_connect_to_send_error (priv->channel,
753                                                         tp_chat_send_error_cb,
754                                                         NULL, NULL,
755                                                         G_OBJECT (chat), NULL);
756         tp_cli_channel_interface_chat_state_connect_to_chat_state_changed (priv->channel,
757                                                                            tp_chat_state_changed_cb,
758                                                                            NULL, NULL,
759                                                                            G_OBJECT (chat), NULL);
760         tp_cli_channel_interface_chat_state_connect_to_chat_state_changed (priv->channel,
761                                                                            tp_chat_state_changed_cb,
762                                                                            NULL, NULL,
763                                                                            G_OBJECT (chat), NULL);
764 }
765
766 static void
767 tp_chat_finalize (GObject *object)
768 {
769         EmpathyTpChatPriv *priv = GET_PRIV (object);
770         guint              i;
771
772         if (priv->acknowledge && priv->channel) {
773                 empathy_debug (DEBUG_DOMAIN, "Closing channel...");
774                 tp_cli_channel_call_close (priv->channel, -1,
775                                            tp_chat_async_cb,
776                                            "closing channel", NULL,
777                                            NULL);
778         }
779
780         if (priv->channel) {
781                 g_signal_handlers_disconnect_by_func (priv->channel,
782                                                       tp_chat_invalidated_cb,
783                                                       object);
784                 g_object_unref (priv->channel);
785         }
786         if (priv->tp_chan) {
787                 g_object_unref (priv->tp_chan);
788         }
789
790         if (priv->properties) {
791                 for (i = 0; i < priv->properties->len; i++) {
792                         TpChatProperty *property;
793
794                         property = g_ptr_array_index (priv->properties, i);
795                         g_free (property->name);
796                         if (property->value) {
797                                 tp_g_value_slice_free (property->value);
798                         }
799                         g_slice_free (TpChatProperty, property);
800                 }
801                 g_ptr_array_free (priv->properties, TRUE);
802         }
803
804         if (priv->remote_contact) {
805                 g_object_unref (priv->remote_contact);
806         }
807         if (priv->group) {
808                 g_object_unref (priv->group);
809         }
810
811         g_object_unref (priv->factory);
812         g_object_unref (priv->user);
813         g_object_unref (priv->account);
814         g_object_unref (priv->mc);
815         g_free (priv->id);
816
817         G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
818 }
819
820 static GObject *
821 tp_chat_constructor (GType                  type,
822                      guint                  n_props,
823                      GObjectConstructParam *props)
824 {
825         GObject           *chat;
826         EmpathyTpChatPriv *priv;
827         gboolean           channel_ready;
828
829         chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
830
831         priv = GET_PRIV (chat);
832         priv->factory = empathy_contact_factory_new ();
833         priv->user = empathy_contact_factory_get_user (priv->factory, priv->account);
834         priv->mc = empathy_mission_control_new ();
835
836         g_signal_connect (priv->channel, "invalidated",
837                           G_CALLBACK (tp_chat_invalidated_cb),
838                           chat);
839
840         g_object_get (priv->channel, "channel-ready", &channel_ready, NULL);
841         if (channel_ready) {
842                 tp_chat_channel_ready_cb (EMPATHY_TP_CHAT (chat));
843         } else {
844                 g_signal_connect_swapped (priv->channel, "notify::channel-ready",
845                                           G_CALLBACK (tp_chat_channel_ready_cb),
846                                           chat);
847         }
848
849         return chat;
850 }
851
852 static void
853 tp_chat_get_property (GObject    *object,
854                       guint       param_id,
855                       GValue     *value,
856                       GParamSpec *pspec)
857 {
858         EmpathyTpChatPriv *priv = GET_PRIV (object);
859
860         switch (param_id) {
861         case PROP_ACCOUNT:
862                 g_value_set_object (value, priv->account);
863                 break;
864         case PROP_TP_CHAN:
865                 g_value_set_object (value, priv->tp_chan);
866                 break;
867         case PROP_CHANNEL:
868                 g_value_set_object (value, priv->channel);
869                 break;
870         case PROP_ACKNOWLEDGE:
871                 g_value_set_boolean (value, priv->acknowledge);
872                 break;
873         case PROP_REMOTE_CONTACT:
874                 g_value_set_object (value, priv->remote_contact);
875                 break;
876         default:
877                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
878                 break;
879         };
880 }
881
882 static void
883 tp_chat_set_property (GObject      *object,
884                       guint         param_id,
885                       const GValue *value,
886                       GParamSpec   *pspec)
887 {
888         EmpathyTpChatPriv *priv = GET_PRIV (object);
889
890         switch (param_id) {
891         case PROP_ACCOUNT:
892                 priv->account = g_object_ref (g_value_get_object (value));
893                 break;
894         case PROP_TP_CHAN:
895                 priv->tp_chan = g_object_ref (g_value_get_object (value));
896                 break;
897         case PROP_CHANNEL:
898                 priv->channel = g_object_ref (g_value_get_object (value));
899                 break;
900         case PROP_ACKNOWLEDGE:
901                 priv->acknowledge = g_value_get_boolean (value);
902                 break;
903         default:
904                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
905                 break;
906         };
907 }
908
909 static void
910 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
911 {
912         GObjectClass *object_class = G_OBJECT_CLASS (klass);
913
914         object_class->finalize = tp_chat_finalize;
915         object_class->constructor = tp_chat_constructor;
916         object_class->get_property = tp_chat_get_property;
917         object_class->set_property = tp_chat_set_property;
918
919         /* Construct properties */
920         g_object_class_install_property (object_class,
921                                          PROP_ACCOUNT,
922                                          g_param_spec_object ("account",
923                                                               "channel Account",
924                                                               "The account associated with the channel",
925                                                               MC_TYPE_ACCOUNT,
926                                                               G_PARAM_READWRITE |
927                                                               G_PARAM_CONSTRUCT_ONLY));
928         g_object_class_install_property (object_class,
929                                          PROP_TP_CHAN,
930                                          g_param_spec_object ("tp-chan",
931                                                               "telepathy channel",
932                                                               "The text channel for the chat",
933                                                               TELEPATHY_CHAN_TYPE,
934                                                               G_PARAM_READWRITE |
935                                                               G_PARAM_CONSTRUCT_ONLY));
936         g_object_class_install_property (object_class,
937                                          PROP_CHANNEL,
938                                          g_param_spec_object ("channel",
939                                                               "telepathy channel",
940                                                               "The text channel for the chat",
941                                                               TP_TYPE_CHANNEL,
942                                                               G_PARAM_READWRITE |
943                                                               G_PARAM_CONSTRUCT_ONLY));
944         g_object_class_install_property (object_class,
945                                          PROP_ACKNOWLEDGE,
946                                          g_param_spec_boolean ("acknowledge",
947                                                                "acknowledge messages",
948                                                                "Wheter or not received messages should be acknowledged",
949                                                                FALSE,
950                                                                G_PARAM_READWRITE |
951                                                                G_PARAM_CONSTRUCT));
952         g_object_class_install_property (object_class,
953                                          PROP_REMOTE_CONTACT,
954                                          g_param_spec_object ("remote-contact",
955                                                               "The remote contact",
956                                                               "The remote contact if there is no group iface on the channel",
957                                                               EMPATHY_TYPE_CONTACT,
958                                                               G_PARAM_READABLE));
959
960         /* Signals */
961         signals[MESSAGE_RECEIVED] =
962                 g_signal_new ("message-received",
963                               G_TYPE_FROM_CLASS (klass),
964                               G_SIGNAL_RUN_LAST,
965                               0,
966                               NULL, NULL,
967                               g_cclosure_marshal_VOID__OBJECT,
968                               G_TYPE_NONE,
969                               1, EMPATHY_TYPE_MESSAGE);
970
971         signals[SEND_ERROR] =
972                 g_signal_new ("send-error",
973                               G_TYPE_FROM_CLASS (klass),
974                               G_SIGNAL_RUN_LAST,
975                               0,
976                               NULL, NULL,
977                               _empathy_marshal_VOID__OBJECT_UINT,
978                               G_TYPE_NONE,
979                               2, EMPATHY_TYPE_MESSAGE, G_TYPE_UINT);
980
981         signals[CHAT_STATE_CHANGED] =
982                 g_signal_new ("chat-state-changed",
983                               G_TYPE_FROM_CLASS (klass),
984                               G_SIGNAL_RUN_LAST,
985                               0,
986                               NULL, NULL,
987                               _empathy_marshal_VOID__OBJECT_UINT,
988                               G_TYPE_NONE,
989                               2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT);
990
991         signals[PROPERTY_CHANGED] =
992                 g_signal_new ("property-changed",
993                               G_TYPE_FROM_CLASS (klass),
994                               G_SIGNAL_RUN_LAST,
995                               0,
996                               NULL, NULL,
997                               _empathy_marshal_VOID__STRING_BOXED,
998                               G_TYPE_NONE,
999                               2, G_TYPE_STRING, G_TYPE_VALUE);
1000
1001         signals[DESTROY] =
1002                 g_signal_new ("destroy",
1003                               G_TYPE_FROM_CLASS (klass),
1004                               G_SIGNAL_RUN_LAST,
1005                               0,
1006                               NULL, NULL,
1007                               g_cclosure_marshal_VOID__VOID,
1008                               G_TYPE_NONE,
1009                               0);
1010
1011         g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
1012 }
1013
1014 static void
1015 empathy_tp_chat_init (EmpathyTpChat *chat)
1016 {
1017 }
1018
1019 static void
1020 tp_chat_iface_init (EmpathyContactListIface *iface)
1021 {
1022         iface->add         = tp_chat_add;
1023         iface->remove      = tp_chat_remove;
1024         iface->get_members = tp_chat_get_members;
1025 }
1026
1027 EmpathyTpChat *
1028 empathy_tp_chat_new (McAccount *account,
1029                      TpChan    *tp_chan,
1030                      gboolean   acknowledge)
1031 {
1032         EmpathyTpChat  *chat;
1033         TpChannel      *channel;
1034         TpConnection   *connection;
1035         MissionControl *mc;
1036         TpConn         *tp_conn;
1037
1038         mc = empathy_mission_control_new ();
1039         tp_conn = mission_control_get_connection (mc, account, NULL);
1040         connection = tp_conn_dup_connection (tp_conn);
1041         channel = tp_chan_dup_channel (tp_chan, connection, NULL);
1042
1043         chat = g_object_new (EMPATHY_TYPE_TP_CHAT, 
1044                              "account", account,
1045                              "channel", channel,
1046                              "tp-chan", tp_chan,
1047                              "acknowledge", acknowledge,
1048                              NULL);
1049
1050         g_object_unref (channel);
1051         g_object_unref (tp_conn);
1052         g_object_unref (connection);
1053         g_object_unref (mc);
1054
1055         return chat;
1056 }
1057
1058 EmpathyTpChat *
1059 empathy_tp_chat_new_with_contact (EmpathyContact *contact)
1060 {
1061         EmpathyTpChat  *chat;
1062         MissionControl *mc;
1063         McAccount      *account;
1064         TpConn         *tp_conn;
1065         TpChan         *text_chan;
1066         const gchar    *bus_name;
1067         guint           handle;
1068
1069         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1070
1071         mc = empathy_mission_control_new ();
1072         account = empathy_contact_get_account (contact);
1073
1074         if (mission_control_get_connection_status (mc, account, NULL) != 0) {
1075                 /* The account is not connected. */
1076                 return NULL;
1077         }
1078
1079         tp_conn = mission_control_get_connection (mc, account, NULL);
1080         g_return_val_if_fail (tp_conn != NULL, NULL);
1081         bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
1082         handle = empathy_contact_get_handle (contact);
1083
1084         text_chan = tp_conn_new_channel (tp_get_bus (),
1085                                          tp_conn,
1086                                          bus_name,
1087                                          TP_IFACE_CHANNEL_TYPE_TEXT,
1088                                          TP_HANDLE_TYPE_CONTACT,
1089                                          handle,
1090                                          TRUE);
1091
1092         chat = empathy_tp_chat_new (account, text_chan, TRUE);
1093
1094         g_object_unref (tp_conn);
1095         g_object_unref (text_chan);
1096         g_object_unref (mc);
1097
1098         return chat;
1099 }
1100
1101 TpChan *
1102 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
1103 {
1104         EmpathyTpChatPriv *priv;
1105
1106         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1107
1108         priv = GET_PRIV (chat);
1109
1110         return priv->tp_chan;
1111 }
1112
1113 McAccount *
1114 empathy_tp_chat_get_account (EmpathyTpChat *chat)
1115 {
1116         EmpathyTpChatPriv *priv;
1117
1118         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1119
1120         priv = GET_PRIV (chat);
1121
1122         return priv->account;
1123 }
1124
1125 void
1126 empathy_tp_chat_send (EmpathyTpChat *chat,
1127                       EmpathyMessage *message)
1128 {
1129         EmpathyTpChatPriv  *priv;
1130         const gchar        *message_body;
1131         EmpathyMessageType  message_type;
1132
1133         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1134         g_return_if_fail (EMPATHY_IS_MESSAGE (message));
1135
1136         priv = GET_PRIV (chat);
1137
1138         message_body = empathy_message_get_body (message);
1139         message_type = empathy_message_get_type (message);
1140
1141         empathy_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
1142         tp_cli_channel_type_text_call_send (priv->channel, -1,
1143                                             message_type,
1144                                             message_body,
1145                                             tp_chat_async_cb,
1146                                             "sending message", NULL,
1147                                             G_OBJECT (chat));
1148 }
1149
1150 void
1151 empathy_tp_chat_set_state (EmpathyTpChat      *chat,
1152                            TpChannelChatState  state)
1153 {
1154         EmpathyTpChatPriv *priv;
1155
1156         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1157
1158         priv = GET_PRIV (chat);
1159
1160         empathy_debug (DEBUG_DOMAIN, "Set state: %d", state);
1161         tp_cli_channel_interface_chat_state_call_set_chat_state (priv->channel, -1,
1162                                                                  state,
1163                                                                  tp_chat_async_cb,
1164                                                                  "setting chat state",
1165                                                                  NULL,
1166                                                                  G_OBJECT (chat));
1167 }
1168
1169 const gchar *
1170 empathy_tp_chat_get_id (EmpathyTpChat *chat)
1171 {
1172         EmpathyTpChatPriv *priv;
1173
1174         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1175
1176         priv = GET_PRIV (chat);
1177
1178         if (!priv->id) {
1179                 priv->id = empathy_inspect_channel (priv->account, priv->tp_chan);
1180         }
1181
1182         return priv->id;
1183 }
1184
1185 EmpathyContact *
1186 empathy_tp_chat_get_remote_contact (EmpathyTpChat *chat)
1187 {
1188         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1189
1190         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1191
1192         return priv->remote_contact;
1193 }
1194