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