]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-chat.c
EmpathyTpFile: inherit from TpFileTransferChannel
[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-request-util.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 struct _EmpathyTpChatPrivate {
42         TpAccount             *account;
43         EmpathyContact        *user;
44         EmpathyContact        *remote_contact;
45         GList                 *members;
46         /* Queue of messages not signalled yet */
47         GQueue                *messages_queue;
48         /* Queue of messages signalled but not acked yet */
49         GQueue                *pending_messages_queue;
50         gboolean               had_properties_list;
51         GPtrArray             *properties;
52         gboolean               can_upgrade_to_muc;
53
54         GHashTable            *messages_being_sent;
55
56         /* GSimpleAsyncResult used when preparing EMPATHY_TP_CHAT_FEATURE_CORE */
57         GSimpleAsyncResult    *ready_result;
58 };
59
60 static void tp_chat_iface_init         (EmpathyContactListIface *iface);
61
62 enum {
63         PROP_0,
64         PROP_ACCOUNT,
65         PROP_REMOTE_CONTACT,
66         PROP_N_MESSAGES_SENDING,
67 };
68
69 enum {
70         MESSAGE_RECEIVED,
71         SEND_ERROR,
72         CHAT_STATE_CHANGED,
73         PROPERTY_CHANGED,
74         MESSAGE_ACKNOWLEDGED,
75         LAST_SIGNAL
76 };
77
78 static guint signals[LAST_SIGNAL];
79
80 G_DEFINE_TYPE_WITH_CODE (EmpathyTpChat, empathy_tp_chat, TP_TYPE_TEXT_CHANNEL,
81                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST,
82                                                 tp_chat_iface_init));
83
84 static void
85 tp_chat_set_delivery_status (EmpathyTpChat         *self,
86                              const gchar           *token,
87                              EmpathyDeliveryStatus  delivery_status)
88 {
89         TpDeliveryReportingSupportFlags flags =
90                 tp_text_channel_get_delivery_reporting_support (
91                         TP_TEXT_CHANNEL (self));
92
93         /* channel must support receiving failures and successes */
94         if (!tp_str_empty (token) &&
95             flags & TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_FAILURES &&
96             flags & TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_SUCCESSES) {
97
98                 DEBUG ("Delivery status (%s) = %u", token, delivery_status);
99
100                 switch (delivery_status) {
101                         case EMPATHY_DELIVERY_STATUS_NONE:
102                                 g_hash_table_remove (self->priv->messages_being_sent,
103                                         token);
104                                 break;
105
106                         default:
107                                 g_hash_table_insert (self->priv->messages_being_sent,
108                                         g_strdup (token),
109                                         GUINT_TO_POINTER (delivery_status));
110                                 break;
111                 }
112
113                 g_object_notify (G_OBJECT (self), "n-messages-sending");
114         }
115 }
116
117 static void tp_chat_prepare_ready_async (TpProxy *proxy,
118         const TpProxyFeature *feature,
119         GAsyncReadyCallback callback,
120         gpointer user_data);
121
122 static void
123 tp_chat_async_cb (TpChannel *proxy,
124                   const GError *error,
125                   gpointer user_data,
126                   GObject *weak_object)
127 {
128         if (error) {
129                 DEBUG ("Error %s: %s", (gchar *) user_data, error->message);
130         }
131 }
132
133 static void
134 create_conference_cb (GObject *source,
135                       GAsyncResult *result,
136                       gpointer user_data)
137 {
138         GError *error = NULL;
139
140         if (!tp_account_channel_request_create_channel_finish (
141                         TP_ACCOUNT_CHANNEL_REQUEST (source), result, &error)) {
142                 DEBUG ("Failed to create conference channel: %s", error->message);
143                 g_error_free (error);
144         }
145 }
146
147 static void
148 tp_chat_add (EmpathyContactList *list,
149              EmpathyContact     *contact,
150              const gchar        *message)
151 {
152         EmpathyTpChat *self = (EmpathyTpChat *) list;
153         TpChannel *channel = (TpChannel *) self;
154
155         if (tp_proxy_has_interface_by_id (self,
156                 TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
157                 TpHandle           handle;
158                 GArray             handles = {(gchar *) &handle, 1};
159
160                 g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
161                 g_return_if_fail (EMPATHY_IS_CONTACT (contact));
162
163                 handle = empathy_contact_get_handle (contact);
164                 tp_cli_channel_interface_group_call_add_members (channel,
165                         -1, &handles, NULL, NULL, NULL, NULL, NULL);
166         } else if (self->priv->can_upgrade_to_muc) {
167                 TpAccountChannelRequest *req;
168                 GHashTable        *props;
169                 const char        *object_path;
170                 GPtrArray          channels = { (gpointer *) &object_path, 1 };
171                 const char        *invitees[2] = { NULL, };
172
173                 invitees[0] = empathy_contact_get_id (contact);
174                 object_path = tp_proxy_get_object_path (self);
175
176                 props = tp_asv_new (
177                     TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
178                         TP_IFACE_CHANNEL_TYPE_TEXT,
179                     TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
180                         TP_HANDLE_TYPE_NONE,
181                     TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_CHANNELS,
182                         TP_ARRAY_TYPE_OBJECT_PATH_LIST, &channels,
183                     TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_INVITEE_IDS,
184                         G_TYPE_STRV, invitees,
185                     /* FIXME: InvitationMessage ? */
186                     NULL);
187
188                 req = tp_account_channel_request_new (self->priv->account, props,
189                         TP_USER_ACTION_TIME_NOT_USER_ACTION);
190
191                 /* Although this is a MUC, it's anonymous, so CreateChannel is
192                  * valid. */
193                 tp_account_channel_request_create_channel_async (req, EMPATHY_CHAT_BUS_NAME,
194                         NULL, create_conference_cb, NULL);
195
196                 g_object_unref (req);
197                 g_hash_table_unref (props);
198         } else {
199                 g_warning ("Cannot add to this channel");
200         }
201 }
202
203 static void
204 tp_chat_remove (EmpathyContactList *list,
205                 EmpathyContact     *contact,
206                 const gchar        *message)
207 {
208         EmpathyTpChat *self = (EmpathyTpChat *) list;
209         TpHandle           handle;
210         GArray             handles = {(gchar *) &handle, 1};
211
212         g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
213         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
214
215         handle = empathy_contact_get_handle (contact);
216         tp_cli_channel_interface_group_call_remove_members ((TpChannel *) self, -1,
217                                                             &handles, NULL,
218                                                             NULL, NULL, NULL,
219                                                             NULL);
220 }
221
222 static GList *
223 tp_chat_get_members (EmpathyContactList *list)
224 {
225         EmpathyTpChat *self = (EmpathyTpChat *) list;
226         GList             *members = NULL;
227
228         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL);
229
230         if (self->priv->members) {
231                 members = g_list_copy (self->priv->members);
232                 g_list_foreach (members, (GFunc) g_object_ref, NULL);
233         } else {
234                 members = g_list_prepend (members, g_object_ref (self->priv->user));
235                 if (self->priv->remote_contact != NULL)
236                         members = g_list_prepend (members, g_object_ref (self->priv->remote_contact));
237         }
238
239         return members;
240 }
241
242 static void
243 check_ready (EmpathyTpChat *self)
244 {
245         if (self->priv->ready_result == NULL)
246                 return;
247
248         if (g_queue_get_length (self->priv->messages_queue) > 0)
249                 return;
250
251         DEBUG ("Ready");
252
253         g_simple_async_result_complete (self->priv->ready_result);
254         tp_clear_object (&self->priv->ready_result);
255 }
256
257 static void
258 tp_chat_emit_queued_messages (EmpathyTpChat *self)
259 {
260         EmpathyMessage    *message;
261
262         /* Check if we can now emit some queued messages */
263         while ((message = g_queue_peek_head (self->priv->messages_queue)) != NULL) {
264                 if (empathy_message_get_sender (message) == NULL) {
265                         break;
266                 }
267
268                 DEBUG ("Queued message ready");
269                 g_queue_pop_head (self->priv->messages_queue);
270                 g_queue_push_tail (self->priv->pending_messages_queue, message);
271                 g_signal_emit (self, signals[MESSAGE_RECEIVED], 0, message);
272         }
273
274         check_ready (self);
275 }
276
277 static void
278 tp_chat_got_sender_cb (TpConnection            *connection,
279                        EmpathyContact          *contact,
280                        const GError            *error,
281                        gpointer                 message,
282                        GObject                 *chat)
283 {
284         EmpathyTpChat *self = (EmpathyTpChat *) chat;
285
286         if (error) {
287                 DEBUG ("Error: %s", error->message);
288                 /* Do not block the message queue, just drop this message */
289                 g_queue_remove (self->priv->messages_queue, message);
290         } else {
291                 empathy_message_set_sender (message, contact);
292         }
293
294         tp_chat_emit_queued_messages (EMPATHY_TP_CHAT (self));
295 }
296
297 static void
298 tp_chat_build_message (EmpathyTpChat *self,
299                        TpMessage     *msg,
300                        gboolean       incoming)
301 {
302         EmpathyMessage    *message;
303         TpContact *sender;
304
305         message = empathy_message_new_from_tp_message (msg, incoming);
306         /* FIXME: this is actually a lie for incoming messages. */
307         empathy_message_set_receiver (message, self->priv->user);
308
309         g_queue_push_tail (self->priv->messages_queue, message);
310
311         sender = tp_signalled_message_get_sender (msg);
312         g_assert (sender != NULL);
313
314         if (tp_contact_get_handle (sender) == 0) {
315                 empathy_message_set_sender (message, self->priv->user);
316                 tp_chat_emit_queued_messages (self);
317         } else {
318                 TpConnection *connection = tp_channel_borrow_connection (
319                         (TpChannel *) self);
320
321                 empathy_tp_contact_factory_get_from_handle (connection,
322                         tp_contact_get_handle (sender),
323                         tp_chat_got_sender_cb,
324                         message, NULL, G_OBJECT (self));
325         }
326 }
327
328 static void
329 handle_delivery_report (EmpathyTpChat *self,
330                 TpMessage *message)
331 {
332         TpDeliveryStatus delivery_status;
333         const GHashTable *header;
334         TpChannelTextSendError delivery_error;
335         gboolean valid;
336         GPtrArray *echo;
337         const gchar *message_body = NULL;
338         const gchar *delivery_dbus_error;
339         const gchar *delivery_token = NULL;
340
341         header = tp_message_peek (message, 0);
342         if (header == NULL)
343                 goto out;
344
345         delivery_token = tp_asv_get_string (header, "delivery-token");
346         delivery_status = tp_asv_get_uint32 (header, "delivery-status", &valid);
347
348         if (!valid) {
349                 goto out;
350         } else if (delivery_status == TP_DELIVERY_STATUS_ACCEPTED) {
351                 DEBUG ("Accepted %s", delivery_token);
352                 tp_chat_set_delivery_status (self, delivery_token,
353                         EMPATHY_DELIVERY_STATUS_ACCEPTED);
354                 goto out;
355         } else if (delivery_status == TP_DELIVERY_STATUS_DELIVERED) {
356                 DEBUG ("Delivered %s", delivery_token);
357                 tp_chat_set_delivery_status (self, delivery_token,
358                         EMPATHY_DELIVERY_STATUS_NONE);
359                 goto out;
360         } else if (delivery_status != TP_DELIVERY_STATUS_PERMANENTLY_FAILED) {
361                 goto out;
362         }
363
364         delivery_error = tp_asv_get_uint32 (header, "delivery-error", &valid);
365         if (!valid)
366                 delivery_error = TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN;
367
368         delivery_dbus_error = tp_asv_get_string (header, "delivery-dbus-error");
369
370         /* TODO: ideally we should use tp-glib API giving us the echoed message as a
371          * TpMessage. (fdo #35884) */
372         echo = tp_asv_get_boxed (header, "delivery-echo",
373                 TP_ARRAY_TYPE_MESSAGE_PART_LIST);
374         if (echo != NULL && echo->len >= 2) {
375                 const GHashTable *echo_body;
376
377                 echo_body = g_ptr_array_index (echo, 1);
378                 if (echo_body != NULL)
379                         message_body = tp_asv_get_string (echo_body, "content");
380         }
381
382         tp_chat_set_delivery_status (self, delivery_token,
383                         EMPATHY_DELIVERY_STATUS_NONE);
384         g_signal_emit (self, signals[SEND_ERROR], 0, message_body,
385                         delivery_error, delivery_dbus_error);
386
387 out:
388         tp_text_channel_ack_message_async (TP_TEXT_CHANNEL (self),
389                 message, NULL, NULL);
390 }
391
392 static void
393 handle_incoming_message (EmpathyTpChat *self,
394                          TpMessage *message,
395                          gboolean pending)
396 {
397         gchar *message_body;
398
399         if (tp_message_is_delivery_report (message)) {
400                 handle_delivery_report (self, message);
401                 return;
402         }
403
404         message_body = tp_message_to_text (message, NULL);
405
406         DEBUG ("Message %s (channel %s): %s",
407                 pending ? "pending" : "received",
408                 tp_proxy_get_object_path (self), message_body);
409
410         if (message_body == NULL) {
411                 DEBUG ("Empty message with NonTextContent, ignoring and acking.");
412
413                 tp_text_channel_ack_message_async (TP_TEXT_CHANNEL (self),
414                         message, NULL, NULL);
415                 return;
416         }
417
418         tp_chat_build_message (self, message, TRUE);
419
420         g_free (message_body);
421 }
422
423 static void
424 message_received_cb (TpTextChannel   *channel,
425                      TpMessage *message,
426                      EmpathyTpChat *self)
427 {
428         handle_incoming_message (self, message, FALSE);
429 }
430
431 static gboolean
432 find_pending_message_func (gconstpointer a,
433                            gconstpointer b)
434 {
435         EmpathyMessage *msg = (EmpathyMessage *) a;
436         TpMessage *message = (TpMessage *) b;
437
438         if (empathy_message_get_tp_message (msg) == message)
439                 return 0;
440
441         return -1;
442 }
443
444 static void
445 pending_message_removed_cb (TpTextChannel   *channel,
446                             TpMessage *message,
447                             EmpathyTpChat *self)
448 {
449         GList *m;
450
451         m = g_queue_find_custom (self->priv->pending_messages_queue, message,
452                                  find_pending_message_func);
453
454         if (m == NULL)
455                 return;
456
457         g_signal_emit (self, signals[MESSAGE_ACKNOWLEDGED], 0, m->data);
458
459         g_object_unref (m->data);
460         g_queue_delete_link (self->priv->pending_messages_queue, m);
461 }
462
463 static void
464 message_sent_cb (TpTextChannel   *channel,
465                  TpMessage *message,
466                  TpMessageSendingFlags flags,
467                  gchar              *token,
468                  EmpathyTpChat      *self)
469 {
470         gchar *message_body;
471
472         message_body = tp_message_to_text (message, NULL);
473
474         DEBUG ("Message sent: %s", message_body);
475
476         tp_chat_build_message (self, message, FALSE);
477
478         g_free (message_body);
479 }
480
481 static TpChannelTextSendError
482 error_to_text_send_error (GError *error)
483 {
484         if (error->domain != TP_ERRORS)
485                 return TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN;
486
487         switch (error->code) {
488                 case TP_ERROR_OFFLINE:
489                         return TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE;
490                 case TP_ERROR_INVALID_HANDLE:
491                         return TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT;
492                 case TP_ERROR_PERMISSION_DENIED:
493                         return TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED;
494                 case TP_ERROR_NOT_IMPLEMENTED:
495                         return TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED;
496         }
497
498         return TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN;
499 }
500
501 static void
502 message_send_cb (GObject *source,
503                  GAsyncResult *result,
504                  gpointer      user_data)
505 {
506         EmpathyTpChat *self = user_data;
507         TpTextChannel *channel = (TpTextChannel *) source;
508         gchar *token = NULL;
509         GError *error = NULL;
510
511         if (!tp_text_channel_send_message_finish (channel, result, &token, &error)) {
512                 DEBUG ("Error: %s", error->message);
513
514                 /* FIXME: we should use the body of the message as first argument of the
515                  * signal but can't easily get it as we just get a user_data pointer. Once
516                  * we'll have rebased EmpathyTpChat on top of TpTextChannel we'll be able
517                  * to use the user_data pointer to pass the message and fix this. */
518                 g_signal_emit (self, signals[SEND_ERROR], 0,
519                                NULL, error_to_text_send_error (error), NULL);
520
521                 g_error_free (error);
522         }
523
524         tp_chat_set_delivery_status (self, token,
525                 EMPATHY_DELIVERY_STATUS_SENDING);
526         g_free (token);
527 }
528
529 typedef struct {
530         EmpathyTpChat *chat;
531         TpChannelChatState state;
532 } StateChangedData;
533
534 static void
535 tp_chat_state_changed_got_contact_cb (TpConnection            *connection,
536                                       EmpathyContact          *contact,
537                                       const GError            *error,
538                                       gpointer                 user_data,
539                                       GObject                 *chat)
540 {
541         TpChannelChatState state;
542
543         if (error) {
544                 DEBUG ("Error: %s", error->message);
545                 return;
546         }
547
548         state = GPOINTER_TO_UINT (user_data);
549         DEBUG ("Chat state changed for %s (%d): %d",
550                 empathy_contact_get_alias (contact),
551                 empathy_contact_get_handle (contact), state);
552
553         g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
554 }
555
556 static void
557 tp_chat_state_changed_cb (TpChannel *channel,
558                           TpHandle   handle,
559                           TpChannelChatState state,
560                           EmpathyTpChat *self)
561 {
562         TpConnection *connection = tp_channel_borrow_connection (
563                 (TpChannel *) self);
564
565         empathy_tp_contact_factory_get_from_handle (connection, handle,
566                 tp_chat_state_changed_got_contact_cb, GUINT_TO_POINTER (state),
567                 NULL, G_OBJECT (self));
568 }
569
570 static void
571 list_pending_messages (EmpathyTpChat *self)
572 {
573         GList *messages, *l;
574
575         messages = tp_text_channel_get_pending_messages (
576                 TP_TEXT_CHANNEL (self));
577
578         for (l = messages; l != NULL; l = g_list_next (l)) {
579                 TpMessage *message = l->data;
580
581                 handle_incoming_message (self, message, FALSE);
582         }
583
584         g_list_free (messages);
585 }
586
587 static void
588 tp_chat_property_flags_changed_cb (TpProxy         *proxy,
589                                    const GPtrArray *properties,
590                                    gpointer         user_data,
591                                    GObject         *chat)
592 {
593         EmpathyTpChat *self = (EmpathyTpChat *) chat;
594         guint              i, j;
595
596         if (!self->priv->had_properties_list || !properties) {
597                 return;
598         }
599
600         for (i = 0; i < properties->len; i++) {
601                 GValueArray           *prop_struct;
602                 EmpathyTpChatProperty *property;
603                 guint                  id;
604                 guint                  flags;
605
606                 prop_struct = g_ptr_array_index (properties, i);
607                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
608                 flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 1));
609
610                 for (j = 0; j < self->priv->properties->len; j++) {
611                         property = g_ptr_array_index (self->priv->properties, j);
612                         if (property->id == id) {
613                                 property->flags = flags;
614                                 DEBUG ("property %s flags changed: %d",
615                                         property->name, property->flags);
616                                 break;
617                         }
618                 }
619         }
620 }
621
622 static void
623 tp_chat_properties_changed_cb (TpProxy         *proxy,
624                                const GPtrArray *properties,
625                                gpointer         user_data,
626                                GObject         *chat)
627 {
628         EmpathyTpChat *self = (EmpathyTpChat *) chat;
629         guint              i, j;
630
631         if (!self->priv->had_properties_list || !properties) {
632                 return;
633         }
634
635         for (i = 0; i < properties->len; i++) {
636                 GValueArray           *prop_struct;
637                 EmpathyTpChatProperty *property;
638                 guint                  id;
639                 GValue                *src_value;
640
641                 prop_struct = g_ptr_array_index (properties, i);
642                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
643                 src_value = g_value_get_boxed (g_value_array_get_nth (prop_struct, 1));
644
645                 for (j = 0; j < self->priv->properties->len; j++) {
646                         property = g_ptr_array_index (self->priv->properties, j);
647                         if (property->id == id) {
648                                 if (property->value) {
649                                         g_value_copy (src_value, property->value);
650                                 } else {
651                                         property->value = tp_g_value_slice_dup (src_value);
652                                 }
653
654                                 DEBUG ("property %s changed", property->name);
655                                 g_signal_emit (chat, signals[PROPERTY_CHANGED], 0,
656                                                property->name, property->value);
657                                 break;
658                         }
659                 }
660         }
661 }
662
663 static void
664 tp_chat_get_properties_cb (TpProxy         *proxy,
665                            const GPtrArray *properties,
666                            const GError    *error,
667                            gpointer         user_data,
668                            GObject         *chat)
669 {
670         if (error) {
671                 DEBUG ("Error getting properties: %s", error->message);
672                 return;
673         }
674
675         tp_chat_properties_changed_cb (proxy, properties, user_data, chat);
676 }
677
678 static void
679 tp_chat_list_properties_cb (TpProxy         *proxy,
680                             const GPtrArray *properties,
681                             const GError    *error,
682                             gpointer         user_data,
683                             GObject         *chat)
684 {
685         EmpathyTpChat *self = (EmpathyTpChat *) chat;
686         GArray            *ids;
687         guint              i;
688
689         if (error) {
690                 DEBUG ("Error listing properties: %s", error->message);
691                 return;
692         }
693
694         self->priv->had_properties_list = TRUE;
695
696         ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), properties->len);
697         self->priv->properties = g_ptr_array_sized_new (properties->len);
698         for (i = 0; i < properties->len; i++) {
699                 GValueArray           *prop_struct;
700                 EmpathyTpChatProperty *property;
701
702                 prop_struct = g_ptr_array_index (properties, i);
703                 property = g_slice_new0 (EmpathyTpChatProperty);
704                 property->id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
705                 property->name = g_value_dup_string (g_value_array_get_nth (prop_struct, 1));
706                 property->flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 3));
707
708                 DEBUG ("Adding property name=%s id=%d flags=%d",
709                         property->name, property->id, property->flags);
710                 g_ptr_array_add (self->priv->properties, property);
711                 if (property->flags & TP_PROPERTY_FLAG_READ) {
712                         g_array_append_val (ids, property->id);
713                 }
714         }
715
716         tp_cli_properties_interface_call_get_properties (proxy, -1,
717                                                          ids,
718                                                          tp_chat_get_properties_cb,
719                                                          NULL, NULL,
720                                                          chat);
721
722         g_array_free (ids, TRUE);
723 }
724
725 void
726 empathy_tp_chat_set_property (EmpathyTpChat *self,
727                               const gchar   *name,
728                               const GValue  *value)
729 {
730         EmpathyTpChatProperty *property;
731         guint                  i;
732
733         if (!self->priv->had_properties_list) {
734                 return;
735         }
736
737         for (i = 0; i < self->priv->properties->len; i++) {
738                 property = g_ptr_array_index (self->priv->properties, i);
739                 if (!tp_strdiff (property->name, name)) {
740                         GPtrArray   *properties;
741                         GValueArray *prop;
742                         GValue       id = {0, };
743                         GValue       dest_value = {0, };
744
745                         if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) {
746                                 break;
747                         }
748
749                         g_value_init (&id, G_TYPE_UINT);
750                         g_value_init (&dest_value, G_TYPE_VALUE);
751                         g_value_set_uint (&id, property->id);
752                         g_value_set_boxed (&dest_value, value);
753
754                         prop = g_value_array_new (2);
755                         g_value_array_append (prop, &id);
756                         g_value_array_append (prop, &dest_value);
757
758                         properties = g_ptr_array_sized_new (1);
759                         g_ptr_array_add (properties, prop);
760
761                         DEBUG ("Set property %s", name);
762                         tp_cli_properties_interface_call_set_properties (self, -1,
763                                                                          properties,
764                                                                          (tp_cli_properties_interface_callback_for_set_properties)
765                                                                          tp_chat_async_cb,
766                                                                          "Seting property", NULL,
767                                                                          G_OBJECT (self));
768
769                         g_ptr_array_free (properties, TRUE);
770                         g_value_array_free (prop);
771
772                         break;
773                 }
774         }
775 }
776
777 EmpathyTpChatProperty *
778 empathy_tp_chat_get_property (EmpathyTpChat *self,
779                               const gchar   *name)
780 {
781         EmpathyTpChatProperty *property;
782         guint                  i;
783
784         if (!self->priv->had_properties_list) {
785                 return NULL;
786         }
787
788         for (i = 0; i < self->priv->properties->len; i++) {
789                 property = g_ptr_array_index (self->priv->properties, i);
790                 if (!tp_strdiff (property->name, name)) {
791                         return property;
792                 }
793         }
794
795         return NULL;
796 }
797
798 GPtrArray *
799 empathy_tp_chat_get_properties (EmpathyTpChat *self)
800 {
801         return self->priv->properties;
802 }
803
804 static void
805 tp_chat_dispose (GObject *object)
806 {
807         EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
808
809         tp_clear_object (&self->priv->account);
810         tp_clear_object (&self->priv->remote_contact);
811         tp_clear_object (&self->priv->user);
812
813         g_queue_foreach (self->priv->messages_queue, (GFunc) g_object_unref, NULL);
814         g_queue_clear (self->priv->messages_queue);
815
816         g_queue_foreach (self->priv->pending_messages_queue,
817                 (GFunc) g_object_unref, NULL);
818         g_queue_clear (self->priv->pending_messages_queue);
819
820         tp_clear_object (&self->priv->ready_result);
821
822         if (G_OBJECT_CLASS (empathy_tp_chat_parent_class)->dispose)
823                 G_OBJECT_CLASS (empathy_tp_chat_parent_class)->dispose (object);
824 }
825
826 static void
827 tp_chat_finalize (GObject *object)
828 {
829         EmpathyTpChat *self = (EmpathyTpChat *) object;
830         guint              i;
831
832         DEBUG ("Finalize: %p", object);
833
834         if (self->priv->properties) {
835                 for (i = 0; i < self->priv->properties->len; i++) {
836                         EmpathyTpChatProperty *property;
837
838                         property = g_ptr_array_index (self->priv->properties, i);
839                         g_free (property->name);
840                         if (property->value) {
841                                 tp_g_value_slice_free (property->value);
842                         }
843                         g_slice_free (EmpathyTpChatProperty, property);
844                 }
845                 g_ptr_array_free (self->priv->properties, TRUE);
846         }
847
848         g_queue_free (self->priv->messages_queue);
849         g_queue_free (self->priv->pending_messages_queue);
850         g_hash_table_destroy (self->priv->messages_being_sent);
851
852         G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
853 }
854
855 static void
856 check_almost_ready (EmpathyTpChat *self)
857 {
858         TpChannel *channel = (TpChannel *) self;
859
860         if (self->priv->ready_result == NULL)
861                 return;
862
863         if (self->priv->user == NULL)
864                 return;
865
866         /* We need either the members (room) or the remote contact (private chat).
867          * If the chat is protected by a password we can't get these information so
868          * consider the chat as ready so it can be presented to the user. */
869         if (!tp_channel_password_needed (channel) && self->priv->members == NULL &&
870             self->priv->remote_contact == NULL)
871                 return;
872
873         g_assert (tp_proxy_is_prepared (self,
874                 TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES));
875
876         tp_g_signal_connect_object (self, "message-received",
877                 G_CALLBACK (message_received_cb), self, 0);
878         tp_g_signal_connect_object (self, "pending-message-removed",
879                 G_CALLBACK (pending_message_removed_cb), self, 0);
880
881         list_pending_messages (self);
882
883         tp_g_signal_connect_object (self, "message-sent",
884                 G_CALLBACK (message_sent_cb), self, 0);
885
886         tp_g_signal_connect_object (self, "chat-state-changed",
887                 G_CALLBACK (tp_chat_state_changed_cb), self, 0);
888
889         check_ready (self);
890 }
891
892 static void
893 tp_chat_got_added_contacts_cb (TpConnection            *connection,
894                                guint                    n_contacts,
895                                EmpathyContact * const * contacts,
896                                guint                    n_failed,
897                                const TpHandle          *failed,
898                                const GError            *error,
899                                gpointer                 user_data,
900                                GObject                 *chat)
901 {
902         EmpathyTpChat *self = (EmpathyTpChat *) chat;
903         guint i;
904         const TpIntSet *members;
905         TpHandle handle;
906         EmpathyContact *contact;
907
908         if (error) {
909                 DEBUG ("Error: %s", error->message);
910                 return;
911         }
912
913         members = tp_channel_group_get_members ((TpChannel *) self);
914         for (i = 0; i < n_contacts; i++) {
915                 contact = contacts[i];
916                 handle = empathy_contact_get_handle (contact);
917
918                 /* Make sure the contact is still member */
919                 if (tp_intset_is_member (members, handle)) {
920                         self->priv->members = g_list_prepend (self->priv->members,
921                                 g_object_ref (contact));
922                         g_signal_emit_by_name (chat, "members-changed",
923                                                contact, NULL, 0, NULL, TRUE);
924                 }
925         }
926
927         check_almost_ready (EMPATHY_TP_CHAT (chat));
928 }
929
930 static EmpathyContact *
931 chat_lookup_contact (EmpathyTpChat *self,
932                      TpHandle       handle,
933                      gboolean       remove_)
934 {
935         GList *l;
936
937         for (l = self->priv->members; l; l = l->next) {
938                 EmpathyContact *c = l->data;
939
940                 if (empathy_contact_get_handle (c) != handle) {
941                         continue;
942                 }
943
944                 if (remove_) {
945                         /* Caller takes the reference. */
946                         self->priv->members = g_list_delete_link (self->priv->members, l);
947                 } else {
948                         g_object_ref (c);
949                 }
950
951                 return c;
952         }
953
954         return NULL;
955 }
956
957 typedef struct
958 {
959     TpHandle old_handle;
960     guint reason;
961     gchar *message;
962 } ContactRenameData;
963
964 static ContactRenameData *
965 contact_rename_data_new (TpHandle handle,
966                          guint reason,
967                          const gchar* message)
968 {
969         ContactRenameData *data = g_new (ContactRenameData, 1);
970         data->old_handle = handle;
971         data->reason = reason;
972         data->message = g_strdup (message);
973
974         return data;
975 }
976
977 static void
978 contact_rename_data_free (ContactRenameData* data)
979 {
980         g_free (data->message);
981         g_free (data);
982 }
983
984 static void
985 tp_chat_got_renamed_contacts_cb (TpConnection            *connection,
986                                  guint                    n_contacts,
987                                  EmpathyContact * const * contacts,
988                                  guint                    n_failed,
989                                  const TpHandle          *failed,
990                                  const GError            *error,
991                                  gpointer                 user_data,
992                                  GObject                 *chat)
993 {
994         EmpathyTpChat *self = (EmpathyTpChat *) chat;
995         const TpIntSet *members;
996         TpHandle handle;
997         EmpathyContact *old = NULL, *new = NULL;
998         ContactRenameData *rename_data = (ContactRenameData *) user_data;
999
1000         if (error) {
1001                 DEBUG ("Error: %s", error->message);
1002                 return;
1003         }
1004
1005         /* renamed members can only be delivered one at a time */
1006         g_warn_if_fail (n_contacts == 1);
1007
1008         new = contacts[0];
1009
1010         members = tp_channel_group_get_members ((TpChannel *) self);
1011         handle = empathy_contact_get_handle (new);
1012
1013         old = chat_lookup_contact (self, rename_data->old_handle, TRUE);
1014
1015         /* Make sure the contact is still member */
1016         if (tp_intset_is_member (members, handle)) {
1017                 self->priv->members = g_list_prepend (self->priv->members,
1018                         g_object_ref (new));
1019
1020                 if (old != NULL) {
1021                         g_signal_emit_by_name (self, "member-renamed",
1022                                                old, new, rename_data->reason,
1023                                                rename_data->message);
1024                         g_object_unref (old);
1025                 }
1026         }
1027
1028         if (self->priv->user == old) {
1029                 /* We change our nick */
1030                 tp_clear_object (&self->priv->user);
1031                 self->priv->user = g_object_ref (new);
1032         }
1033
1034         check_almost_ready (self);
1035 }
1036
1037
1038 static void
1039 tp_chat_group_members_changed_cb (TpChannel     *channel,
1040                                   gchar         *message,
1041                                   GArray        *added,
1042                                   GArray        *removed,
1043                                   GArray        *local_pending,
1044                                   GArray        *remote_pending,
1045                                   guint          actor,
1046                                   guint          reason,
1047                                   EmpathyTpChat *self)
1048 {
1049         EmpathyContact *contact;
1050         EmpathyContact *actor_contact = NULL;
1051         guint i;
1052         ContactRenameData *rename_data;
1053         TpHandle old_handle;
1054         TpConnection *connection = tp_channel_borrow_connection (
1055                 (TpChannel *) self);
1056
1057         /* Contact renamed */
1058         if (reason == TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED) {
1059                 /* there can only be a single 'added' and a single 'removed' handle */
1060                 if (removed->len != 1 || added->len != 1) {
1061                         g_warning ("RENAMED with %u added, %u removed (expected 1, 1)",
1062                                 added->len, removed->len);
1063                         return;
1064                 }
1065
1066                 old_handle = g_array_index (removed, guint, 0);
1067
1068                 rename_data = contact_rename_data_new (old_handle, reason, message);
1069                 empathy_tp_contact_factory_get_from_handles (connection,
1070                         added->len, (TpHandle *) added->data,
1071                         tp_chat_got_renamed_contacts_cb,
1072                         rename_data, (GDestroyNotify) contact_rename_data_free,
1073                         G_OBJECT (self));
1074                 return;
1075         }
1076
1077         if (actor != 0) {
1078                 actor_contact = chat_lookup_contact (self, actor, FALSE);
1079                 if (actor_contact == NULL) {
1080                         /* FIXME: handle this a tad more gracefully: perhaps
1081                          * the actor was a server op. We could use the
1082                          * contact-ids detail of MembersChangedDetailed.
1083                          */
1084                         DEBUG ("actor %u not a channel member", actor);
1085                 }
1086         }
1087
1088         /* Remove contacts that are not members anymore */
1089         for (i = 0; i < removed->len; i++) {
1090                 contact = chat_lookup_contact (self,
1091                         g_array_index (removed, TpHandle, i), TRUE);
1092
1093                 if (contact != NULL) {
1094                         g_signal_emit_by_name (self, "members-changed", contact,
1095                                                actor_contact, reason, message,
1096                                                FALSE);
1097                         g_object_unref (contact);
1098                 }
1099         }
1100
1101         /* Request added contacts */
1102         if (added->len > 0) {
1103                 empathy_tp_contact_factory_get_from_handles (connection,
1104                         added->len, (TpHandle *) added->data,
1105                         tp_chat_got_added_contacts_cb, NULL, NULL,
1106                         G_OBJECT (self));
1107         }
1108
1109         if (actor_contact != NULL) {
1110                 g_object_unref (actor_contact);
1111         }
1112 }
1113
1114 static void
1115 tp_chat_got_remote_contact_cb (TpConnection            *connection,
1116                                EmpathyContact          *contact,
1117                                const GError            *error,
1118                                gpointer                 user_data,
1119                                GObject                 *chat)
1120 {
1121         EmpathyTpChat *self = (EmpathyTpChat *) chat;
1122
1123         if (error) {
1124                 DEBUG ("Error: %s", error->message);
1125                 empathy_tp_chat_leave (self, "");
1126                 return;
1127         }
1128
1129         self->priv->remote_contact = g_object_ref (contact);
1130         g_object_notify (chat, "remote-contact");
1131
1132         check_almost_ready (self);
1133 }
1134
1135 static void
1136 tp_chat_got_self_contact_cb (TpConnection            *connection,
1137                              EmpathyContact          *contact,
1138                              const GError            *error,
1139                              gpointer                 user_data,
1140                              GObject                 *chat)
1141 {
1142         EmpathyTpChat *self = (EmpathyTpChat *) chat;
1143
1144         if (error) {
1145                 DEBUG ("Error: %s", error->message);
1146                 empathy_tp_chat_leave (self, "");
1147                 return;
1148         }
1149
1150         self->priv->user = g_object_ref (contact);
1151         empathy_contact_set_is_user (self->priv->user, TRUE);
1152         check_almost_ready (self);
1153 }
1154
1155 static void
1156 tp_chat_get_property (GObject    *object,
1157                       guint       param_id,
1158                       GValue     *value,
1159                       GParamSpec *pspec)
1160 {
1161         EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
1162
1163         switch (param_id) {
1164         case PROP_ACCOUNT:
1165                 g_value_set_object (value, self->priv->account);
1166                 break;
1167         case PROP_REMOTE_CONTACT:
1168                 g_value_set_object (value, self->priv->remote_contact);
1169                 break;
1170         case PROP_N_MESSAGES_SENDING:
1171                 g_value_set_uint (value,
1172                         g_hash_table_size (self->priv->messages_being_sent));
1173                 break;
1174         default:
1175                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1176                 break;
1177         };
1178 }
1179
1180 static void
1181 tp_chat_set_property (GObject      *object,
1182                       guint         param_id,
1183                       const GValue *value,
1184                       GParamSpec   *pspec)
1185 {
1186         EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
1187
1188         switch (param_id) {
1189         case PROP_ACCOUNT:
1190                 self->priv->account = g_value_dup_object (value);
1191                 break;
1192         default:
1193                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1194                 break;
1195         };
1196 }
1197
1198 enum {
1199         FEAT_READY,
1200         N_FEAT
1201 };
1202
1203 static const TpProxyFeature *
1204 tp_chat_list_features (TpProxyClass *cls G_GNUC_UNUSED)
1205 {
1206         static TpProxyFeature features[N_FEAT + 1] = { { 0 } };
1207   static GQuark need[2] = {0, 0};
1208
1209         if (G_LIKELY (features[0].name != 0))
1210                 return features;
1211
1212         features[FEAT_READY].name = EMPATHY_TP_CHAT_FEATURE_READY;
1213         need[0] = TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES;
1214         features[FEAT_READY].depends_on = need;
1215         features[FEAT_READY].prepare_async =
1216                 tp_chat_prepare_ready_async;
1217
1218         /* assert that the terminator at the end is there */
1219         g_assert (features[N_FEAT].name == 0);
1220
1221         return features;
1222 }
1223
1224 static void
1225 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
1226 {
1227         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1228         TpProxyClass *proxy_class = TP_PROXY_CLASS (klass);
1229
1230         object_class->dispose = tp_chat_dispose;
1231         object_class->finalize = tp_chat_finalize;
1232         object_class->get_property = tp_chat_get_property;
1233         object_class->set_property = tp_chat_set_property;
1234
1235         proxy_class->list_features = tp_chat_list_features;
1236
1237         g_object_class_install_property (object_class,
1238                                          PROP_ACCOUNT,
1239                                          g_param_spec_object ("account",
1240                                                               "TpAccount",
1241                                                               "the account associated with the chat",
1242                                                               TP_TYPE_ACCOUNT,
1243                                                               G_PARAM_READWRITE |
1244                                                               G_PARAM_CONSTRUCT_ONLY |
1245                                                               G_PARAM_STATIC_STRINGS));
1246
1247         g_object_class_install_property (object_class,
1248                                          PROP_REMOTE_CONTACT,
1249                                          g_param_spec_object ("remote-contact",
1250                                                               "The remote contact",
1251                                                               "The remote contact if there is no group iface on the channel",
1252                                                               EMPATHY_TYPE_CONTACT,
1253                                                               G_PARAM_READABLE));
1254
1255         g_object_class_install_property (object_class,
1256                                          PROP_N_MESSAGES_SENDING,
1257                                          g_param_spec_uint ("n-messages-sending",
1258                                                             "Num Messages Sending",
1259                                                             "The number of messages being sent",
1260                                                             0, G_MAXUINT, 0,
1261                                                             G_PARAM_READABLE));
1262
1263         /* Signals */
1264         signals[MESSAGE_RECEIVED] =
1265                 g_signal_new ("message-received-empathy",
1266                               G_TYPE_FROM_CLASS (klass),
1267                               G_SIGNAL_RUN_LAST,
1268                               0,
1269                               NULL, NULL,
1270                               g_cclosure_marshal_VOID__OBJECT,
1271                               G_TYPE_NONE,
1272                               1, EMPATHY_TYPE_MESSAGE);
1273
1274         signals[SEND_ERROR] =
1275                 g_signal_new ("send-error",
1276                               G_TYPE_FROM_CLASS (klass),
1277                               G_SIGNAL_RUN_LAST,
1278                               0,
1279                               NULL, NULL,
1280                               _empathy_marshal_VOID__STRING_UINT_STRING,
1281                               G_TYPE_NONE,
1282                               3, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING);
1283
1284         signals[CHAT_STATE_CHANGED] =
1285                 g_signal_new ("chat-state-changed-empathy",
1286                               G_TYPE_FROM_CLASS (klass),
1287                               G_SIGNAL_RUN_LAST,
1288                               0,
1289                               NULL, NULL,
1290                               _empathy_marshal_VOID__OBJECT_UINT,
1291                               G_TYPE_NONE,
1292                               2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT);
1293
1294         signals[PROPERTY_CHANGED] =
1295                 g_signal_new ("property-changed",
1296                               G_TYPE_FROM_CLASS (klass),
1297                               G_SIGNAL_RUN_LAST,
1298                               0,
1299                               NULL, NULL,
1300                               _empathy_marshal_VOID__STRING_BOXED,
1301                               G_TYPE_NONE,
1302                               2, G_TYPE_STRING, G_TYPE_VALUE);
1303
1304         signals[MESSAGE_ACKNOWLEDGED] =
1305                 g_signal_new ("message-acknowledged",
1306                               G_TYPE_FROM_CLASS (klass),
1307                               G_SIGNAL_RUN_LAST,
1308                               0,
1309                               NULL, NULL,
1310                               g_cclosure_marshal_VOID__OBJECT,
1311                               G_TYPE_NONE,
1312                               1, EMPATHY_TYPE_MESSAGE);
1313
1314         g_type_class_add_private (object_class, sizeof (EmpathyTpChatPrivate));
1315 }
1316
1317 static void
1318 empathy_tp_chat_init (EmpathyTpChat *self)
1319 {
1320         self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
1321                 EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPrivate);
1322
1323         self->priv->messages_queue = g_queue_new ();
1324         self->priv->pending_messages_queue = g_queue_new ();
1325         self->priv->messages_being_sent = g_hash_table_new_full (
1326                 g_str_hash, g_str_equal, g_free, NULL);
1327 }
1328
1329 static void
1330 tp_chat_iface_init (EmpathyContactListIface *iface)
1331 {
1332         iface->add         = tp_chat_add;
1333         iface->remove      = tp_chat_remove;
1334         iface->get_members = tp_chat_get_members;
1335 }
1336
1337 EmpathyTpChat *
1338 empathy_tp_chat_new (
1339                      TpSimpleClientFactory *factory,
1340                      TpAccount *account,
1341                      TpConnection *conn,
1342                      const gchar *object_path,
1343                      const GHashTable *immutable_properties)
1344 {
1345         TpProxy *conn_proxy = (TpProxy *) conn;
1346
1347         g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
1348         g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL);
1349         g_return_val_if_fail (immutable_properties != NULL, NULL);
1350
1351         return g_object_new (EMPATHY_TYPE_TP_CHAT,
1352                              "factory", factory,
1353                              "account", account,
1354                              "connection", conn,
1355                              "dbus-daemon", conn_proxy->dbus_daemon,
1356                              "bus-name", conn_proxy->bus_name,
1357                              "object-path", object_path,
1358                              "channel-properties", immutable_properties,
1359                              NULL);
1360 }
1361
1362 const gchar *
1363 empathy_tp_chat_get_id (EmpathyTpChat *self)
1364 {
1365         const gchar *id;
1366
1367         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), NULL);
1368
1369         id = tp_channel_get_identifier ((TpChannel *) self);
1370         if (!EMP_STR_EMPTY (id))
1371                 return id;
1372         else if (self->priv->remote_contact)
1373                 return empathy_contact_get_id (self->priv->remote_contact);
1374         else
1375                 return NULL;
1376
1377 }
1378
1379 EmpathyContact *
1380 empathy_tp_chat_get_remote_contact (EmpathyTpChat *self)
1381 {
1382         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), NULL);
1383
1384         return self->priv->remote_contact;
1385 }
1386
1387 TpAccount *
1388 empathy_tp_chat_get_account (EmpathyTpChat *self)
1389 {
1390         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), NULL);
1391
1392         return self->priv->account;
1393 }
1394
1395 void
1396 empathy_tp_chat_send (EmpathyTpChat *self,
1397                       TpMessage *message)
1398 {
1399         gchar *message_body;
1400
1401         g_return_if_fail (EMPATHY_IS_TP_CHAT (self));
1402         g_return_if_fail (TP_IS_CLIENT_MESSAGE (message));
1403
1404         message_body = tp_message_to_text (message, NULL);
1405
1406         DEBUG ("Sending message: %s", message_body);
1407
1408         tp_text_channel_send_message_async (TP_TEXT_CHANNEL (self),
1409                 message, TP_MESSAGE_SENDING_FLAG_REPORT_DELIVERY,
1410                 message_send_cb, self);
1411
1412         g_free (message_body);
1413 }
1414
1415 const GList *
1416 empathy_tp_chat_get_pending_messages (EmpathyTpChat *self)
1417 {
1418         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), NULL);
1419
1420         return self->priv->pending_messages_queue->head;
1421 }
1422
1423 void
1424 empathy_tp_chat_acknowledge_message (EmpathyTpChat *self,
1425                                      EmpathyMessage *message) {
1426         TpMessage *tp_msg;
1427
1428         g_return_if_fail (EMPATHY_IS_TP_CHAT (self));
1429
1430         if (!empathy_message_is_incoming (message))
1431                 return;
1432
1433         tp_msg = empathy_message_get_tp_message (message);
1434         tp_text_channel_ack_message_async (TP_TEXT_CHANNEL (self),
1435                                            tp_msg, NULL, NULL);
1436 }
1437
1438 /**
1439  * empathy_tp_chat_can_add_contact:
1440  *
1441  * Returns: %TRUE if empathy_contact_list_add() will work for this channel.
1442  * That is if this chat is a 1-to-1 channel that can be upgraded to
1443  * a MUC using the Conference interface or if the channel is a MUC.
1444  */
1445 gboolean
1446 empathy_tp_chat_can_add_contact (EmpathyTpChat *self)
1447 {
1448         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), FALSE);
1449
1450         return self->priv->can_upgrade_to_muc ||
1451                 tp_proxy_has_interface_by_id (self,
1452                         TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP);;
1453 }
1454
1455 static void
1456 tp_channel_leave_async_cb (GObject *source_object,
1457         GAsyncResult *res,
1458         gpointer user_data)
1459 {
1460         GError *error = NULL;
1461
1462         if (!tp_channel_leave_finish (TP_CHANNEL (source_object), res, &error)) {
1463                 DEBUG ("Could not leave channel properly: (%s); closing the channel",
1464                         error->message);
1465                 g_error_free (error);
1466         }
1467 }
1468
1469 void
1470 empathy_tp_chat_leave (EmpathyTpChat *self,
1471                 const gchar *message)
1472 {
1473         TpChannel *channel = (TpChannel *) self;
1474
1475         DEBUG ("Leaving channel %s with message \"%s\"",
1476                 tp_channel_get_identifier (channel), message);
1477
1478         tp_channel_leave_async (channel, TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
1479                 message, tp_channel_leave_async_cb, self);
1480 }
1481
1482 static void
1483 add_members_cb (TpChannel *proxy,
1484                 const GError *error,
1485                 gpointer user_data,
1486                 GObject *weak_object)
1487 {
1488         EmpathyTpChat *self = (EmpathyTpChat *) weak_object;
1489
1490         if (error != NULL) {
1491                 DEBUG ("Failed to join chat (%s): %s",
1492                         tp_channel_get_identifier ((TpChannel *) self), error->message);
1493         }
1494 }
1495
1496 void
1497 empathy_tp_chat_join (EmpathyTpChat *self)
1498 {
1499         TpHandle self_handle;
1500         GArray *members;
1501
1502         self_handle = tp_channel_group_get_self_handle ((TpChannel *) self);
1503
1504         members = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
1505         g_array_append_val (members, self_handle);
1506
1507         tp_cli_channel_interface_group_call_add_members ((TpChannel *) self, -1, members,
1508                 "", add_members_cb, NULL, NULL, G_OBJECT (self));
1509
1510         g_array_free (members, TRUE);
1511 }
1512
1513 gboolean
1514 empathy_tp_chat_is_invited (EmpathyTpChat *self,
1515                             TpHandle *inviter)
1516 {
1517         TpHandle self_handle;
1518
1519         if (!tp_proxy_has_interface (self, TP_IFACE_CHANNEL_INTERFACE_GROUP))
1520                 return FALSE;
1521
1522         self_handle = tp_channel_group_get_self_handle ((TpChannel *) self);
1523         if (self_handle == 0)
1524                 return FALSE;
1525
1526         return tp_channel_group_get_local_pending_info ((TpChannel *) self, self_handle,
1527                 inviter, NULL, NULL);
1528 }
1529
1530 TpChannelChatState
1531 empathy_tp_chat_get_chat_state (EmpathyTpChat *self,
1532                             EmpathyContact *contact)
1533 {
1534         return tp_channel_get_chat_state ((TpChannel *) self,
1535                 empathy_contact_get_handle (contact));
1536 }
1537
1538 EmpathyContact *
1539 empathy_tp_chat_get_self_contact (EmpathyTpChat *self)
1540 {
1541         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), NULL);
1542
1543         return self->priv->user;
1544 }
1545
1546 GQuark
1547 empathy_tp_chat_get_feature_ready (void)
1548 {
1549         return g_quark_from_static_string ("empathy-tp-chat-feature-ready");
1550 }
1551
1552 static void
1553 tp_chat_prepare_ready_async (TpProxy *proxy,
1554         const TpProxyFeature *feature,
1555         GAsyncReadyCallback callback,
1556         gpointer user_data)
1557 {
1558         EmpathyTpChat *self = (EmpathyTpChat *) proxy;
1559         TpChannel *channel = (TpChannel *) proxy;
1560         TpConnection *connection;
1561
1562         g_assert (self->priv->ready_result == NULL);
1563         self->priv->ready_result = g_simple_async_result_new (G_OBJECT (self),
1564                 callback, user_data, tp_chat_prepare_ready_async);
1565
1566         connection = tp_channel_borrow_connection (channel);
1567
1568         if (tp_proxy_has_interface_by_id (self,
1569                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
1570                 const TpIntSet *members;
1571                 GArray *handles;
1572                 TpHandle handle;
1573
1574                 /* Get self contact from the group's self handle */
1575                 handle = tp_channel_group_get_self_handle (channel);
1576                 empathy_tp_contact_factory_get_from_handle (connection,
1577                         handle, tp_chat_got_self_contact_cb,
1578                         NULL, NULL, G_OBJECT (self));
1579
1580                 /* Get initial member contacts */
1581                 members = tp_channel_group_get_members (channel);
1582                 handles = tp_intset_to_array (members);
1583                 empathy_tp_contact_factory_get_from_handles (connection,
1584                         handles->len, (TpHandle *) handles->data,
1585                         tp_chat_got_added_contacts_cb, NULL, NULL, G_OBJECT (self));
1586
1587                 self->priv->can_upgrade_to_muc = FALSE;
1588
1589                 tp_g_signal_connect_object (self, "group-members-changed",
1590                         G_CALLBACK (tp_chat_group_members_changed_cb), self, 0);
1591         } else {
1592                 TpCapabilities *caps;
1593                 GPtrArray *classes;
1594                 guint i;
1595                 TpHandle handle;
1596
1597                 /* Get the self contact from the connection's self handle */
1598                 handle = tp_connection_get_self_handle (connection);
1599                 empathy_tp_contact_factory_get_from_handle (connection,
1600                         handle, tp_chat_got_self_contact_cb,
1601                         NULL, NULL, G_OBJECT (self));
1602
1603                 /* Get the remote contact */
1604                 handle = tp_channel_get_handle (channel, NULL);
1605                 empathy_tp_contact_factory_get_from_handle (connection,
1606                         handle, tp_chat_got_remote_contact_cb,
1607                         NULL, NULL, G_OBJECT (self));
1608
1609                 caps = tp_connection_get_capabilities (connection);
1610                 g_assert (caps != NULL);
1611
1612                 classes = tp_capabilities_get_channel_classes (caps);
1613
1614                 for (i = 0; i < classes->len; i++) {
1615                         GValueArray *array = g_ptr_array_index (classes, i);
1616                         const char **oprops = g_value_get_boxed (
1617                                 g_value_array_get_nth (array, 1));
1618
1619                         if (tp_strv_contains (oprops, TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_CHANNELS)) {
1620                                 self->priv->can_upgrade_to_muc = TRUE;
1621                                 break;
1622                         }
1623                 }
1624         }
1625
1626         if (tp_proxy_has_interface_by_id (self,
1627                                           TP_IFACE_QUARK_PROPERTIES_INTERFACE)) {
1628                 tp_cli_properties_interface_call_list_properties (channel, -1,
1629                                                                   tp_chat_list_properties_cb,
1630                                                                   NULL, NULL,
1631                                                                   G_OBJECT (self));
1632                 tp_cli_properties_interface_connect_to_properties_changed (channel,
1633                                                                            tp_chat_properties_changed_cb,
1634                                                                            NULL, NULL,
1635                                                                            G_OBJECT (self), NULL);
1636                 tp_cli_properties_interface_connect_to_property_flags_changed (channel,
1637                                                                                tp_chat_property_flags_changed_cb,
1638                                                                                NULL, NULL,
1639                                                                                G_OBJECT (self), NULL);
1640         }
1641 }