]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-chat.c
add myself to AUTHORS
[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 #include <telepathy-glib/interfaces.h>
30
31 #include "empathy-tp-chat.h"
32 #include "empathy-tp-contact-factory.h"
33 #include "empathy-contact-monitor.h"
34 #include "empathy-contact-list.h"
35 #include "empathy-marshal.h"
36 #include "empathy-time.h"
37 #include "empathy-utils.h"
38
39 #define DEBUG_FLAG EMPATHY_DEBUG_TP | EMPATHY_DEBUG_CHAT
40 #include "empathy-debug.h"
41
42 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpChat)
43 typedef struct {
44         gboolean               dispose_has_run;
45         EmpathyTpContactFactory *factory;
46         EmpathyContactMonitor *contact_monitor;
47         EmpathyContact        *user;
48         EmpathyContact        *remote_contact;
49         GList                 *members;
50         TpChannel             *channel;
51         gboolean               listing_pending_messages;
52         /* Queue of messages not signalled yet */
53         GQueue                *messages_queue;
54         /* Queue of messages signalled but not acked yet */
55         GQueue                *pending_messages_queue;
56         gboolean               had_properties_list;
57         GPtrArray             *properties;
58         TpChannelPasswordFlags password_flags;
59         /* TRUE if we fetched the password flag of the channel or if it's not needed
60          * (channel doesn't implement the Password interface) */
61         gboolean               got_password_flags;
62         gboolean               ready;
63 } EmpathyTpChatPriv;
64
65 static void tp_chat_iface_init         (EmpathyContactListIface *iface);
66
67 enum {
68         PROP_0,
69         PROP_CHANNEL,
70         PROP_REMOTE_CONTACT,
71         PROP_PASSWORD_NEEDED,
72         PROP_READY,
73 };
74
75 enum {
76         MESSAGE_RECEIVED,
77         SEND_ERROR,
78         CHAT_STATE_CHANGED,
79         PROPERTY_CHANGED,
80         DESTROY,
81         LAST_SIGNAL
82 };
83
84 static guint signals[LAST_SIGNAL];
85
86 G_DEFINE_TYPE_WITH_CODE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT,
87                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST,
88                                                 tp_chat_iface_init));
89
90 static void acknowledge_messages (EmpathyTpChat *chat, GArray *ids);
91
92 static void
93 tp_chat_invalidated_cb (TpProxy       *proxy,
94                         guint          domain,
95                         gint           code,
96                         gchar         *message,
97                         EmpathyTpChat *chat)
98 {
99         DEBUG ("Channel invalidated: %s", message);
100         g_signal_emit (chat, signals[DESTROY], 0);
101 }
102
103 static void
104 tp_chat_async_cb (TpChannel *proxy,
105                   const GError *error,
106                   gpointer user_data,
107                   GObject *weak_object)
108 {
109         if (error) {
110                 DEBUG ("Error %s: %s", (gchar *) user_data, error->message);
111         }
112 }
113
114 static void
115 tp_chat_add (EmpathyContactList *list,
116              EmpathyContact     *contact,
117              const gchar        *message)
118 {
119         EmpathyTpChatPriv *priv = GET_PRIV (list);
120         TpHandle           handle;
121         GArray             handles = {(gchar *) &handle, 1};
122
123         g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
124         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
125
126         handle = empathy_contact_get_handle (contact);
127         tp_cli_channel_interface_group_call_add_members (priv->channel, -1,
128                                                          &handles, NULL,
129                                                          NULL, NULL, NULL,
130                                                          NULL);
131 }
132
133 static void
134 tp_chat_remove (EmpathyContactList *list,
135                 EmpathyContact     *contact,
136                 const gchar        *message)
137 {
138         EmpathyTpChatPriv *priv = GET_PRIV (list);
139         TpHandle           handle;
140         GArray             handles = {(gchar *) &handle, 1};
141
142         g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
143         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
144
145         handle = empathy_contact_get_handle (contact);
146         tp_cli_channel_interface_group_call_remove_members (priv->channel, -1,
147                                                             &handles, NULL,
148                                                             NULL, NULL, NULL,
149                                                             NULL);
150 }
151
152 static GList *
153 tp_chat_get_members (EmpathyContactList *list)
154 {
155         EmpathyTpChatPriv *priv = GET_PRIV (list);
156         GList             *members = NULL;
157
158         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL);
159
160         if (priv->members) {
161                 members = g_list_copy (priv->members);
162                 g_list_foreach (members, (GFunc) g_object_ref, NULL);
163         } else {
164                 members = g_list_prepend (members, g_object_ref (priv->user));
165                 if (priv->remote_contact != NULL)
166                         members = g_list_prepend (members, g_object_ref (priv->remote_contact));
167         }
168
169         return members;
170 }
171
172 static EmpathyContactMonitor *
173 tp_chat_get_monitor (EmpathyContactList *list)
174 {
175         EmpathyTpChatPriv *priv;
176
177         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL);
178
179         priv = GET_PRIV (list);
180
181         if (priv->contact_monitor == NULL) {
182                 priv->contact_monitor = empathy_contact_monitor_new_for_iface (list);
183         }
184
185         return priv->contact_monitor;
186 }
187
188 static void
189 tp_chat_emit_queued_messages (EmpathyTpChat *chat)
190 {
191         EmpathyTpChatPriv *priv = GET_PRIV (chat);
192         EmpathyMessage    *message;
193
194         /* Check if we can now emit some queued messages */
195         while ((message = g_queue_peek_head (priv->messages_queue)) != NULL) {
196                 if (empathy_message_get_sender (message) == NULL) {
197                         break;
198                 }
199
200                 DEBUG ("Queued message ready");
201                 g_queue_pop_head (priv->messages_queue);
202                 g_queue_push_tail (priv->pending_messages_queue, message);
203                 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
204         }
205 }
206
207 static void
208 tp_chat_got_sender_cb (EmpathyTpContactFactory *factory,
209                        EmpathyContact          *contact,
210                        const GError            *error,
211                        gpointer                 message,
212                        GObject                 *chat)
213 {
214         EmpathyTpChatPriv *priv = GET_PRIV (chat);
215
216         if (error) {
217                 DEBUG ("Error: %s", error->message);
218                 /* Do not block the message queue, just drop this message */
219                 g_queue_remove (priv->messages_queue, message);
220         } else {
221                 empathy_message_set_sender (message, contact);
222         }
223
224         tp_chat_emit_queued_messages (EMPATHY_TP_CHAT (chat));
225 }
226
227 static void
228 tp_chat_build_message (EmpathyTpChat *chat,
229                        gboolean       incoming,
230                        guint          id,
231                        guint          type,
232                        guint          timestamp,
233                        guint          from_handle,
234                        const gchar   *message_body,
235                        TpChannelTextMessageFlags flags)
236 {
237         EmpathyTpChatPriv *priv;
238         EmpathyMessage    *message;
239
240         priv = GET_PRIV (chat);
241
242         message = empathy_message_new (message_body);
243         empathy_message_set_tptype (message, type);
244         empathy_message_set_receiver (message, priv->user);
245         empathy_message_set_timestamp (message, timestamp);
246         empathy_message_set_id (message, id);
247         empathy_message_set_incoming (message, incoming);
248         empathy_message_set_flags (message, flags);
249
250         g_queue_push_tail (priv->messages_queue, message);
251
252         if (from_handle == 0) {
253                 empathy_message_set_sender (message, priv->user);
254                 tp_chat_emit_queued_messages (chat);
255         } else {
256                 empathy_tp_contact_factory_get_from_handle (priv->factory,
257                         from_handle,
258                         tp_chat_got_sender_cb,
259                         message, NULL, G_OBJECT (chat));
260         }
261 }
262
263 static void
264 tp_chat_received_cb (TpChannel   *channel,
265                      guint        message_id,
266                      guint        timestamp,
267                      guint        from_handle,
268                      guint        message_type,
269                      guint        message_flags,
270                      const gchar *message_body,
271                      gpointer     user_data,
272                      GObject     *chat_)
273 {
274         EmpathyTpChat *chat = EMPATHY_TP_CHAT (chat_);
275         EmpathyTpChatPriv *priv = GET_PRIV (chat);
276
277         if (priv->channel == NULL)
278                 return;
279
280         if (priv->listing_pending_messages) {
281                 return;
282         }
283
284         DEBUG ("Message received: %s", message_body);
285
286         if (message_flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT &&
287             !tp_strdiff (message_body, "")) {
288                 GArray *ids;
289
290                 DEBUG ("Empty message with NonTextContent, ignoring and acking.");
291
292                 ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
293                 g_array_append_val (ids, message_id);
294                 acknowledge_messages (chat, ids);
295                 g_array_free (ids, TRUE);
296
297                 return;
298         }
299
300         tp_chat_build_message (chat,
301                                TRUE,
302                                message_id,
303                                message_type,
304                                timestamp,
305                                from_handle,
306                                message_body,
307                                message_flags);
308 }
309
310 static void
311 tp_chat_sent_cb (TpChannel   *channel,
312                  guint        timestamp,
313                  guint        message_type,
314                  const gchar *message_body,
315                  gpointer     user_data,
316                  GObject     *chat_)
317 {
318         EmpathyTpChat *chat = EMPATHY_TP_CHAT (chat_);
319         EmpathyTpChatPriv *priv = GET_PRIV (chat);
320
321         if (priv->channel == NULL)
322                 return;
323
324         DEBUG ("Message sent: %s", message_body);
325
326         tp_chat_build_message (chat,
327                                FALSE,
328                                0,
329                                message_type,
330                                timestamp,
331                                0,
332                                message_body,
333                                0);
334 }
335
336 static void
337 tp_chat_send_error_cb (TpChannel   *channel,
338                        guint        error_code,
339                        guint        timestamp,
340                        guint        message_type,
341                        const gchar *message_body,
342                        gpointer     user_data,
343                        GObject     *chat)
344 {
345         EmpathyTpChatPriv *priv = GET_PRIV (chat);
346
347         if (priv->channel == NULL)
348                 return;
349
350         DEBUG ("Error sending '%s' (%d)", message_body, error_code);
351
352         g_signal_emit (chat, signals[SEND_ERROR], 0, message_body, error_code);
353 }
354
355 static void
356 tp_chat_send_cb (TpChannel    *proxy,
357                  const GError *error,
358                  gpointer      user_data,
359                  GObject      *chat)
360 {
361         EmpathyMessage *message = EMPATHY_MESSAGE (user_data);
362
363         if (error) {
364                 DEBUG ("Error: %s", error->message);
365                 g_signal_emit (chat, signals[SEND_ERROR], 0,
366                                empathy_message_get_body (message),
367                                TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN);
368         }
369 }
370
371 typedef struct {
372         EmpathyTpChat *chat;
373         TpChannelChatState state;
374 } StateChangedData;
375
376 static void
377 tp_chat_state_changed_got_contact_cb (EmpathyTpContactFactory *factory,
378                                       EmpathyContact          *contact,
379                                       const GError            *error,
380                                       gpointer                 user_data,
381                                       GObject                 *chat)
382 {
383         TpChannelChatState state;
384
385         if (error) {
386                 DEBUG ("Error: %s", error->message);
387                 return;
388         }
389
390         state = GPOINTER_TO_UINT (user_data);
391         DEBUG ("Chat state changed for %s (%d): %d",
392                 empathy_contact_get_name (contact),
393                 empathy_contact_get_handle (contact), state);
394
395         g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
396 }
397
398 static void
399 tp_chat_state_changed_cb (TpChannel *channel,
400                           TpHandle   handle,
401                           TpChannelChatState state,
402                           gpointer   user_data,
403                           GObject   *chat)
404 {
405         EmpathyTpChatPriv *priv = GET_PRIV (chat);
406
407         empathy_tp_contact_factory_get_from_handle (priv->factory, handle,
408                 tp_chat_state_changed_got_contact_cb, GUINT_TO_POINTER (state),
409                 NULL, chat);
410 }
411
412 static void
413 tp_chat_list_pending_messages_cb (TpChannel       *channel,
414                                   const GPtrArray *messages_list,
415                                   const GError    *error,
416                                   gpointer         user_data,
417                                   GObject         *chat_)
418 {
419         EmpathyTpChat     *chat = EMPATHY_TP_CHAT (chat_);
420         EmpathyTpChatPriv *priv = GET_PRIV (chat);
421         guint              i;
422         GArray            *empty_non_text_content_ids = NULL;
423
424         priv->listing_pending_messages = FALSE;
425
426         if (priv->channel == NULL)
427                 return;
428
429         if (error) {
430                 DEBUG ("Error listing pending messages: %s", error->message);
431                 return;
432         }
433
434         for (i = 0; i < messages_list->len; i++) {
435                 GValueArray    *message_struct;
436                 const gchar    *message_body;
437                 guint           message_id;
438                 guint           timestamp;
439                 guint           from_handle;
440                 guint           message_type;
441                 guint           message_flags;
442
443                 message_struct = g_ptr_array_index (messages_list, i);
444
445                 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
446                 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
447                 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
448                 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
449                 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
450                 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
451
452                 DEBUG ("Message pending: %s", message_body);
453
454                 if (message_flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT &&
455                     !tp_strdiff (message_body, "")) {
456                         DEBUG ("Empty message with NonTextContent, ignoring and acking.");
457
458                         if (empty_non_text_content_ids == NULL) {
459                                 empty_non_text_content_ids = g_array_new (FALSE, FALSE, sizeof (guint));
460                         }
461
462                         g_array_append_val (empty_non_text_content_ids, message_id);
463                         continue;
464                 }
465
466                 tp_chat_build_message (chat,
467                                        TRUE,
468                                        message_id,
469                                        message_type,
470                                        timestamp,
471                                        from_handle,
472                                        message_body,
473                                        message_flags);
474         }
475
476         if (empty_non_text_content_ids != NULL) {
477                 acknowledge_messages (chat, empty_non_text_content_ids);
478                 g_array_free (empty_non_text_content_ids, TRUE);
479         }
480 }
481
482 static void
483 tp_chat_property_flags_changed_cb (TpProxy         *proxy,
484                                    const GPtrArray *properties,
485                                    gpointer         user_data,
486                                    GObject         *chat)
487 {
488         EmpathyTpChatPriv *priv = GET_PRIV (chat);
489         guint              i, j;
490
491         if (priv->channel == NULL)
492                 return;
493
494         if (!priv->had_properties_list || !properties) {
495                 return;
496         }
497
498         for (i = 0; i < properties->len; i++) {
499                 GValueArray           *prop_struct;
500                 EmpathyTpChatProperty *property;
501                 guint                  id;
502                 guint                  flags;
503
504                 prop_struct = g_ptr_array_index (properties, i);
505                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
506                 flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 1));
507
508                 for (j = 0; j < priv->properties->len; j++) {
509                         property = g_ptr_array_index (priv->properties, j);
510                         if (property->id == id) {
511                                 property->flags = flags;
512                                 DEBUG ("property %s flags changed: %d",
513                                         property->name, property->flags);
514                                 break;
515                         }
516                 }
517         }
518 }
519
520 static void
521 tp_chat_properties_changed_cb (TpProxy         *proxy,
522                                const GPtrArray *properties,
523                                gpointer         user_data,
524                                GObject         *chat)
525 {
526         EmpathyTpChatPriv *priv = GET_PRIV (chat);
527         guint              i, j;
528
529         if (priv->channel == NULL)
530                 return;
531
532         if (!priv->had_properties_list || !properties) {
533                 return;
534         }
535
536         for (i = 0; i < properties->len; i++) {
537                 GValueArray           *prop_struct;
538                 EmpathyTpChatProperty *property;
539                 guint                  id;
540                 GValue                *src_value;
541
542                 prop_struct = g_ptr_array_index (properties, i);
543                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
544                 src_value = g_value_get_boxed (g_value_array_get_nth (prop_struct, 1));
545
546                 for (j = 0; j < priv->properties->len; j++) {
547                         property = g_ptr_array_index (priv->properties, j);
548                         if (property->id == id) {
549                                 if (property->value) {
550                                         g_value_copy (src_value, property->value);
551                                 } else {
552                                         property->value = tp_g_value_slice_dup (src_value);
553                                 }
554
555                                 DEBUG ("property %s changed", property->name);
556                                 g_signal_emit (chat, signals[PROPERTY_CHANGED], 0,
557                                                property->name, property->value);
558                                 break;
559                         }
560                 }
561         }
562 }
563
564 static void
565 tp_chat_get_properties_cb (TpProxy         *proxy,
566                            const GPtrArray *properties,
567                            const GError    *error,
568                            gpointer         user_data,
569                            GObject         *chat)
570 {
571         if (error) {
572                 DEBUG ("Error getting properties: %s", error->message);
573                 return;
574         }
575
576         tp_chat_properties_changed_cb (proxy, properties, user_data, chat);
577 }
578
579 static void
580 tp_chat_list_properties_cb (TpProxy         *proxy,
581                             const GPtrArray *properties,
582                             const GError    *error,
583                             gpointer         user_data,
584                             GObject         *chat)
585 {
586         EmpathyTpChatPriv *priv = GET_PRIV (chat);
587         GArray            *ids;
588         guint              i;
589
590         if (priv->channel == NULL)
591                 return;
592
593         priv->had_properties_list = TRUE;
594
595         if (error) {
596                 DEBUG ("Error listing properties: %s", error->message);
597                 return;
598         }
599
600         ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), properties->len);
601         priv->properties = g_ptr_array_sized_new (properties->len);
602         for (i = 0; i < properties->len; i++) {
603                 GValueArray           *prop_struct;
604                 EmpathyTpChatProperty *property;
605
606                 prop_struct = g_ptr_array_index (properties, i);
607                 property = g_slice_new0 (EmpathyTpChatProperty);
608                 property->id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
609                 property->name = g_value_dup_string (g_value_array_get_nth (prop_struct, 1));
610                 property->flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 3));
611
612                 DEBUG ("Adding property name=%s id=%d flags=%d",
613                         property->name, property->id, property->flags);
614                 g_ptr_array_add (priv->properties, property);
615                 if (property->flags & TP_PROPERTY_FLAG_READ) {
616                         g_array_append_val (ids, property->id);
617                 }
618         }
619
620         tp_cli_properties_interface_call_get_properties (proxy, -1,
621                                                          ids,
622                                                          tp_chat_get_properties_cb,
623                                                          NULL, NULL,
624                                                          chat);
625
626         g_array_free (ids, TRUE);
627 }
628
629 void
630 empathy_tp_chat_set_property (EmpathyTpChat *chat,
631                               const gchar   *name,
632                               const GValue  *value)
633 {
634         EmpathyTpChatPriv     *priv = GET_PRIV (chat);
635         EmpathyTpChatProperty *property;
636         guint                  i;
637
638         if (!priv->had_properties_list) {
639                 return;
640         }
641
642         for (i = 0; i < priv->properties->len; i++) {
643                 property = g_ptr_array_index (priv->properties, i);
644                 if (!tp_strdiff (property->name, name)) {
645                         GPtrArray   *properties;
646                         GValueArray *prop;
647                         GValue       id = {0, };
648                         GValue       dest_value = {0, };
649
650                         if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) {
651                                 break;
652                         }
653
654                         g_value_init (&id, G_TYPE_UINT);
655                         g_value_init (&dest_value, G_TYPE_VALUE);
656                         g_value_set_uint (&id, property->id);
657                         g_value_set_boxed (&dest_value, value);
658
659                         prop = g_value_array_new (2);
660                         g_value_array_append (prop, &id);
661                         g_value_array_append (prop, &dest_value);
662
663                         properties = g_ptr_array_sized_new (1);
664                         g_ptr_array_add (properties, prop);
665
666                         DEBUG ("Set property %s", name);
667                         tp_cli_properties_interface_call_set_properties (priv->channel, -1,
668                                                                          properties,
669                                                                          (tp_cli_properties_interface_callback_for_set_properties)
670                                                                          tp_chat_async_cb,
671                                                                          "Seting property", NULL,
672                                                                          G_OBJECT (chat));
673
674                         g_ptr_array_free (properties, TRUE);
675                         g_value_array_free (prop);
676
677                         break;
678                 }
679         }
680 }
681
682 EmpathyTpChatProperty *
683 empathy_tp_chat_get_property (EmpathyTpChat *chat,
684                               const gchar   *name)
685 {
686         EmpathyTpChatPriv     *priv = GET_PRIV (chat);
687         EmpathyTpChatProperty *property;
688         guint                  i;
689
690         if (!priv->had_properties_list) {
691                 return NULL;
692         }
693
694         for (i = 0; i < priv->properties->len; i++) {
695                 property = g_ptr_array_index (priv->properties, i);
696                 if (!tp_strdiff (property->name, name)) {
697                         return property;
698                 }
699         }
700
701         return NULL;
702 }
703
704 GPtrArray *
705 empathy_tp_chat_get_properties (EmpathyTpChat *chat)
706 {
707         EmpathyTpChatPriv *priv = GET_PRIV (chat);
708
709         return priv->properties;
710 }
711
712 static void
713 tp_chat_dispose (GObject *object)
714 {
715         EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
716         EmpathyTpChatPriv *priv = GET_PRIV (self);
717
718         if (priv->dispose_has_run)
719                 return;
720
721         priv->dispose_has_run = TRUE;
722
723         if (priv->channel != NULL) {
724                 g_signal_handlers_disconnect_by_func (priv->channel,
725                         tp_chat_invalidated_cb, self);
726                 g_object_unref (priv->channel);
727         }
728         priv->channel = NULL;
729
730         if (priv->remote_contact != NULL)
731                 g_object_unref (priv->remote_contact);
732         priv->remote_contact = NULL;
733
734         if (priv->factory != NULL)
735                 g_object_unref (priv->factory);
736         priv->factory = NULL;
737
738         if (priv->user != NULL)
739                 g_object_unref (priv->user);
740         priv->user = NULL;
741
742         if (priv->contact_monitor)
743                 g_object_unref (priv->contact_monitor);
744         priv->contact_monitor = NULL;
745
746         g_queue_foreach (priv->messages_queue, (GFunc) g_object_unref, NULL);
747         g_queue_clear (priv->messages_queue);
748
749         g_queue_foreach (priv->pending_messages_queue,
750                 (GFunc) g_object_unref, NULL);
751         g_queue_clear (priv->pending_messages_queue);
752
753         if (G_OBJECT_CLASS (empathy_tp_chat_parent_class)->dispose)
754                 G_OBJECT_CLASS (empathy_tp_chat_parent_class)->dispose (object);
755 }
756
757 static void
758 tp_chat_finalize (GObject *object)
759 {
760         EmpathyTpChatPriv *priv = GET_PRIV (object);
761         guint              i;
762
763         DEBUG ("Finalize: %p", object);
764
765         if (priv->properties) {
766                 for (i = 0; i < priv->properties->len; i++) {
767                         EmpathyTpChatProperty *property;
768
769                         property = g_ptr_array_index (priv->properties, i);
770                         g_free (property->name);
771                         if (property->value) {
772                                 tp_g_value_slice_free (property->value);
773                         }
774                         g_slice_free (EmpathyTpChatProperty, property);
775                 }
776                 g_ptr_array_free (priv->properties, TRUE);
777         }
778
779         g_queue_free (priv->messages_queue);
780         g_queue_free (priv->pending_messages_queue);
781
782         G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
783 }
784
785 static void
786 tp_chat_check_if_ready (EmpathyTpChat *chat)
787 {
788         EmpathyTpChatPriv *priv = GET_PRIV (chat);
789
790         if (priv->ready)
791                 return;
792
793         if (priv->user == NULL)
794                 return;
795
796         if (!priv->got_password_flags)
797                 return;
798
799         /* We need either the members (room) or the remote contact (private chat).
800          * If the chat is protected by a password we can't get these information so
801          * consider the chat as ready so it can be presented to the user. */
802         if (!empathy_tp_chat_password_needed (chat) && priv->members == NULL &&
803             priv->remote_contact == NULL)
804                 return;
805
806         DEBUG ("Ready!");
807
808         tp_cli_channel_type_text_connect_to_received (priv->channel,
809                                                       tp_chat_received_cb,
810                                                       NULL, NULL,
811                                                       G_OBJECT (chat), NULL);
812         priv->listing_pending_messages = TRUE;
813         tp_cli_channel_type_text_call_list_pending_messages (priv->channel, -1,
814                                                              FALSE,
815                                                              tp_chat_list_pending_messages_cb,
816                                                              NULL, NULL,
817                                                              G_OBJECT (chat));
818
819         tp_cli_channel_type_text_connect_to_sent (priv->channel,
820                                                   tp_chat_sent_cb,
821                                                   NULL, NULL,
822                                                   G_OBJECT (chat), NULL);
823         tp_cli_channel_type_text_connect_to_send_error (priv->channel,
824                                                         tp_chat_send_error_cb,
825                                                         NULL, NULL,
826                                                         G_OBJECT (chat), NULL);
827         tp_cli_channel_interface_chat_state_connect_to_chat_state_changed (priv->channel,
828                                                                            tp_chat_state_changed_cb,
829                                                                            NULL, NULL,
830                                                                            G_OBJECT (chat), NULL);
831         priv->ready = TRUE;
832         g_object_notify (G_OBJECT (chat), "ready");
833 }
834
835 static void
836 tp_chat_update_remote_contact (EmpathyTpChat *chat)
837 {
838         EmpathyTpChatPriv *priv = GET_PRIV (chat);
839         EmpathyContact *contact = NULL;
840         TpHandle self_handle;
841         TpHandleType handle_type;
842         GList *l;
843
844         /* If this is a named chatroom, never pretend it is a private chat */
845         tp_channel_get_handle (priv->channel, &handle_type);
846         if (handle_type == TP_HANDLE_TYPE_ROOM) {
847                 return;
848         }
849
850         /* This is an MSN-like chat where anyone can join the chat at anytime.
851          * If there is only one non-self contact member, we are in a private
852          * chat and we set the "remote-contact" property to that contact. If
853          * there are more, set the "remote-contact" property to NULL and the
854          * UI will display a contact list. */
855         self_handle = tp_channel_group_get_self_handle (priv->channel);
856         for (l = priv->members; l; l = l->next) {
857                 /* Skip self contact if member */
858                 if (empathy_contact_get_handle (l->data) == self_handle) {
859                         continue;
860                 }
861
862                 /* We have more than one remote contact, break */
863                 if (contact != NULL) {
864                         contact = NULL;
865                         break;
866                 }
867
868                 /* If we didn't find yet a remote contact, keep this one */
869                 contact = l->data;
870         }
871
872         if (priv->remote_contact == contact) {
873                 return;
874         }
875
876         DEBUG ("Changing remote contact from %p to %p",
877                 priv->remote_contact, contact);
878
879         if (priv->remote_contact) {
880                 g_object_unref (priv->remote_contact);
881         }
882
883         priv->remote_contact = contact ? g_object_ref (contact) : NULL;
884         g_object_notify (G_OBJECT (chat), "remote-contact");
885 }
886
887 static void
888 tp_chat_got_added_contacts_cb (EmpathyTpContactFactory *factory,
889                                guint                    n_contacts,
890                                EmpathyContact * const * contacts,
891                                guint                    n_failed,
892                                const TpHandle          *failed,
893                                const GError            *error,
894                                gpointer                 user_data,
895                                GObject                 *chat)
896 {
897         EmpathyTpChatPriv *priv = GET_PRIV (chat);
898         guint i;
899         const TpIntSet *members;
900         TpHandle handle;
901         EmpathyContact *contact;
902
903         if (error) {
904                 DEBUG ("Error: %s", error->message);
905                 return;
906         }
907
908         members = tp_channel_group_get_members (priv->channel);
909         for (i = 0; i < n_contacts; i++) {
910                 contact = contacts[i];
911                 handle = empathy_contact_get_handle (contact);
912
913                 /* Make sure the contact is still member */
914                 if (tp_intset_is_member (members, handle)) {
915                         priv->members = g_list_prepend (priv->members,
916                                 g_object_ref (contact));
917                         g_signal_emit_by_name (chat, "members-changed",
918                                                contact, NULL, 0, NULL, TRUE);
919                 }
920         }
921
922         tp_chat_update_remote_contact (EMPATHY_TP_CHAT (chat));
923         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
924 }
925
926 static EmpathyContact *
927 chat_lookup_contact (EmpathyTpChat *chat,
928                      TpHandle       handle,
929                      gboolean       remove_)
930 {
931         EmpathyTpChatPriv *priv = GET_PRIV (chat);
932         GList *l;
933
934         for (l = priv->members; l; l = l->next) {
935                 EmpathyContact *c = l->data;
936
937                 if (empathy_contact_get_handle (c) != handle) {
938                         continue;
939                 }
940
941                 if (remove_) {
942                         /* Caller takes the reference. */
943                         priv->members = g_list_delete_link (priv->members, l);
944                 } else {
945                         g_object_ref (c);
946                 }
947
948                 return c;
949         }
950
951         return NULL;
952 }
953
954 typedef struct
955 {
956     TpHandle old_handle;
957     guint reason;
958     gchar *message;
959 } ContactRenameData;
960
961 static ContactRenameData *
962 contact_rename_data_new (TpHandle handle,
963                          guint reason,
964                          const gchar* message)
965 {
966         ContactRenameData *data = g_new (ContactRenameData, 1);
967         data->old_handle = handle;
968         data->reason = reason;
969         data->message = g_strdup (message);
970
971         return data;
972 }
973
974 static void
975 contact_rename_data_free (ContactRenameData* data)
976 {
977         g_free (data->message);
978         g_free (data);
979 }
980
981 static void
982 tp_chat_got_renamed_contacts_cb (EmpathyTpContactFactory *factory,
983                                  guint                    n_contacts,
984                                  EmpathyContact * const * contacts,
985                                  guint                    n_failed,
986                                  const TpHandle          *failed,
987                                  const GError            *error,
988                                  gpointer                 user_data,
989                                  GObject                 *chat)
990 {
991         EmpathyTpChatPriv *priv = GET_PRIV (chat);
992         const TpIntSet *members;
993         TpHandle handle;
994         EmpathyContact *old = NULL, *new = NULL;
995         ContactRenameData *rename_data = (ContactRenameData *) user_data;
996
997         if (error) {
998                 DEBUG ("Error: %s", error->message);
999                 return;
1000         }
1001
1002         /* renamed members can only be delivered one at a time */
1003         g_warn_if_fail (n_contacts == 1);
1004
1005         new = contacts[0];
1006
1007         members = tp_channel_group_get_members (priv->channel);
1008         handle = empathy_contact_get_handle (new);
1009
1010         old = chat_lookup_contact (EMPATHY_TP_CHAT (chat),
1011                                    rename_data->old_handle, TRUE);
1012
1013         /* Make sure the contact is still member */
1014         if (tp_intset_is_member (members, handle)) {
1015                 priv->members = g_list_prepend (priv->members,
1016                         g_object_ref (new));
1017
1018                 if (old != NULL) {
1019                         g_signal_emit_by_name (chat, "member-renamed",
1020                                                old, new, rename_data->reason,
1021                                                rename_data->message);
1022                         g_object_unref (old);
1023                 }
1024         }
1025
1026         tp_chat_update_remote_contact (EMPATHY_TP_CHAT (chat));
1027         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
1028 }
1029
1030
1031 static void
1032 tp_chat_group_members_changed_cb (TpChannel     *self,
1033                                   gchar         *message,
1034                                   GArray        *added,
1035                                   GArray        *removed,
1036                                   GArray        *local_pending,
1037                                   GArray        *remote_pending,
1038                                   guint          actor,
1039                                   guint          reason,
1040                                   EmpathyTpChat *chat)
1041 {
1042         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1043         EmpathyContact *contact;
1044         EmpathyContact *actor_contact = NULL;
1045         guint i;
1046         ContactRenameData *rename_data;
1047         TpHandle old_handle;
1048
1049         /* Contact renamed */
1050         if (reason == TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED) {
1051                 /* there can only be a single 'added' and a single 'removed' handle */
1052                 g_warn_if_fail (removed->len == 1);
1053                 g_warn_if_fail (added->len == 1);
1054
1055                 old_handle = g_array_index (removed, guint, 0);
1056
1057                 rename_data = contact_rename_data_new (old_handle, reason, message);
1058                 empathy_tp_contact_factory_get_from_handles (priv->factory,
1059                         added->len, (TpHandle *) added->data,
1060                         tp_chat_got_renamed_contacts_cb,
1061                         rename_data, (GDestroyNotify) contact_rename_data_free,
1062                         G_OBJECT (chat));
1063                 return;
1064         }
1065
1066         if (actor != 0) {
1067                 actor_contact = chat_lookup_contact (chat, actor, FALSE);
1068                 if (actor_contact == NULL) {
1069                         /* FIXME: handle this a tad more gracefully: perhaps
1070                          * the actor was a server op. We could use the
1071                          * contact-ids detail of MembersChangedDetailed.
1072                          */
1073                         DEBUG ("actor %u not a channel member", actor);
1074                 }
1075         }
1076
1077         /* Remove contacts that are not members anymore */
1078         for (i = 0; i < removed->len; i++) {
1079                 contact = chat_lookup_contact (chat,
1080                         g_array_index (removed, TpHandle, i), TRUE);
1081
1082                 if (contact != NULL) {
1083                         g_signal_emit_by_name (chat, "members-changed", contact,
1084                                                actor_contact, reason, message,
1085                                                FALSE);
1086                         g_object_unref (contact);
1087                 }
1088         }
1089
1090         /* Request added contacts */
1091         if (added->len > 0) {
1092                 empathy_tp_contact_factory_get_from_handles (priv->factory,
1093                         added->len, (TpHandle *) added->data,
1094                         tp_chat_got_added_contacts_cb, NULL, NULL,
1095                         G_OBJECT (chat));
1096         }
1097
1098         tp_chat_update_remote_contact (chat);
1099
1100         if (actor_contact != NULL) {
1101                 g_object_unref (actor_contact);
1102         }
1103 }
1104
1105 static void
1106 tp_chat_got_remote_contact_cb (EmpathyTpContactFactory *factory,
1107                                EmpathyContact          *contact,
1108                                const GError            *error,
1109                                gpointer                 user_data,
1110                                GObject                 *chat)
1111 {
1112         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1113
1114         if (error) {
1115                 DEBUG ("Error: %s", error->message);
1116                 empathy_tp_chat_close (EMPATHY_TP_CHAT (chat));
1117                 return;
1118         }
1119
1120         priv->remote_contact = g_object_ref (contact);
1121         g_object_notify (chat, "remote-contact");
1122
1123         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
1124 }
1125
1126 static void
1127 tp_chat_got_self_contact_cb (EmpathyTpContactFactory *factory,
1128                              EmpathyContact          *contact,
1129                              const GError            *error,
1130                              gpointer                 user_data,
1131                              GObject                 *chat)
1132 {
1133         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1134
1135         if (error) {
1136                 DEBUG ("Error: %s", error->message);
1137                 empathy_tp_chat_close (EMPATHY_TP_CHAT (chat));
1138                 return;
1139         }
1140
1141         priv->user = g_object_ref (contact);
1142         empathy_contact_set_is_user (priv->user, TRUE);
1143         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
1144 }
1145
1146 static void
1147 password_flags_changed_cb (TpChannel *channel,
1148     guint added,
1149     guint removed,
1150     gpointer user_data,
1151     GObject *weak_object)
1152 {
1153         EmpathyTpChat *self = EMPATHY_TP_CHAT (weak_object);
1154         EmpathyTpChatPriv *priv = GET_PRIV (self);
1155         gboolean was_needed, needed;
1156
1157         was_needed = empathy_tp_chat_password_needed (self);
1158
1159         priv->password_flags |= added;
1160         priv->password_flags ^= removed;
1161
1162         needed = empathy_tp_chat_password_needed (self);
1163
1164         if (was_needed != needed)
1165                 g_object_notify (G_OBJECT (self), "password-needed");
1166 }
1167
1168 static void
1169 got_password_flags_cb (TpChannel *proxy,
1170                              guint password_flags,
1171                              const GError *error,
1172                              gpointer user_data,
1173                              GObject *weak_object)
1174 {
1175         EmpathyTpChat *self = EMPATHY_TP_CHAT (weak_object);
1176         EmpathyTpChatPriv *priv = GET_PRIV (self);
1177
1178         priv->got_password_flags = TRUE;
1179         priv->password_flags = password_flags;
1180
1181         tp_chat_check_if_ready (EMPATHY_TP_CHAT (self));
1182 }
1183
1184 static GObject *
1185 tp_chat_constructor (GType                  type,
1186                      guint                  n_props,
1187                      GObjectConstructParam *props)
1188 {
1189         GObject           *chat;
1190         EmpathyTpChatPriv *priv;
1191         TpConnection      *connection;
1192         TpHandle           handle;
1193
1194         chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
1195
1196         priv = GET_PRIV (chat);
1197
1198         connection = tp_channel_borrow_connection (priv->channel);
1199         priv->factory = empathy_tp_contact_factory_dup_singleton (connection);
1200         g_signal_connect (priv->channel, "invalidated",
1201                           G_CALLBACK (tp_chat_invalidated_cb),
1202                           chat);
1203
1204         if (tp_proxy_has_interface_by_id (priv->channel,
1205                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
1206                 const TpIntSet *members;
1207                 GArray *handles;
1208
1209                 /* Get self contact from the group's self handle */
1210                 handle = tp_channel_group_get_self_handle (priv->channel);
1211                 empathy_tp_contact_factory_get_from_handle (priv->factory,
1212                         handle, tp_chat_got_self_contact_cb,
1213                         NULL, NULL, chat);
1214
1215                 /* Get initial member contacts */
1216                 members = tp_channel_group_get_members (priv->channel);
1217                 handles = tp_intset_to_array (members);
1218                 empathy_tp_contact_factory_get_from_handles (priv->factory,
1219                         handles->len, (TpHandle *) handles->data,
1220                         tp_chat_got_added_contacts_cb, NULL, NULL, chat);
1221
1222                 g_signal_connect (priv->channel, "group-members-changed",
1223                         G_CALLBACK (tp_chat_group_members_changed_cb), chat);
1224         } else {
1225                 /* Get the self contact from the connection's self handle */
1226                 handle = tp_connection_get_self_handle (connection);
1227                 empathy_tp_contact_factory_get_from_handle (priv->factory,
1228                         handle, tp_chat_got_self_contact_cb,
1229                         NULL, NULL, chat);
1230
1231                 /* Get the remote contact */
1232                 handle = tp_channel_get_handle (priv->channel, NULL);
1233                 empathy_tp_contact_factory_get_from_handle (priv->factory,
1234                         handle, tp_chat_got_remote_contact_cb,
1235                         NULL, NULL, chat);
1236         }
1237
1238         if (tp_proxy_has_interface_by_id (priv->channel,
1239                                           TP_IFACE_QUARK_PROPERTIES_INTERFACE)) {
1240                 tp_cli_properties_interface_call_list_properties (priv->channel, -1,
1241                                                                   tp_chat_list_properties_cb,
1242                                                                   NULL, NULL,
1243                                                                   G_OBJECT (chat));
1244                 tp_cli_properties_interface_connect_to_properties_changed (priv->channel,
1245                                                                            tp_chat_properties_changed_cb,
1246                                                                            NULL, NULL,
1247                                                                            G_OBJECT (chat), NULL);
1248                 tp_cli_properties_interface_connect_to_property_flags_changed (priv->channel,
1249                                                                                tp_chat_property_flags_changed_cb,
1250                                                                                NULL, NULL,
1251                                                                                G_OBJECT (chat), NULL);
1252         }
1253
1254         /* Check if the chat is password protected */
1255         if (tp_proxy_has_interface_by_id (priv->channel,
1256                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_PASSWORD)) {
1257                 priv->got_password_flags = FALSE;
1258
1259                 tp_cli_channel_interface_password_connect_to_password_flags_changed
1260                         (priv->channel, password_flags_changed_cb, chat, NULL,
1261                          G_OBJECT (chat), NULL);
1262
1263                 tp_cli_channel_interface_password_call_get_password_flags
1264                         (priv->channel, -1, got_password_flags_cb, chat, NULL, chat);
1265         } else {
1266                 /* No Password interface, so no need to fetch the password flags */
1267                 priv->got_password_flags = TRUE;
1268         }
1269
1270         return chat;
1271 }
1272
1273 static void
1274 tp_chat_get_property (GObject    *object,
1275                       guint       param_id,
1276                       GValue     *value,
1277                       GParamSpec *pspec)
1278 {
1279         EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
1280         EmpathyTpChatPriv *priv = GET_PRIV (object);
1281
1282         switch (param_id) {
1283         case PROP_CHANNEL:
1284                 g_value_set_object (value, priv->channel);
1285                 break;
1286         case PROP_REMOTE_CONTACT:
1287                 g_value_set_object (value, priv->remote_contact);
1288                 break;
1289         case PROP_READY:
1290                 g_value_set_boolean (value, priv->ready);
1291                 break;
1292         case PROP_PASSWORD_NEEDED:
1293                 g_value_set_boolean (value, empathy_tp_chat_password_needed (self));
1294                 break;
1295         default:
1296                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1297                 break;
1298         };
1299 }
1300
1301 static void
1302 tp_chat_set_property (GObject      *object,
1303                       guint         param_id,
1304                       const GValue *value,
1305                       GParamSpec   *pspec)
1306 {
1307         EmpathyTpChatPriv *priv = GET_PRIV (object);
1308
1309         switch (param_id) {
1310         case PROP_CHANNEL:
1311                 priv->channel = g_value_dup_object (value);
1312                 break;
1313         default:
1314                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1315                 break;
1316         };
1317 }
1318
1319 static void
1320 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
1321 {
1322         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1323
1324         object_class->dispose = tp_chat_dispose;
1325         object_class->finalize = tp_chat_finalize;
1326         object_class->constructor = tp_chat_constructor;
1327         object_class->get_property = tp_chat_get_property;
1328         object_class->set_property = tp_chat_set_property;
1329
1330         g_object_class_install_property (object_class,
1331                                          PROP_CHANNEL,
1332                                          g_param_spec_object ("channel",
1333                                                               "telepathy channel",
1334                                                               "The text channel for the chat",
1335                                                               TP_TYPE_CHANNEL,
1336                                                               G_PARAM_READWRITE |
1337                                                               G_PARAM_CONSTRUCT_ONLY));
1338
1339         g_object_class_install_property (object_class,
1340                                          PROP_REMOTE_CONTACT,
1341                                          g_param_spec_object ("remote-contact",
1342                                                               "The remote contact",
1343                                                               "The remote contact if there is no group iface on the channel",
1344                                                               EMPATHY_TYPE_CONTACT,
1345                                                               G_PARAM_READABLE));
1346
1347         g_object_class_install_property (object_class,
1348                                          PROP_READY,
1349                                          g_param_spec_boolean ("ready",
1350                                                                "Is the object ready",
1351                                                                "This object can't be used until this becomes true",
1352                                                                FALSE,
1353                                                                G_PARAM_READABLE));
1354
1355         g_object_class_install_property (object_class,
1356                                          PROP_PASSWORD_NEEDED,
1357                                          g_param_spec_boolean ("password-needed",
1358                                                                "password needed",
1359                                                                "TRUE if a password is needed to join the channel",
1360                                                                FALSE,
1361                                                                G_PARAM_READABLE));
1362
1363         /* Signals */
1364         signals[MESSAGE_RECEIVED] =
1365                 g_signal_new ("message-received",
1366                               G_TYPE_FROM_CLASS (klass),
1367                               G_SIGNAL_RUN_LAST,
1368                               0,
1369                               NULL, NULL,
1370                               g_cclosure_marshal_VOID__OBJECT,
1371                               G_TYPE_NONE,
1372                               1, EMPATHY_TYPE_MESSAGE);
1373
1374         signals[SEND_ERROR] =
1375                 g_signal_new ("send-error",
1376                               G_TYPE_FROM_CLASS (klass),
1377                               G_SIGNAL_RUN_LAST,
1378                               0,
1379                               NULL, NULL,
1380                               _empathy_marshal_VOID__STRING_UINT,
1381                               G_TYPE_NONE,
1382                               2, G_TYPE_STRING, G_TYPE_UINT);
1383
1384         signals[CHAT_STATE_CHANGED] =
1385                 g_signal_new ("chat-state-changed",
1386                               G_TYPE_FROM_CLASS (klass),
1387                               G_SIGNAL_RUN_LAST,
1388                               0,
1389                               NULL, NULL,
1390                               _empathy_marshal_VOID__OBJECT_UINT,
1391                               G_TYPE_NONE,
1392                               2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT);
1393
1394         signals[PROPERTY_CHANGED] =
1395                 g_signal_new ("property-changed",
1396                               G_TYPE_FROM_CLASS (klass),
1397                               G_SIGNAL_RUN_LAST,
1398                               0,
1399                               NULL, NULL,
1400                               _empathy_marshal_VOID__STRING_BOXED,
1401                               G_TYPE_NONE,
1402                               2, G_TYPE_STRING, G_TYPE_VALUE);
1403
1404         signals[DESTROY] =
1405                 g_signal_new ("destroy",
1406                               G_TYPE_FROM_CLASS (klass),
1407                               G_SIGNAL_RUN_LAST,
1408                               0,
1409                               NULL, NULL,
1410                               g_cclosure_marshal_VOID__VOID,
1411                               G_TYPE_NONE,
1412                               0);
1413
1414         g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
1415 }
1416
1417 static void
1418 empathy_tp_chat_init (EmpathyTpChat *chat)
1419 {
1420         EmpathyTpChatPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chat,
1421                 EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv);
1422
1423         chat->priv = priv;
1424         priv->contact_monitor = NULL;
1425         priv->messages_queue = g_queue_new ();
1426         priv->pending_messages_queue = g_queue_new ();
1427 }
1428
1429 static void
1430 tp_chat_iface_init (EmpathyContactListIface *iface)
1431 {
1432         iface->add         = tp_chat_add;
1433         iface->remove      = tp_chat_remove;
1434         iface->get_members = tp_chat_get_members;
1435         iface->get_monitor = tp_chat_get_monitor;
1436 }
1437
1438 EmpathyTpChat *
1439 empathy_tp_chat_new (TpChannel *channel)
1440 {
1441         return g_object_new (EMPATHY_TYPE_TP_CHAT,
1442                              "channel", channel,
1443                              NULL);
1444 }
1445
1446 void
1447 empathy_tp_chat_close (EmpathyTpChat *chat) {
1448         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1449
1450         /* If there are still messages left, it'll come back..
1451          * We loose the ordering of sent messages though */
1452         tp_cli_channel_call_close (priv->channel, -1, tp_chat_async_cb,
1453                 "closing channel", NULL, NULL);
1454 }
1455
1456 const gchar *
1457 empathy_tp_chat_get_id (EmpathyTpChat *chat)
1458 {
1459         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1460         const gchar *id;
1461
1462
1463         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1464
1465         id = tp_channel_get_identifier (priv->channel);
1466         if (!EMP_STR_EMPTY (id))
1467                 return id;
1468         else if (priv->remote_contact)
1469                 return empathy_contact_get_id (priv->remote_contact);
1470         else
1471                 return NULL;
1472
1473 }
1474
1475 EmpathyContact *
1476 empathy_tp_chat_get_remote_contact (EmpathyTpChat *chat)
1477 {
1478         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1479
1480         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1481         g_return_val_if_fail (priv->ready, NULL);
1482
1483         return priv->remote_contact;
1484 }
1485
1486 TpChannel *
1487 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
1488 {
1489         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1490
1491         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1492
1493         return priv->channel;
1494 }
1495
1496 TpConnection *
1497 empathy_tp_chat_get_connection (EmpathyTpChat *chat)
1498 {
1499         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1500
1501         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1502
1503         return tp_channel_borrow_connection (priv->channel);
1504 }
1505
1506 gboolean
1507 empathy_tp_chat_is_ready (EmpathyTpChat *chat)
1508 {
1509         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1510
1511         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
1512
1513         return priv->ready;
1514 }
1515
1516 void
1517 empathy_tp_chat_send (EmpathyTpChat *chat,
1518                       EmpathyMessage *message)
1519 {
1520         EmpathyTpChatPriv        *priv = GET_PRIV (chat);
1521         const gchar              *message_body;
1522         TpChannelTextMessageType  message_type;
1523
1524         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1525         g_return_if_fail (EMPATHY_IS_MESSAGE (message));
1526         g_return_if_fail (priv->ready);
1527
1528         message_body = empathy_message_get_body (message);
1529         message_type = empathy_message_get_tptype (message);
1530
1531         DEBUG ("Sending message: %s", message_body);
1532         tp_cli_channel_type_text_call_send (priv->channel, -1,
1533                                             message_type,
1534                                             message_body,
1535                                             tp_chat_send_cb,
1536                                             g_object_ref (message),
1537                                             (GDestroyNotify) g_object_unref,
1538                                             G_OBJECT (chat));
1539 }
1540
1541 void
1542 empathy_tp_chat_set_state (EmpathyTpChat      *chat,
1543                            TpChannelChatState  state)
1544 {
1545         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1546
1547         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1548         g_return_if_fail (priv->ready);
1549
1550         if (tp_proxy_has_interface_by_id (priv->channel,
1551                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_CHAT_STATE)) {
1552                 DEBUG ("Set state: %d", state);
1553                 tp_cli_channel_interface_chat_state_call_set_chat_state (priv->channel, -1,
1554                                                                          state,
1555                                                                          tp_chat_async_cb,
1556                                                                          "setting chat state",
1557                                                                          NULL,
1558                                                                          G_OBJECT (chat));
1559         }
1560 }
1561
1562
1563 const GList *
1564 empathy_tp_chat_get_pending_messages (EmpathyTpChat *chat)
1565 {
1566         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1567
1568         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1569         g_return_val_if_fail (priv->ready, NULL);
1570
1571         return priv->pending_messages_queue->head;
1572 }
1573
1574 static void
1575 acknowledge_messages (EmpathyTpChat *chat, GArray *ids) {
1576         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1577
1578         tp_cli_channel_type_text_call_acknowledge_pending_messages (
1579                 priv->channel, -1, ids, tp_chat_async_cb,
1580                 "acknowledging received message", NULL, G_OBJECT (chat));
1581 }
1582
1583 void
1584 empathy_tp_chat_acknowledge_message (EmpathyTpChat *chat,
1585                                      EmpathyMessage *message) {
1586         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1587         GArray *message_ids;
1588         GList *m;
1589         guint id;
1590
1591         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1592         g_return_if_fail (priv->ready);
1593
1594         if (!empathy_message_is_incoming (message))
1595                 goto out;
1596
1597         message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
1598
1599         id = empathy_message_get_id (message);
1600         g_array_append_val (message_ids, id);
1601         acknowledge_messages (chat, message_ids);
1602         g_array_free (message_ids, TRUE);
1603
1604 out:
1605         m = g_queue_find (priv->pending_messages_queue, message);
1606         g_assert (m != NULL);
1607         g_queue_delete_link (priv->pending_messages_queue, m);
1608         g_object_unref (message);
1609 }
1610
1611 void
1612 empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat,
1613                                       const GList *messages) {
1614         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1615         /* Copy messages as the messges list (probably is) our own */
1616         GList *msgs = g_list_copy ((GList *) messages);
1617         GList *l;
1618         guint length;
1619         GArray *message_ids;
1620
1621         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1622         g_return_if_fail (priv->ready);
1623
1624         length = g_list_length ((GList *) messages);
1625
1626         if (length == 0)
1627                 return;
1628
1629         message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), length);
1630
1631         for (l = msgs; l != NULL; l = g_list_next (l)) {
1632                 GList *m;
1633
1634                 EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
1635
1636                 m = g_queue_find (priv->pending_messages_queue, message);
1637                 g_assert (m != NULL);
1638                 g_queue_delete_link (priv->pending_messages_queue, m);
1639
1640                 if (empathy_message_is_incoming (message)) {
1641                         guint id = empathy_message_get_id (message);
1642                         g_array_append_val (message_ids, id);
1643                 }
1644                 g_object_unref (message);
1645         }
1646
1647         if (message_ids->len > 0)
1648                 acknowledge_messages (chat, message_ids);
1649
1650         g_array_free (message_ids, TRUE);
1651         g_list_free (msgs);
1652 }
1653
1654 gboolean
1655 empathy_tp_chat_password_needed (EmpathyTpChat *self)
1656 {
1657         EmpathyTpChatPriv *priv = GET_PRIV (self);
1658
1659         return priv->password_flags & TP_CHANNEL_PASSWORD_FLAG_PROVIDE;
1660 }
1661
1662 static void
1663 provide_password_cb (TpChannel *channel,
1664                                       gboolean correct,
1665                                       const GError *error,
1666                                       gpointer user_data,
1667                                       GObject *weak_object)
1668 {
1669         GSimpleAsyncResult *result = user_data;
1670
1671         if (error != NULL) {
1672                 g_simple_async_result_set_from_error (result, error);
1673         }
1674         else if (!correct) {
1675                 /* The current D-Bus API is a bit weird so re-use the
1676                  * AuthenticationFailed error */
1677                 g_simple_async_result_set_error (result, TP_ERRORS,
1678                                                  TP_ERROR_AUTHENTICATION_FAILED, "Wrong password");
1679         }
1680
1681         g_simple_async_result_complete (result);
1682         g_object_unref (result);
1683 }
1684
1685 void
1686 empathy_tp_chat_provide_password_async (EmpathyTpChat *self,
1687                                                      const gchar *password,
1688                                                      GAsyncReadyCallback callback,
1689                                                      gpointer user_data)
1690 {
1691         EmpathyTpChatPriv *priv = GET_PRIV (self);
1692         GSimpleAsyncResult *result;
1693
1694         result = g_simple_async_result_new (G_OBJECT (self),
1695                                             callback, user_data,
1696                                             empathy_tp_chat_provide_password_finish);
1697
1698         tp_cli_channel_interface_password_call_provide_password
1699                 (priv->channel, -1, password, provide_password_cb, result,
1700                  NULL, G_OBJECT (self));
1701 }
1702
1703 gboolean
1704 empathy_tp_chat_provide_password_finish (EmpathyTpChat *self,
1705                                                       GAsyncResult *result,
1706                                                       GError **error)
1707 {
1708         if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
1709                 error))
1710                 return FALSE;
1711
1712         g_return_val_if_fail (g_simple_async_result_is_valid (result,
1713                                                               G_OBJECT (self), empathy_tp_chat_provide_password_finish), FALSE);
1714
1715         return TRUE;
1716 }