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