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