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