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