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