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