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