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