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