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