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