]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-chat.c
Update for new API
[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->channel);
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         case PROP_READY:
874                 g_value_set_boolean (value, priv->ready);
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         g_object_class_install_property (object_class,
920                                          PROP_ACCOUNT,
921                                          g_param_spec_object ("account",
922                                                               "channel Account",
923                                                               "The account associated with the channel",
924                                                               MC_TYPE_ACCOUNT,
925                                                               G_PARAM_READWRITE |
926                                                               G_PARAM_CONSTRUCT_ONLY));
927         g_object_class_install_property (object_class,
928                                          PROP_TP_CHAN,
929                                          g_param_spec_object ("tp-chan",
930                                                               "telepathy channel",
931                                                               "The text channel for the chat",
932                                                               TELEPATHY_CHAN_TYPE,
933                                                               G_PARAM_READWRITE |
934                                                               G_PARAM_CONSTRUCT_ONLY));
935         g_object_class_install_property (object_class,
936                                          PROP_CHANNEL,
937                                          g_param_spec_object ("channel",
938                                                               "telepathy channel",
939                                                               "The text channel for the chat",
940                                                               TP_TYPE_CHANNEL,
941                                                               G_PARAM_READWRITE |
942                                                               G_PARAM_CONSTRUCT_ONLY));
943         g_object_class_install_property (object_class,
944                                          PROP_ACKNOWLEDGE,
945                                          g_param_spec_boolean ("acknowledge",
946                                                                "acknowledge messages",
947                                                                "Wheter or not received messages should be acknowledged",
948                                                                FALSE,
949                                                                G_PARAM_READWRITE |
950                                                                G_PARAM_CONSTRUCT));
951
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         g_object_class_install_property (object_class,
960                                          PROP_READY,
961                                          g_param_spec_boolean ("ready",
962                                                                "Is the object ready",
963                                                                "This object can't be used until this becomes true",
964                                                                FALSE,
965                                                                G_PARAM_READABLE));
966
967         /* Signals */
968         signals[MESSAGE_RECEIVED] =
969                 g_signal_new ("message-received",
970                               G_TYPE_FROM_CLASS (klass),
971                               G_SIGNAL_RUN_LAST,
972                               0,
973                               NULL, NULL,
974                               g_cclosure_marshal_VOID__OBJECT,
975                               G_TYPE_NONE,
976                               1, EMPATHY_TYPE_MESSAGE);
977
978         signals[SEND_ERROR] =
979                 g_signal_new ("send-error",
980                               G_TYPE_FROM_CLASS (klass),
981                               G_SIGNAL_RUN_LAST,
982                               0,
983                               NULL, NULL,
984                               _empathy_marshal_VOID__OBJECT_UINT,
985                               G_TYPE_NONE,
986                               2, EMPATHY_TYPE_MESSAGE, G_TYPE_UINT);
987
988         signals[CHAT_STATE_CHANGED] =
989                 g_signal_new ("chat-state-changed",
990                               G_TYPE_FROM_CLASS (klass),
991                               G_SIGNAL_RUN_LAST,
992                               0,
993                               NULL, NULL,
994                               _empathy_marshal_VOID__OBJECT_UINT,
995                               G_TYPE_NONE,
996                               2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT);
997
998         signals[PROPERTY_CHANGED] =
999                 g_signal_new ("property-changed",
1000                               G_TYPE_FROM_CLASS (klass),
1001                               G_SIGNAL_RUN_LAST,
1002                               0,
1003                               NULL, NULL,
1004                               _empathy_marshal_VOID__STRING_BOXED,
1005                               G_TYPE_NONE,
1006                               2, G_TYPE_STRING, G_TYPE_VALUE);
1007
1008         signals[DESTROY] =
1009                 g_signal_new ("destroy",
1010                               G_TYPE_FROM_CLASS (klass),
1011                               G_SIGNAL_RUN_LAST,
1012                               0,
1013                               NULL, NULL,
1014                               g_cclosure_marshal_VOID__VOID,
1015                               G_TYPE_NONE,
1016                               0);
1017
1018         g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
1019 }
1020
1021 static void
1022 empathy_tp_chat_init (EmpathyTpChat *chat)
1023 {
1024 }
1025
1026 static void
1027 tp_chat_iface_init (EmpathyContactListIface *iface)
1028 {
1029         iface->add         = tp_chat_add;
1030         iface->remove      = tp_chat_remove;
1031         iface->get_members = tp_chat_get_members;
1032 }
1033
1034 EmpathyTpChat *
1035 empathy_tp_chat_new (McAccount *account,
1036                      TpChan    *tp_chan,
1037                      gboolean   acknowledge)
1038 {
1039         EmpathyTpChat  *chat;
1040         TpChannel      *channel;
1041         TpConnection   *connection;
1042         MissionControl *mc;
1043
1044         mc = empathy_mission_control_new ();
1045         connection = mission_control_get_tpconnection (mc, account, NULL);
1046         channel = tp_chan_dup_channel (tp_chan, connection, NULL);
1047
1048         chat = g_object_new (EMPATHY_TYPE_TP_CHAT, 
1049                              "account", account,
1050                              "channel", channel,
1051                              "tp-chan", tp_chan,
1052                              "acknowledge", acknowledge,
1053                              NULL);
1054
1055         g_object_unref (channel);
1056         g_object_unref (connection);
1057         g_object_unref (mc);
1058
1059         return chat;
1060 }
1061
1062 EmpathyTpChat *
1063 empathy_tp_chat_new_with_contact (EmpathyContact *contact)
1064 {
1065         EmpathyTpChat  *chat;
1066         MissionControl *mc;
1067         McAccount      *account;
1068         TpConn         *tp_conn;
1069         TpChan         *text_chan;
1070         const gchar    *bus_name;
1071         guint           handle;
1072
1073         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1074
1075         mc = empathy_mission_control_new ();
1076         account = empathy_contact_get_account (contact);
1077
1078         if (mission_control_get_connection_status (mc, account, NULL) != 0) {
1079                 /* The account is not connected. */
1080                 return NULL;
1081         }
1082
1083         tp_conn = mission_control_get_connection (mc, account, NULL);
1084         g_return_val_if_fail (tp_conn != NULL, NULL);
1085         bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
1086         handle = empathy_contact_get_handle (contact);
1087
1088         text_chan = tp_conn_new_channel (tp_get_bus (),
1089                                          tp_conn,
1090                                          bus_name,
1091                                          TP_IFACE_CHANNEL_TYPE_TEXT,
1092                                          TP_HANDLE_TYPE_CONTACT,
1093                                          handle,
1094                                          TRUE);
1095
1096         chat = empathy_tp_chat_new (account, text_chan, TRUE);
1097
1098         g_object_unref (tp_conn);
1099         g_object_unref (text_chan);
1100         g_object_unref (mc);
1101
1102         return chat;
1103 }
1104
1105 TpChan *
1106 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
1107 {
1108         EmpathyTpChatPriv *priv;
1109
1110         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1111
1112         priv = GET_PRIV (chat);
1113
1114         return priv->tp_chan;
1115 }
1116
1117 McAccount *
1118 empathy_tp_chat_get_account (EmpathyTpChat *chat)
1119 {
1120         EmpathyTpChatPriv *priv;
1121
1122         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1123
1124         priv = GET_PRIV (chat);
1125
1126         return priv->account;
1127 }
1128
1129 void
1130 empathy_tp_chat_send (EmpathyTpChat *chat,
1131                       EmpathyMessage *message)
1132 {
1133         EmpathyTpChatPriv  *priv = GET_PRIV (chat);
1134         const gchar        *message_body;
1135         EmpathyMessageType  message_type;
1136
1137         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1138         g_return_if_fail (EMPATHY_IS_MESSAGE (message));
1139         g_return_if_fail (priv->ready);
1140
1141         message_body = empathy_message_get_body (message);
1142         message_type = empathy_message_get_type (message);
1143
1144         empathy_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
1145         tp_cli_channel_type_text_call_send (priv->channel, -1,
1146                                             message_type,
1147                                             message_body,
1148                                             tp_chat_async_cb,
1149                                             "sending message", NULL,
1150                                             G_OBJECT (chat));
1151 }
1152
1153 void
1154 empathy_tp_chat_set_state (EmpathyTpChat      *chat,
1155                            TpChannelChatState  state)
1156 {
1157         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1158
1159         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1160         g_return_if_fail (priv->ready);
1161
1162         empathy_debug (DEBUG_DOMAIN, "Set state: %d", state);
1163         tp_cli_channel_interface_chat_state_call_set_chat_state (priv->channel, -1,
1164                                                                  state,
1165                                                                  tp_chat_async_cb,
1166                                                                  "setting chat state",
1167                                                                  NULL,
1168                                                                  G_OBJECT (chat));
1169 }
1170
1171 const gchar *
1172 empathy_tp_chat_get_id (EmpathyTpChat *chat)
1173 {
1174         EmpathyTpChatPriv *priv;
1175
1176         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1177
1178         priv = GET_PRIV (chat);
1179
1180         if (!priv->id) {
1181                 priv->id = empathy_inspect_channel (priv->account, priv->tp_chan);
1182         }
1183
1184         return priv->id;
1185 }
1186
1187 EmpathyContact *
1188 empathy_tp_chat_get_remote_contact (EmpathyTpChat *chat)
1189 {
1190         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1191
1192         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1193
1194         return priv->remote_contact;
1195 }
1196
1197 gboolean
1198 empathy_tp_chat_is_ready (EmpathyTpChat *chat)
1199 {
1200         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1201
1202         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
1203
1204         return priv->ready;
1205 }
1206