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