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