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