]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-chat.c
Merge branch 'undo-close-tab'
[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-marshal.h"
34 #include "empathy-request-util.h"
35 #include "empathy-time.h"
36 #include "empathy-utils.h"
37
38 #define DEBUG_FLAG EMPATHY_DEBUG_TP | EMPATHY_DEBUG_CHAT
39 #include "empathy-debug.h"
40
41 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpChat)
42 typedef struct {
43         gboolean               dispose_has_run;
44         TpAccount             *account;
45         TpConnection          *connection;
46         EmpathyContact        *user;
47         EmpathyContact        *remote_contact;
48         GList                 *members;
49         TpChannel             *channel;
50         /* Queue of messages not signalled yet */
51         GQueue                *messages_queue;
52         /* Queue of messages signalled but not acked yet */
53         GQueue                *pending_messages_queue;
54         gboolean               had_properties_list;
55         GPtrArray             *properties;
56         TpChannelPasswordFlags password_flags;
57         /* TRUE if we fetched the password flag of the channel or if it's not needed
58          * (channel doesn't implement the Password interface) */
59         gboolean               got_password_flags;
60         gboolean               ready;
61         gboolean               can_upgrade_to_muc;
62         gboolean               got_sms_channel;
63         gboolean               sms_channel;
64
65         GHashTable            *messages_being_sent;
66 } EmpathyTpChatPriv;
67
68 static void tp_chat_iface_init         (EmpathyContactListIface *iface);
69
70 enum {
71         PROP_0,
72         PROP_ACCOUNT,
73         PROP_CHANNEL,
74         PROP_REMOTE_CONTACT,
75         PROP_PASSWORD_NEEDED,
76         PROP_READY,
77         PROP_SMS_CHANNEL,
78         PROP_N_MESSAGES_SENDING,
79 };
80
81 enum {
82         MESSAGE_RECEIVED,
83         SEND_ERROR,
84         CHAT_STATE_CHANGED,
85         PROPERTY_CHANGED,
86         DESTROY,
87         MESSAGE_ACKNOWLEDGED,
88         LAST_SIGNAL
89 };
90
91 static guint signals[LAST_SIGNAL];
92
93 G_DEFINE_TYPE_WITH_CODE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT,
94                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST,
95                                                 tp_chat_iface_init));
96
97 static void
98 tp_chat_set_delivery_status (EmpathyTpChat         *self,
99                              const gchar           *token,
100                              EmpathyDeliveryStatus  delivery_status)
101 {
102         EmpathyTpChatPriv *priv = GET_PRIV (self);
103         TpDeliveryReportingSupportFlags flags =
104                 tp_text_channel_get_delivery_reporting_support (
105                         TP_TEXT_CHANNEL (priv->channel));
106
107         /* channel must support receiving failures and successes */
108         if (!tp_str_empty (token) &&
109             flags & TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_FAILURES &&
110             flags & TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_SUCCESSES) {
111
112                 DEBUG ("Delivery status (%s) = %u", token, delivery_status);
113
114                 switch (delivery_status) {
115                         case EMPATHY_DELIVERY_STATUS_NONE:
116                                 g_hash_table_remove (priv->messages_being_sent,
117                                         token);
118                                 break;
119
120                         default:
121                                 g_hash_table_insert (priv->messages_being_sent,
122                                         g_strdup (token),
123                                         GUINT_TO_POINTER (delivery_status));
124                                 break;
125                 }
126
127                 g_object_notify (G_OBJECT (self), "n-messages-sending");
128         }
129 }
130
131 static void
132 tp_chat_invalidated_cb (TpProxy       *proxy,
133                         guint          domain,
134                         gint           code,
135                         gchar         *message,
136                         EmpathyTpChat *chat)
137 {
138         DEBUG ("Channel invalidated: %s", message);
139         g_signal_emit (chat, signals[DESTROY], 0);
140 }
141
142 static void
143 tp_chat_async_cb (TpChannel *proxy,
144                   const GError *error,
145                   gpointer user_data,
146                   GObject *weak_object)
147 {
148         if (error) {
149                 DEBUG ("Error %s: %s", (gchar *) user_data, error->message);
150         }
151 }
152
153 static void
154 create_conference_cb (GObject *source,
155                       GAsyncResult *result,
156                       gpointer user_data)
157 {
158         GError *error = NULL;
159
160         if (!tp_account_channel_request_create_channel_finish (
161                         TP_ACCOUNT_CHANNEL_REQUEST (source), result, &error)) {
162                 DEBUG ("Failed to create conference channel: %s", error->message);
163                 g_error_free (error);
164         }
165 }
166
167 static void
168 tp_chat_add (EmpathyContactList *list,
169              EmpathyContact     *contact,
170              const gchar        *message)
171 {
172         EmpathyTpChatPriv *priv = GET_PRIV (list);
173
174         if (tp_proxy_has_interface_by_id (priv->channel,
175                 TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
176                 TpHandle           handle;
177                 GArray             handles = {(gchar *) &handle, 1};
178
179                 g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
180                 g_return_if_fail (EMPATHY_IS_CONTACT (contact));
181
182                 handle = empathy_contact_get_handle (contact);
183                 tp_cli_channel_interface_group_call_add_members (priv->channel,
184                         -1, &handles, NULL, NULL, NULL, NULL, NULL);
185         } else if (priv->can_upgrade_to_muc) {
186                 TpAccountChannelRequest *req;
187                 GHashTable        *props;
188                 const char        *object_path;
189                 GPtrArray          channels = { (gpointer *) &object_path, 1 };
190                 const char        *invitees[2] = { NULL, };
191
192                 invitees[0] = empathy_contact_get_id (contact);
193                 object_path = tp_proxy_get_object_path (priv->channel);
194
195                 props = tp_asv_new (
196                     TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
197                         TP_IFACE_CHANNEL_TYPE_TEXT,
198                     TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
199                         TP_HANDLE_TYPE_NONE,
200                     TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_CHANNELS,
201                         TP_ARRAY_TYPE_OBJECT_PATH_LIST, &channels,
202                     TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_INVITEE_IDS,
203                         G_TYPE_STRV, invitees,
204                     /* FIXME: InvitationMessage ? */
205                     NULL);
206
207                 req = tp_account_channel_request_new (priv->account, props,
208                         TP_USER_ACTION_TIME_NOT_USER_ACTION);
209
210                 /* Although this is a MUC, it's anonymous, so CreateChannel is
211                  * valid. */
212                 tp_account_channel_request_create_channel_async (req, EMPATHY_CHAT_BUS_NAME,
213                         NULL, create_conference_cb, NULL);
214
215                 g_object_unref (req);
216                 g_hash_table_unref (props);
217         } else {
218                 g_warning ("Cannot add to this channel");
219         }
220 }
221
222 static void
223 tp_chat_remove (EmpathyContactList *list,
224                 EmpathyContact     *contact,
225                 const gchar        *message)
226 {
227         EmpathyTpChatPriv *priv = GET_PRIV (list);
228         TpHandle           handle;
229         GArray             handles = {(gchar *) &handle, 1};
230
231         g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
232         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
233
234         handle = empathy_contact_get_handle (contact);
235         tp_cli_channel_interface_group_call_remove_members (priv->channel, -1,
236                                                             &handles, NULL,
237                                                             NULL, NULL, NULL,
238                                                             NULL);
239 }
240
241 static GList *
242 tp_chat_get_members (EmpathyContactList *list)
243 {
244         EmpathyTpChatPriv *priv = GET_PRIV (list);
245         GList             *members = NULL;
246
247         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL);
248
249         if (priv->members) {
250                 members = g_list_copy (priv->members);
251                 g_list_foreach (members, (GFunc) g_object_ref, NULL);
252         } else {
253                 members = g_list_prepend (members, g_object_ref (priv->user));
254                 if (priv->remote_contact != NULL)
255                         members = g_list_prepend (members, g_object_ref (priv->remote_contact));
256         }
257
258         return members;
259 }
260
261 static void
262 check_ready (EmpathyTpChat *chat)
263 {
264         EmpathyTpChatPriv *priv = GET_PRIV (chat);
265
266         if (priv->ready)
267                 return;
268
269         if (g_queue_get_length (priv->messages_queue) > 0)
270                 return;
271
272         DEBUG ("Ready");
273
274         priv->ready = TRUE;
275         g_object_notify (G_OBJECT (chat), "ready");
276 }
277
278 static void
279 tp_chat_emit_queued_messages (EmpathyTpChat *chat)
280 {
281         EmpathyTpChatPriv *priv = GET_PRIV (chat);
282         EmpathyMessage    *message;
283
284         /* Check if we can now emit some queued messages */
285         while ((message = g_queue_peek_head (priv->messages_queue)) != NULL) {
286                 if (empathy_message_get_sender (message) == NULL) {
287                         break;
288                 }
289
290                 DEBUG ("Queued message ready");
291                 g_queue_pop_head (priv->messages_queue);
292                 g_queue_push_tail (priv->pending_messages_queue, message);
293                 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
294         }
295
296         check_ready (chat);
297 }
298
299 static void
300 tp_chat_got_sender_cb (TpConnection            *connection,
301                        EmpathyContact          *contact,
302                        const GError            *error,
303                        gpointer                 message,
304                        GObject                 *chat)
305 {
306         EmpathyTpChatPriv *priv = GET_PRIV (chat);
307
308         if (error) {
309                 DEBUG ("Error: %s", error->message);
310                 /* Do not block the message queue, just drop this message */
311                 g_queue_remove (priv->messages_queue, message);
312         } else {
313                 empathy_message_set_sender (message, contact);
314         }
315
316         tp_chat_emit_queued_messages (EMPATHY_TP_CHAT (chat));
317 }
318
319 static void
320 tp_chat_build_message (EmpathyTpChat *chat,
321                        TpMessage     *msg,
322                        gboolean       incoming)
323 {
324         EmpathyTpChatPriv *priv;
325         EmpathyMessage    *message;
326         TpContact *sender;
327
328         priv = GET_PRIV (chat);
329
330         message = empathy_message_new_from_tp_message (msg, incoming);
331         /* FIXME: this is actually a lie for incoming messages. */
332         empathy_message_set_receiver (message, priv->user);
333
334         g_queue_push_tail (priv->messages_queue, message);
335
336         sender = tp_signalled_message_get_sender (msg);
337         g_assert (sender != NULL);
338
339         if (tp_contact_get_handle (sender) == 0) {
340                 empathy_message_set_sender (message, priv->user);
341                 tp_chat_emit_queued_messages (chat);
342         } else {
343                 empathy_tp_contact_factory_get_from_handle (priv->connection,
344                         tp_contact_get_handle (sender),
345                         tp_chat_got_sender_cb,
346                         message, NULL, G_OBJECT (chat));
347         }
348 }
349
350 static void
351 handle_delivery_report (EmpathyTpChat *self,
352                 TpMessage *message)
353 {
354         EmpathyTpChatPriv *priv = GET_PRIV (self);
355         TpDeliveryStatus delivery_status;
356         const GHashTable *header;
357         TpChannelTextSendError delivery_error;
358         gboolean valid;
359         GPtrArray *echo;
360         const gchar *message_body = NULL;
361         const gchar *delivery_dbus_error;
362         const gchar *delivery_token = NULL;
363
364         header = tp_message_peek (message, 0);
365         if (header == NULL)
366                 goto out;
367
368         delivery_token = tp_asv_get_string (header, "delivery-token");
369         delivery_status = tp_asv_get_uint32 (header, "delivery-status", &valid);
370
371         if (!valid) {
372                 goto out;
373         } else if (delivery_status == TP_DELIVERY_STATUS_ACCEPTED) {
374                 DEBUG ("Accepted %s", delivery_token);
375                 tp_chat_set_delivery_status (self, delivery_token,
376                         EMPATHY_DELIVERY_STATUS_ACCEPTED);
377                 goto out;
378         } else if (delivery_status == TP_DELIVERY_STATUS_DELIVERED) {
379                 DEBUG ("Delivered %s", delivery_token);
380                 tp_chat_set_delivery_status (self, delivery_token,
381                         EMPATHY_DELIVERY_STATUS_NONE);
382                 goto out;
383         } else if (delivery_status != TP_DELIVERY_STATUS_PERMANENTLY_FAILED) {
384                 goto out;
385         }
386
387         delivery_error = tp_asv_get_uint32 (header, "delivery-error", &valid);
388         if (!valid)
389                 delivery_error = TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN;
390
391         delivery_dbus_error = tp_asv_get_string (header, "delivery-dbus-error");
392
393         /* TODO: ideally we should use tp-glib API giving us the echoed message as a
394          * TpMessage. (fdo #35884) */
395         echo = tp_asv_get_boxed (header, "delivery-echo",
396                 TP_ARRAY_TYPE_MESSAGE_PART_LIST);
397         if (echo != NULL && echo->len >= 1) {
398                 const GHashTable *echo_body;
399
400                 echo_body = g_ptr_array_index (echo, 1);
401                 if (echo_body != NULL)
402                         message_body = tp_asv_get_string (echo_body, "content");
403         }
404
405         tp_chat_set_delivery_status (self, delivery_token,
406                         EMPATHY_DELIVERY_STATUS_NONE);
407         g_signal_emit (self, signals[SEND_ERROR], 0, message_body,
408                         delivery_error, delivery_dbus_error);
409
410 out:
411         tp_text_channel_ack_message_async (TP_TEXT_CHANNEL (priv->channel),
412                 message, NULL, NULL);
413 }
414
415 static void
416 handle_incoming_message (EmpathyTpChat *self,
417                          TpMessage *message,
418                          gboolean pending)
419 {
420         EmpathyTpChatPriv *priv = GET_PRIV (self);
421         gchar *message_body;
422
423         if (tp_message_is_delivery_report (message)) {
424                 handle_delivery_report (self, message);
425                 return;
426         }
427
428         message_body = tp_message_to_text (message, NULL);
429
430         DEBUG ("Message %s (channel %s): %s",
431                 pending ? "pending" : "received",
432                 tp_proxy_get_object_path (priv->channel), message_body);
433
434         if (message_body == NULL) {
435                 DEBUG ("Empty message with NonTextContent, ignoring and acking.");
436
437                 tp_text_channel_ack_message_async (TP_TEXT_CHANNEL (priv->channel),
438                         message, NULL, NULL);
439                 return;
440         }
441
442         tp_chat_build_message (self, message, TRUE);
443
444         g_free (message_body);
445 }
446
447 static void
448 message_received_cb (TpTextChannel   *channel,
449                      TpMessage *message,
450                      EmpathyTpChat *chat)
451 {
452         handle_incoming_message (chat, message, FALSE);
453 }
454
455 static gboolean
456 find_pending_message_func (gconstpointer a,
457                            gconstpointer b)
458 {
459         EmpathyMessage *msg = (EmpathyMessage *) a;
460         TpMessage *message = (TpMessage *) b;
461
462         if (empathy_message_get_tp_message (msg) == message)
463                 return 0;
464
465         return -1;
466 }
467
468 static void
469 pending_message_removed_cb (TpTextChannel   *channel,
470                             TpMessage *message,
471                             EmpathyTpChat *chat)
472 {
473         EmpathyTpChatPriv *priv = GET_PRIV (chat);
474         GList *m;
475
476         m = g_queue_find_custom (priv->pending_messages_queue, message,
477                                  find_pending_message_func);
478
479         if (m == NULL)
480                 return;
481
482         g_signal_emit (chat, signals[MESSAGE_ACKNOWLEDGED], 0, m->data);
483
484         g_object_unref (m->data);
485         g_queue_delete_link (priv->pending_messages_queue, m);
486 }
487
488 static void
489 message_sent_cb (TpTextChannel   *channel,
490                  TpMessage *message,
491                  TpMessageSendingFlags flags,
492                  gchar              *token,
493                  EmpathyTpChat      *chat)
494 {
495         gchar *message_body;
496
497         message_body = tp_message_to_text (message, NULL);
498
499         DEBUG ("Message sent: %s", message_body);
500
501         tp_chat_build_message (chat, message, FALSE);
502
503         g_free (message_body);
504 }
505
506 static TpChannelTextSendError
507 error_to_text_send_error (GError *error)
508 {
509         if (error->domain != TP_ERRORS)
510                 return TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN;
511
512         switch (error->code) {
513                 case TP_ERROR_OFFLINE:
514                         return TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE;
515                 case TP_ERROR_INVALID_HANDLE:
516                         return TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT;
517                 case TP_ERROR_PERMISSION_DENIED:
518                         return TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED;
519                 case TP_ERROR_NOT_IMPLEMENTED:
520                         return TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED;
521         }
522
523         return TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN;
524 }
525
526 static void
527 message_send_cb (GObject *source,
528                  GAsyncResult *result,
529                  gpointer      user_data)
530 {
531         EmpathyTpChat *chat = user_data;
532         TpTextChannel *channel = (TpTextChannel *) source;
533         gchar *token = NULL;
534         GError *error = NULL;
535
536         if (!tp_text_channel_send_message_finish (channel, result, &token, &error)) {
537                 DEBUG ("Error: %s", error->message);
538
539                 /* FIXME: we should use the body of the message as first argument of the
540                  * signal but can't easily get it as we just get a user_data pointer. Once
541                  * we'll have rebased EmpathyTpChat on top of TpTextChannel we'll be able
542                  * to use the user_data pointer to pass the message and fix this. */
543                 g_signal_emit (chat, signals[SEND_ERROR], 0,
544                                NULL, error_to_text_send_error (error), NULL);
545
546                 g_error_free (error);
547         }
548
549         tp_chat_set_delivery_status (chat, token,
550                 EMPATHY_DELIVERY_STATUS_SENDING);
551         g_free (token);
552 }
553
554 typedef struct {
555         EmpathyTpChat *chat;
556         TpChannelChatState state;
557 } StateChangedData;
558
559 static void
560 tp_chat_state_changed_got_contact_cb (TpConnection            *connection,
561                                       EmpathyContact          *contact,
562                                       const GError            *error,
563                                       gpointer                 user_data,
564                                       GObject                 *chat)
565 {
566         TpChannelChatState state;
567
568         if (error) {
569                 DEBUG ("Error: %s", error->message);
570                 return;
571         }
572
573         state = GPOINTER_TO_UINT (user_data);
574         DEBUG ("Chat state changed for %s (%d): %d",
575                 empathy_contact_get_alias (contact),
576                 empathy_contact_get_handle (contact), state);
577
578         g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
579 }
580
581 static void
582 tp_chat_state_changed_cb (TpChannel *channel,
583                           TpHandle   handle,
584                           TpChannelChatState state,
585                           EmpathyTpChat *chat)
586 {
587         EmpathyTpChatPriv *priv = GET_PRIV (chat);
588
589         empathy_tp_contact_factory_get_from_handle (priv->connection, handle,
590                 tp_chat_state_changed_got_contact_cb, GUINT_TO_POINTER (state),
591                 NULL, G_OBJECT (chat));
592 }
593
594 static void
595 list_pending_messages (EmpathyTpChat *self)
596 {
597         EmpathyTpChatPriv *priv = GET_PRIV (self);
598         GList *messages, *l;
599
600         g_assert (priv->channel != NULL);
601
602         messages = tp_text_channel_get_pending_messages (
603                 TP_TEXT_CHANNEL (priv->channel));
604
605         for (l = messages; l != NULL; l = g_list_next (l)) {
606                 TpMessage *message = l->data;
607
608                 handle_incoming_message (self, message, FALSE);
609         }
610
611         g_list_free (messages);
612 }
613
614 static void
615 tp_chat_property_flags_changed_cb (TpProxy         *proxy,
616                                    const GPtrArray *properties,
617                                    gpointer         user_data,
618                                    GObject         *chat)
619 {
620         EmpathyTpChatPriv *priv = GET_PRIV (chat);
621         guint              i, j;
622
623         if (priv->channel == NULL)
624                 return;
625
626         if (!priv->had_properties_list || !properties) {
627                 return;
628         }
629
630         for (i = 0; i < properties->len; i++) {
631                 GValueArray           *prop_struct;
632                 EmpathyTpChatProperty *property;
633                 guint                  id;
634                 guint                  flags;
635
636                 prop_struct = g_ptr_array_index (properties, i);
637                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
638                 flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 1));
639
640                 for (j = 0; j < priv->properties->len; j++) {
641                         property = g_ptr_array_index (priv->properties, j);
642                         if (property->id == id) {
643                                 property->flags = flags;
644                                 DEBUG ("property %s flags changed: %d",
645                                         property->name, property->flags);
646                                 break;
647                         }
648                 }
649         }
650 }
651
652 static void
653 tp_chat_properties_changed_cb (TpProxy         *proxy,
654                                const GPtrArray *properties,
655                                gpointer         user_data,
656                                GObject         *chat)
657 {
658         EmpathyTpChatPriv *priv = GET_PRIV (chat);
659         guint              i, j;
660
661         if (priv->channel == NULL)
662                 return;
663
664         if (!priv->had_properties_list || !properties) {
665                 return;
666         }
667
668         for (i = 0; i < properties->len; i++) {
669                 GValueArray           *prop_struct;
670                 EmpathyTpChatProperty *property;
671                 guint                  id;
672                 GValue                *src_value;
673
674                 prop_struct = g_ptr_array_index (properties, i);
675                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
676                 src_value = g_value_get_boxed (g_value_array_get_nth (prop_struct, 1));
677
678                 for (j = 0; j < priv->properties->len; j++) {
679                         property = g_ptr_array_index (priv->properties, j);
680                         if (property->id == id) {
681                                 if (property->value) {
682                                         g_value_copy (src_value, property->value);
683                                 } else {
684                                         property->value = tp_g_value_slice_dup (src_value);
685                                 }
686
687                                 DEBUG ("property %s changed", property->name);
688                                 g_signal_emit (chat, signals[PROPERTY_CHANGED], 0,
689                                                property->name, property->value);
690                                 break;
691                         }
692                 }
693         }
694 }
695
696 static void
697 tp_chat_get_properties_cb (TpProxy         *proxy,
698                            const GPtrArray *properties,
699                            const GError    *error,
700                            gpointer         user_data,
701                            GObject         *chat)
702 {
703         if (error) {
704                 DEBUG ("Error getting properties: %s", error->message);
705                 return;
706         }
707
708         tp_chat_properties_changed_cb (proxy, properties, user_data, chat);
709 }
710
711 static void
712 tp_chat_list_properties_cb (TpProxy         *proxy,
713                             const GPtrArray *properties,
714                             const GError    *error,
715                             gpointer         user_data,
716                             GObject         *chat)
717 {
718         EmpathyTpChatPriv *priv = GET_PRIV (chat);
719         GArray            *ids;
720         guint              i;
721
722         if (priv->channel == NULL)
723                 return;
724
725         priv->had_properties_list = TRUE;
726
727         if (error) {
728                 DEBUG ("Error listing properties: %s", error->message);
729                 return;
730         }
731
732         ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), properties->len);
733         priv->properties = g_ptr_array_sized_new (properties->len);
734         for (i = 0; i < properties->len; i++) {
735                 GValueArray           *prop_struct;
736                 EmpathyTpChatProperty *property;
737
738                 prop_struct = g_ptr_array_index (properties, i);
739                 property = g_slice_new0 (EmpathyTpChatProperty);
740                 property->id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
741                 property->name = g_value_dup_string (g_value_array_get_nth (prop_struct, 1));
742                 property->flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 3));
743
744                 DEBUG ("Adding property name=%s id=%d flags=%d",
745                         property->name, property->id, property->flags);
746                 g_ptr_array_add (priv->properties, property);
747                 if (property->flags & TP_PROPERTY_FLAG_READ) {
748                         g_array_append_val (ids, property->id);
749                 }
750         }
751
752         tp_cli_properties_interface_call_get_properties (proxy, -1,
753                                                          ids,
754                                                          tp_chat_get_properties_cb,
755                                                          NULL, NULL,
756                                                          chat);
757
758         g_array_free (ids, TRUE);
759 }
760
761 void
762 empathy_tp_chat_set_property (EmpathyTpChat *chat,
763                               const gchar   *name,
764                               const GValue  *value)
765 {
766         EmpathyTpChatPriv     *priv = GET_PRIV (chat);
767         EmpathyTpChatProperty *property;
768         guint                  i;
769
770         if (!priv->had_properties_list) {
771                 return;
772         }
773
774         for (i = 0; i < priv->properties->len; i++) {
775                 property = g_ptr_array_index (priv->properties, i);
776                 if (!tp_strdiff (property->name, name)) {
777                         GPtrArray   *properties;
778                         GValueArray *prop;
779                         GValue       id = {0, };
780                         GValue       dest_value = {0, };
781
782                         if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) {
783                                 break;
784                         }
785
786                         g_value_init (&id, G_TYPE_UINT);
787                         g_value_init (&dest_value, G_TYPE_VALUE);
788                         g_value_set_uint (&id, property->id);
789                         g_value_set_boxed (&dest_value, value);
790
791                         prop = g_value_array_new (2);
792                         g_value_array_append (prop, &id);
793                         g_value_array_append (prop, &dest_value);
794
795                         properties = g_ptr_array_sized_new (1);
796                         g_ptr_array_add (properties, prop);
797
798                         DEBUG ("Set property %s", name);
799                         tp_cli_properties_interface_call_set_properties (priv->channel, -1,
800                                                                          properties,
801                                                                          (tp_cli_properties_interface_callback_for_set_properties)
802                                                                          tp_chat_async_cb,
803                                                                          "Seting property", NULL,
804                                                                          G_OBJECT (chat));
805
806                         g_ptr_array_free (properties, TRUE);
807                         g_value_array_free (prop);
808
809                         break;
810                 }
811         }
812 }
813
814 EmpathyTpChatProperty *
815 empathy_tp_chat_get_property (EmpathyTpChat *chat,
816                               const gchar   *name)
817 {
818         EmpathyTpChatPriv     *priv = GET_PRIV (chat);
819         EmpathyTpChatProperty *property;
820         guint                  i;
821
822         if (!priv->had_properties_list) {
823                 return NULL;
824         }
825
826         for (i = 0; i < priv->properties->len; i++) {
827                 property = g_ptr_array_index (priv->properties, i);
828                 if (!tp_strdiff (property->name, name)) {
829                         return property;
830                 }
831         }
832
833         return NULL;
834 }
835
836 GPtrArray *
837 empathy_tp_chat_get_properties (EmpathyTpChat *chat)
838 {
839         EmpathyTpChatPriv *priv = GET_PRIV (chat);
840
841         return priv->properties;
842 }
843
844 static void
845 tp_chat_dispose (GObject *object)
846 {
847         EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
848         EmpathyTpChatPriv *priv = GET_PRIV (self);
849
850         if (priv->dispose_has_run)
851                 return;
852
853         priv->dispose_has_run = TRUE;
854
855         tp_clear_object (&priv->account);
856
857         if (priv->connection != NULL)
858                 g_object_unref (priv->connection);
859         priv->connection = NULL;
860
861         if (priv->channel != NULL) {
862                 g_signal_handlers_disconnect_by_func (priv->channel,
863                         tp_chat_invalidated_cb, self);
864                 g_object_unref (priv->channel);
865         }
866         priv->channel = NULL;
867
868         if (priv->remote_contact != NULL)
869                 g_object_unref (priv->remote_contact);
870         priv->remote_contact = NULL;
871
872         if (priv->user != NULL)
873                 g_object_unref (priv->user);
874         priv->user = NULL;
875
876         g_queue_foreach (priv->messages_queue, (GFunc) g_object_unref, NULL);
877         g_queue_clear (priv->messages_queue);
878
879         g_queue_foreach (priv->pending_messages_queue,
880                 (GFunc) g_object_unref, NULL);
881         g_queue_clear (priv->pending_messages_queue);
882
883         if (G_OBJECT_CLASS (empathy_tp_chat_parent_class)->dispose)
884                 G_OBJECT_CLASS (empathy_tp_chat_parent_class)->dispose (object);
885 }
886
887 static void
888 tp_chat_finalize (GObject *object)
889 {
890         EmpathyTpChatPriv *priv = GET_PRIV (object);
891         guint              i;
892
893         DEBUG ("Finalize: %p", object);
894
895         if (priv->properties) {
896                 for (i = 0; i < priv->properties->len; i++) {
897                         EmpathyTpChatProperty *property;
898
899                         property = g_ptr_array_index (priv->properties, i);
900                         g_free (property->name);
901                         if (property->value) {
902                                 tp_g_value_slice_free (property->value);
903                         }
904                         g_slice_free (EmpathyTpChatProperty, property);
905                 }
906                 g_ptr_array_free (priv->properties, TRUE);
907         }
908
909         g_queue_free (priv->messages_queue);
910         g_queue_free (priv->pending_messages_queue);
911         g_hash_table_destroy (priv->messages_being_sent);
912
913         G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
914 }
915
916 static void
917 check_almost_ready (EmpathyTpChat *chat)
918 {
919         EmpathyTpChatPriv *priv = GET_PRIV (chat);
920
921         if (priv->ready)
922                 return;
923
924         if (priv->user == NULL)
925                 return;
926
927         if (!priv->got_password_flags)
928                 return;
929
930         if (!priv->got_sms_channel)
931                 return;
932
933         /* We need either the members (room) or the remote contact (private chat).
934          * If the chat is protected by a password we can't get these information so
935          * consider the chat as ready so it can be presented to the user. */
936         if (!empathy_tp_chat_password_needed (chat) && priv->members == NULL &&
937             priv->remote_contact == NULL)
938                 return;
939
940         /* We use the default factory so this feature should have been prepared */
941         g_assert (tp_proxy_is_prepared (priv->channel,
942                 TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES));
943
944         tp_g_signal_connect_object (priv->channel, "message-received",
945                 G_CALLBACK (message_received_cb), chat, 0);
946         tp_g_signal_connect_object (priv->channel, "pending-message-removed",
947                 G_CALLBACK (pending_message_removed_cb), chat, 0);
948
949         list_pending_messages (chat);
950
951         tp_g_signal_connect_object (priv->channel, "message-sent",
952                 G_CALLBACK (message_sent_cb), chat, 0);
953
954         tp_g_signal_connect_object (priv->channel, "chat-state-changed",
955                 G_CALLBACK (tp_chat_state_changed_cb), chat, 0);
956
957         check_ready (chat);
958 }
959
960 static void
961 tp_chat_update_remote_contact (EmpathyTpChat *chat)
962 {
963         EmpathyTpChatPriv *priv = GET_PRIV (chat);
964         EmpathyContact *contact = NULL;
965         TpHandle self_handle;
966         TpHandleType handle_type;
967         GList *l;
968
969         /* If this is a named chatroom, never pretend it is a private chat */
970         tp_channel_get_handle (priv->channel, &handle_type);
971         if (handle_type == TP_HANDLE_TYPE_ROOM) {
972                 return;
973         }
974
975         /* This is an MSN chat, but it's the new style where 1-1 chats don't
976          * have the group interface. If it has the conference interface, then
977          * it is indeed a MUC. */
978         if (tp_proxy_has_interface_by_id (priv->channel,
979                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_CONFERENCE)) {
980                 return;
981         }
982
983         /* This is an MSN-like chat where anyone can join the chat at anytime.
984          * If there is only one non-self contact member, we are in a private
985          * chat and we set the "remote-contact" property to that contact. If
986          * there are more, set the "remote-contact" property to NULL and the
987          * UI will display a contact list. */
988         self_handle = tp_channel_group_get_self_handle (priv->channel);
989         for (l = priv->members; l; l = l->next) {
990                 /* Skip self contact if member */
991                 if (empathy_contact_get_handle (l->data) == self_handle) {
992                         continue;
993                 }
994
995                 /* We have more than one remote contact, break */
996                 if (contact != NULL) {
997                         contact = NULL;
998                         break;
999                 }
1000
1001                 /* If we didn't find yet a remote contact, keep this one */
1002                 contact = l->data;
1003         }
1004
1005         if (priv->remote_contact == contact) {
1006                 return;
1007         }
1008
1009         DEBUG ("Changing remote contact from %p to %p",
1010                 priv->remote_contact, contact);
1011
1012         if (priv->remote_contact) {
1013                 g_object_unref (priv->remote_contact);
1014         }
1015
1016         priv->remote_contact = contact ? g_object_ref (contact) : NULL;
1017         g_object_notify (G_OBJECT (chat), "remote-contact");
1018 }
1019
1020 static void
1021 tp_chat_got_added_contacts_cb (TpConnection            *connection,
1022                                guint                    n_contacts,
1023                                EmpathyContact * const * contacts,
1024                                guint                    n_failed,
1025                                const TpHandle          *failed,
1026                                const GError            *error,
1027                                gpointer                 user_data,
1028                                GObject                 *chat)
1029 {
1030         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1031         guint i;
1032         const TpIntSet *members;
1033         TpHandle handle;
1034         EmpathyContact *contact;
1035
1036         if (error) {
1037                 DEBUG ("Error: %s", error->message);
1038                 return;
1039         }
1040
1041         members = tp_channel_group_get_members (priv->channel);
1042         for (i = 0; i < n_contacts; i++) {
1043                 contact = contacts[i];
1044                 handle = empathy_contact_get_handle (contact);
1045
1046                 /* Make sure the contact is still member */
1047                 if (tp_intset_is_member (members, handle)) {
1048                         priv->members = g_list_prepend (priv->members,
1049                                 g_object_ref (contact));
1050                         g_signal_emit_by_name (chat, "members-changed",
1051                                                contact, NULL, 0, NULL, TRUE);
1052                 }
1053         }
1054
1055         tp_chat_update_remote_contact (EMPATHY_TP_CHAT (chat));
1056         check_almost_ready (EMPATHY_TP_CHAT (chat));
1057 }
1058
1059 static EmpathyContact *
1060 chat_lookup_contact (EmpathyTpChat *chat,
1061                      TpHandle       handle,
1062                      gboolean       remove_)
1063 {
1064         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1065         GList *l;
1066
1067         for (l = priv->members; l; l = l->next) {
1068                 EmpathyContact *c = l->data;
1069
1070                 if (empathy_contact_get_handle (c) != handle) {
1071                         continue;
1072                 }
1073
1074                 if (remove_) {
1075                         /* Caller takes the reference. */
1076                         priv->members = g_list_delete_link (priv->members, l);
1077                 } else {
1078                         g_object_ref (c);
1079                 }
1080
1081                 return c;
1082         }
1083
1084         return NULL;
1085 }
1086
1087 typedef struct
1088 {
1089     TpHandle old_handle;
1090     guint reason;
1091     gchar *message;
1092 } ContactRenameData;
1093
1094 static ContactRenameData *
1095 contact_rename_data_new (TpHandle handle,
1096                          guint reason,
1097                          const gchar* message)
1098 {
1099         ContactRenameData *data = g_new (ContactRenameData, 1);
1100         data->old_handle = handle;
1101         data->reason = reason;
1102         data->message = g_strdup (message);
1103
1104         return data;
1105 }
1106
1107 static void
1108 contact_rename_data_free (ContactRenameData* data)
1109 {
1110         g_free (data->message);
1111         g_free (data);
1112 }
1113
1114 static void
1115 tp_chat_got_renamed_contacts_cb (TpConnection            *connection,
1116                                  guint                    n_contacts,
1117                                  EmpathyContact * const * contacts,
1118                                  guint                    n_failed,
1119                                  const TpHandle          *failed,
1120                                  const GError            *error,
1121                                  gpointer                 user_data,
1122                                  GObject                 *chat)
1123 {
1124         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1125         const TpIntSet *members;
1126         TpHandle handle;
1127         EmpathyContact *old = NULL, *new = NULL;
1128         ContactRenameData *rename_data = (ContactRenameData *) user_data;
1129
1130         if (error) {
1131                 DEBUG ("Error: %s", error->message);
1132                 return;
1133         }
1134
1135         /* renamed members can only be delivered one at a time */
1136         g_warn_if_fail (n_contacts == 1);
1137
1138         new = contacts[0];
1139
1140         members = tp_channel_group_get_members (priv->channel);
1141         handle = empathy_contact_get_handle (new);
1142
1143         old = chat_lookup_contact (EMPATHY_TP_CHAT (chat),
1144                                    rename_data->old_handle, TRUE);
1145
1146         /* Make sure the contact is still member */
1147         if (tp_intset_is_member (members, handle)) {
1148                 priv->members = g_list_prepend (priv->members,
1149                         g_object_ref (new));
1150
1151                 if (old != NULL) {
1152                         g_signal_emit_by_name (chat, "member-renamed",
1153                                                old, new, rename_data->reason,
1154                                                rename_data->message);
1155                         g_object_unref (old);
1156                 }
1157         }
1158
1159         if (priv->user == old) {
1160                 /* We change our nick */
1161                 tp_clear_object (&priv->user);
1162                 priv->user = g_object_ref (new);
1163         }
1164
1165         tp_chat_update_remote_contact (EMPATHY_TP_CHAT (chat));
1166         check_almost_ready (EMPATHY_TP_CHAT (chat));
1167 }
1168
1169
1170 static void
1171 tp_chat_group_members_changed_cb (TpChannel     *self,
1172                                   gchar         *message,
1173                                   GArray        *added,
1174                                   GArray        *removed,
1175                                   GArray        *local_pending,
1176                                   GArray        *remote_pending,
1177                                   guint          actor,
1178                                   guint          reason,
1179                                   EmpathyTpChat *chat)
1180 {
1181         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1182         EmpathyContact *contact;
1183         EmpathyContact *actor_contact = NULL;
1184         guint i;
1185         ContactRenameData *rename_data;
1186         TpHandle old_handle;
1187
1188         /* Contact renamed */
1189         if (reason == TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED) {
1190                 /* there can only be a single 'added' and a single 'removed' handle */
1191                 if (removed->len != 1 || added->len != 1) {
1192                         g_warning ("RENAMED with %u added, %u removed (expected 1, 1)",
1193                                 added->len, removed->len);
1194                         return;
1195                 }
1196
1197                 old_handle = g_array_index (removed, guint, 0);
1198
1199                 rename_data = contact_rename_data_new (old_handle, reason, message);
1200                 empathy_tp_contact_factory_get_from_handles (priv->connection,
1201                         added->len, (TpHandle *) added->data,
1202                         tp_chat_got_renamed_contacts_cb,
1203                         rename_data, (GDestroyNotify) contact_rename_data_free,
1204                         G_OBJECT (chat));
1205                 return;
1206         }
1207
1208         if (actor != 0) {
1209                 actor_contact = chat_lookup_contact (chat, actor, FALSE);
1210                 if (actor_contact == NULL) {
1211                         /* FIXME: handle this a tad more gracefully: perhaps
1212                          * the actor was a server op. We could use the
1213                          * contact-ids detail of MembersChangedDetailed.
1214                          */
1215                         DEBUG ("actor %u not a channel member", actor);
1216                 }
1217         }
1218
1219         /* Remove contacts that are not members anymore */
1220         for (i = 0; i < removed->len; i++) {
1221                 contact = chat_lookup_contact (chat,
1222                         g_array_index (removed, TpHandle, i), TRUE);
1223
1224                 if (contact != NULL) {
1225                         g_signal_emit_by_name (chat, "members-changed", contact,
1226                                                actor_contact, reason, message,
1227                                                FALSE);
1228                         g_object_unref (contact);
1229                 }
1230         }
1231
1232         /* Request added contacts */
1233         if (added->len > 0) {
1234                 empathy_tp_contact_factory_get_from_handles (priv->connection,
1235                         added->len, (TpHandle *) added->data,
1236                         tp_chat_got_added_contacts_cb, NULL, NULL,
1237                         G_OBJECT (chat));
1238         }
1239
1240         tp_chat_update_remote_contact (chat);
1241
1242         if (actor_contact != NULL) {
1243                 g_object_unref (actor_contact);
1244         }
1245 }
1246
1247 static void
1248 tp_chat_got_remote_contact_cb (TpConnection            *connection,
1249                                EmpathyContact          *contact,
1250                                const GError            *error,
1251                                gpointer                 user_data,
1252                                GObject                 *chat)
1253 {
1254         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1255
1256         if (error) {
1257                 DEBUG ("Error: %s", error->message);
1258                 empathy_tp_chat_leave (EMPATHY_TP_CHAT (chat), "");
1259                 return;
1260         }
1261
1262         priv->remote_contact = g_object_ref (contact);
1263         g_object_notify (chat, "remote-contact");
1264
1265         check_almost_ready (EMPATHY_TP_CHAT (chat));
1266 }
1267
1268 static void
1269 tp_chat_got_self_contact_cb (TpConnection            *connection,
1270                              EmpathyContact          *contact,
1271                              const GError            *error,
1272                              gpointer                 user_data,
1273                              GObject                 *chat)
1274 {
1275         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1276
1277         if (error) {
1278                 DEBUG ("Error: %s", error->message);
1279                 empathy_tp_chat_leave (EMPATHY_TP_CHAT (chat), "");
1280                 return;
1281         }
1282
1283         priv->user = g_object_ref (contact);
1284         empathy_contact_set_is_user (priv->user, TRUE);
1285         check_almost_ready (EMPATHY_TP_CHAT (chat));
1286 }
1287
1288 static void
1289 password_flags_changed_cb (TpChannel *channel,
1290     guint added,
1291     guint removed,
1292     gpointer user_data,
1293     GObject *weak_object)
1294 {
1295         EmpathyTpChat *self = EMPATHY_TP_CHAT (weak_object);
1296         EmpathyTpChatPriv *priv = GET_PRIV (self);
1297         gboolean was_needed, needed;
1298
1299         was_needed = empathy_tp_chat_password_needed (self);
1300
1301         priv->password_flags |= added;
1302         priv->password_flags ^= removed;
1303
1304         needed = empathy_tp_chat_password_needed (self);
1305
1306         if (was_needed != needed)
1307                 g_object_notify (G_OBJECT (self), "password-needed");
1308 }
1309
1310 static void
1311 got_password_flags_cb (TpChannel *proxy,
1312                              guint password_flags,
1313                              const GError *error,
1314                              gpointer user_data,
1315                              GObject *weak_object)
1316 {
1317         EmpathyTpChat *self = EMPATHY_TP_CHAT (weak_object);
1318         EmpathyTpChatPriv *priv = GET_PRIV (self);
1319
1320         priv->got_password_flags = TRUE;
1321         priv->password_flags = password_flags;
1322
1323         check_almost_ready (EMPATHY_TP_CHAT (self));
1324 }
1325
1326 static void
1327 sms_channel_changed_cb (TpChannel *channel,
1328                         gboolean   sms_channel,
1329                         gpointer   user_data,
1330                         GObject   *chat)
1331 {
1332         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1333
1334         priv->sms_channel = sms_channel;
1335
1336         g_object_notify (G_OBJECT (chat), "sms-channel");
1337 }
1338
1339 static void
1340 get_sms_channel_cb (TpProxy      *channel,
1341                     const GValue *value,
1342                     const GError *in_error,
1343                     gpointer      user_data,
1344                     GObject      *chat)
1345 {
1346         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1347
1348         if (in_error != NULL) {
1349                 DEBUG ("Failed to get SMSChannel: %s", in_error->message);
1350                 return;
1351         }
1352
1353         g_return_if_fail (G_VALUE_HOLDS_BOOLEAN (value));
1354
1355         priv->sms_channel = g_value_get_boolean (value);
1356         priv->got_sms_channel = TRUE;
1357
1358         check_almost_ready (EMPATHY_TP_CHAT (chat));
1359 }
1360
1361 static GObject *
1362 tp_chat_constructor (GType                  type,
1363                      guint                  n_props,
1364                      GObjectConstructParam *props)
1365 {
1366         GObject           *chat;
1367         EmpathyTpChatPriv *priv;
1368         TpHandle           handle;
1369
1370         chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
1371
1372         priv = GET_PRIV (chat);
1373
1374         priv->connection = g_object_ref (tp_account_get_connection (priv->account));
1375         tp_g_signal_connect_object (priv->channel, "invalidated",
1376                           G_CALLBACK (tp_chat_invalidated_cb),
1377                           chat, 0);
1378
1379         g_assert (tp_proxy_is_prepared (priv->connection,
1380                 TP_CONNECTION_FEATURE_CAPABILITIES));
1381
1382         if (tp_proxy_has_interface_by_id (priv->channel,
1383                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
1384                 const TpIntSet *members;
1385                 GArray *handles;
1386
1387                 /* Get self contact from the group's self handle */
1388                 handle = tp_channel_group_get_self_handle (priv->channel);
1389                 empathy_tp_contact_factory_get_from_handle (priv->connection,
1390                         handle, tp_chat_got_self_contact_cb,
1391                         NULL, NULL, chat);
1392
1393                 /* Get initial member contacts */
1394                 members = tp_channel_group_get_members (priv->channel);
1395                 handles = tp_intset_to_array (members);
1396                 empathy_tp_contact_factory_get_from_handles (priv->connection,
1397                         handles->len, (TpHandle *) handles->data,
1398                         tp_chat_got_added_contacts_cb, NULL, NULL, chat);
1399
1400                 priv->can_upgrade_to_muc = FALSE;
1401
1402                 tp_g_signal_connect_object (priv->channel, "group-members-changed",
1403                         G_CALLBACK (tp_chat_group_members_changed_cb), chat, 0);
1404         } else {
1405                 TpCapabilities *caps;
1406                 GPtrArray *classes;
1407                 guint i;
1408
1409                 /* Get the self contact from the connection's self handle */
1410                 handle = tp_connection_get_self_handle (priv->connection);
1411                 empathy_tp_contact_factory_get_from_handle (priv->connection,
1412                         handle, tp_chat_got_self_contact_cb,
1413                         NULL, NULL, chat);
1414
1415                 /* Get the remote contact */
1416                 handle = tp_channel_get_handle (priv->channel, NULL);
1417                 empathy_tp_contact_factory_get_from_handle (priv->connection,
1418                         handle, tp_chat_got_remote_contact_cb,
1419                         NULL, NULL, chat);
1420
1421                 caps = tp_connection_get_capabilities (priv->connection);
1422                 g_assert (caps != NULL);
1423
1424                 classes = tp_capabilities_get_channel_classes (caps);
1425
1426                 for (i = 0; i < classes->len; i++) {
1427                         GValueArray *array = g_ptr_array_index (classes, i);
1428                         const char **oprops = g_value_get_boxed (
1429                                 g_value_array_get_nth (array, 1));
1430
1431                         if (tp_strv_contains (oprops, TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_CHANNELS)) {
1432                                 priv->can_upgrade_to_muc = TRUE;
1433                                 break;
1434                         }
1435                 }
1436         }
1437
1438         if (tp_proxy_has_interface_by_id (priv->channel,
1439                                           TP_IFACE_QUARK_PROPERTIES_INTERFACE)) {
1440                 tp_cli_properties_interface_call_list_properties (priv->channel, -1,
1441                                                                   tp_chat_list_properties_cb,
1442                                                                   NULL, NULL,
1443                                                                   G_OBJECT (chat));
1444                 tp_cli_properties_interface_connect_to_properties_changed (priv->channel,
1445                                                                            tp_chat_properties_changed_cb,
1446                                                                            NULL, NULL,
1447                                                                            G_OBJECT (chat), NULL);
1448                 tp_cli_properties_interface_connect_to_property_flags_changed (priv->channel,
1449                                                                                tp_chat_property_flags_changed_cb,
1450                                                                                NULL, NULL,
1451                                                                                G_OBJECT (chat), NULL);
1452         }
1453
1454         /* Check if the chat is password protected */
1455         if (tp_proxy_has_interface_by_id (priv->channel,
1456                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_PASSWORD)) {
1457                 priv->got_password_flags = FALSE;
1458
1459                 tp_cli_channel_interface_password_connect_to_password_flags_changed
1460                         (priv->channel, password_flags_changed_cb, chat, NULL,
1461                          G_OBJECT (chat), NULL);
1462
1463                 tp_cli_channel_interface_password_call_get_password_flags
1464                         (priv->channel, -1, got_password_flags_cb, chat, NULL, chat);
1465         } else {
1466                 /* No Password interface, so no need to fetch the password flags */
1467                 priv->got_password_flags = TRUE;
1468         }
1469
1470         /* Check if the chat is for SMS */
1471         if (tp_proxy_has_interface_by_id (priv->channel,
1472                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_SMS)) {
1473                 tp_cli_channel_interface_sms_connect_to_sms_channel_changed (
1474                         priv->channel,
1475                         sms_channel_changed_cb, chat, NULL, G_OBJECT (chat),
1476                         NULL);
1477
1478                 tp_cli_dbus_properties_call_get (priv->channel, -1,
1479                         TP_IFACE_CHANNEL_INTERFACE_SMS, "SMSChannel",
1480                         get_sms_channel_cb, chat, NULL, G_OBJECT (chat));
1481         } else {
1482                 /* if there's no SMS support, then we're not waiting for it */
1483                 priv->got_sms_channel = TRUE;
1484         }
1485
1486         return chat;
1487 }
1488
1489 static void
1490 tp_chat_get_property (GObject    *object,
1491                       guint       param_id,
1492                       GValue     *value,
1493                       GParamSpec *pspec)
1494 {
1495         EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
1496         EmpathyTpChatPriv *priv = GET_PRIV (object);
1497
1498         switch (param_id) {
1499         case PROP_ACCOUNT:
1500                 g_value_set_object (value, priv->account);
1501                 break;
1502         case PROP_CHANNEL:
1503                 g_value_set_object (value, priv->channel);
1504                 break;
1505         case PROP_REMOTE_CONTACT:
1506                 g_value_set_object (value, priv->remote_contact);
1507                 break;
1508         case PROP_READY:
1509                 g_value_set_boolean (value, priv->ready);
1510                 break;
1511         case PROP_PASSWORD_NEEDED:
1512                 g_value_set_boolean (value, empathy_tp_chat_password_needed (self));
1513                 break;
1514         case PROP_SMS_CHANNEL:
1515                 g_value_set_boolean (value, priv->sms_channel);
1516                 break;
1517         case PROP_N_MESSAGES_SENDING:
1518                 g_value_set_uint (value,
1519                         g_hash_table_size (priv->messages_being_sent));
1520                 break;
1521         default:
1522                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1523                 break;
1524         };
1525 }
1526
1527 static void
1528 tp_chat_set_property (GObject      *object,
1529                       guint         param_id,
1530                       const GValue *value,
1531                       GParamSpec   *pspec)
1532 {
1533         EmpathyTpChatPriv *priv = GET_PRIV (object);
1534
1535         switch (param_id) {
1536         case PROP_ACCOUNT:
1537                 priv->account = g_value_dup_object (value);
1538                 break;
1539         case PROP_CHANNEL:
1540                 priv->channel = g_value_dup_object (value);
1541                 break;
1542         default:
1543                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1544                 break;
1545         };
1546 }
1547
1548 static void
1549 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
1550 {
1551         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1552
1553         object_class->dispose = tp_chat_dispose;
1554         object_class->finalize = tp_chat_finalize;
1555         object_class->constructor = tp_chat_constructor;
1556         object_class->get_property = tp_chat_get_property;
1557         object_class->set_property = tp_chat_set_property;
1558
1559         g_object_class_install_property (object_class,
1560                                          PROP_ACCOUNT,
1561                                          g_param_spec_object ("account",
1562                                                               "TpAccount",
1563                                                               "the account associated with the chat",
1564                                                               TP_TYPE_ACCOUNT,
1565                                                               G_PARAM_READWRITE |
1566                                                               G_PARAM_CONSTRUCT_ONLY |
1567                                                               G_PARAM_STATIC_STRINGS));
1568
1569         g_object_class_install_property (object_class,
1570                                          PROP_CHANNEL,
1571                                          g_param_spec_object ("channel",
1572                                                               "telepathy channel",
1573                                                               "The text channel for the chat",
1574                                                               TP_TYPE_CHANNEL,
1575                                                               G_PARAM_READWRITE |
1576                                                               G_PARAM_CONSTRUCT_ONLY));
1577
1578         g_object_class_install_property (object_class,
1579                                          PROP_REMOTE_CONTACT,
1580                                          g_param_spec_object ("remote-contact",
1581                                                               "The remote contact",
1582                                                               "The remote contact if there is no group iface on the channel",
1583                                                               EMPATHY_TYPE_CONTACT,
1584                                                               G_PARAM_READABLE));
1585
1586         g_object_class_install_property (object_class,
1587                                          PROP_READY,
1588                                          g_param_spec_boolean ("ready",
1589                                                                "Is the object ready",
1590                                                                "This object can't be used until this becomes true",
1591                                                                FALSE,
1592                                                                G_PARAM_READABLE));
1593
1594         g_object_class_install_property (object_class,
1595                                          PROP_PASSWORD_NEEDED,
1596                                          g_param_spec_boolean ("password-needed",
1597                                                                "password needed",
1598                                                                "TRUE if a password is needed to join the channel",
1599                                                                FALSE,
1600                                                                G_PARAM_READABLE));
1601
1602         g_object_class_install_property (object_class,
1603                                          PROP_SMS_CHANNEL,
1604                                          g_param_spec_boolean ("sms-channel",
1605                                                                "SMS Channel",
1606                                                                "TRUE if channel is for sending SMSes",
1607                                                                FALSE,
1608                                                                G_PARAM_READABLE));
1609
1610         g_object_class_install_property (object_class,
1611                                          PROP_N_MESSAGES_SENDING,
1612                                          g_param_spec_uint ("n-messages-sending",
1613                                                             "Num Messages Sending",
1614                                                             "The number of messages being sent",
1615                                                             0, G_MAXUINT, 0,
1616                                                             G_PARAM_READABLE));
1617
1618         /* Signals */
1619         signals[MESSAGE_RECEIVED] =
1620                 g_signal_new ("message-received",
1621                               G_TYPE_FROM_CLASS (klass),
1622                               G_SIGNAL_RUN_LAST,
1623                               0,
1624                               NULL, NULL,
1625                               g_cclosure_marshal_VOID__OBJECT,
1626                               G_TYPE_NONE,
1627                               1, EMPATHY_TYPE_MESSAGE);
1628
1629         signals[SEND_ERROR] =
1630                 g_signal_new ("send-error",
1631                               G_TYPE_FROM_CLASS (klass),
1632                               G_SIGNAL_RUN_LAST,
1633                               0,
1634                               NULL, NULL,
1635                               _empathy_marshal_VOID__STRING_UINT_STRING,
1636                               G_TYPE_NONE,
1637                               3, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING);
1638
1639         signals[CHAT_STATE_CHANGED] =
1640                 g_signal_new ("chat-state-changed",
1641                               G_TYPE_FROM_CLASS (klass),
1642                               G_SIGNAL_RUN_LAST,
1643                               0,
1644                               NULL, NULL,
1645                               _empathy_marshal_VOID__OBJECT_UINT,
1646                               G_TYPE_NONE,
1647                               2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT);
1648
1649         signals[PROPERTY_CHANGED] =
1650                 g_signal_new ("property-changed",
1651                               G_TYPE_FROM_CLASS (klass),
1652                               G_SIGNAL_RUN_LAST,
1653                               0,
1654                               NULL, NULL,
1655                               _empathy_marshal_VOID__STRING_BOXED,
1656                               G_TYPE_NONE,
1657                               2, G_TYPE_STRING, G_TYPE_VALUE);
1658
1659         signals[DESTROY] =
1660                 g_signal_new ("destroy",
1661                               G_TYPE_FROM_CLASS (klass),
1662                               G_SIGNAL_RUN_LAST,
1663                               0,
1664                               NULL, NULL,
1665                               g_cclosure_marshal_VOID__VOID,
1666                               G_TYPE_NONE,
1667                               0);
1668
1669         signals[MESSAGE_ACKNOWLEDGED] =
1670                 g_signal_new ("message-acknowledged",
1671                               G_TYPE_FROM_CLASS (klass),
1672                               G_SIGNAL_RUN_LAST,
1673                               0,
1674                               NULL, NULL,
1675                               g_cclosure_marshal_VOID__OBJECT,
1676                               G_TYPE_NONE,
1677                               1, EMPATHY_TYPE_MESSAGE);
1678
1679         g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
1680 }
1681
1682 static void
1683 empathy_tp_chat_init (EmpathyTpChat *chat)
1684 {
1685         EmpathyTpChatPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chat,
1686                 EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv);
1687
1688         chat->priv = priv;
1689         priv->messages_queue = g_queue_new ();
1690         priv->pending_messages_queue = g_queue_new ();
1691         priv->messages_being_sent = g_hash_table_new_full (
1692                 g_str_hash, g_str_equal, g_free, NULL);
1693 }
1694
1695 static void
1696 tp_chat_iface_init (EmpathyContactListIface *iface)
1697 {
1698         iface->add         = tp_chat_add;
1699         iface->remove      = tp_chat_remove;
1700         iface->get_members = tp_chat_get_members;
1701 }
1702
1703 EmpathyTpChat *
1704 empathy_tp_chat_new (TpAccount *account,
1705                      TpChannel *channel)
1706 {
1707         g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
1708         g_return_val_if_fail (TP_IS_TEXT_CHANNEL (channel), NULL);
1709
1710         return g_object_new (EMPATHY_TYPE_TP_CHAT,
1711                              "account", account,
1712                              "channel", channel,
1713                              NULL);
1714 }
1715
1716 const gchar *
1717 empathy_tp_chat_get_id (EmpathyTpChat *chat)
1718 {
1719         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1720         const gchar *id;
1721
1722
1723         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1724
1725         id = tp_channel_get_identifier (priv->channel);
1726         if (!EMP_STR_EMPTY (id))
1727                 return id;
1728         else if (priv->remote_contact)
1729                 return empathy_contact_get_id (priv->remote_contact);
1730         else
1731                 return NULL;
1732
1733 }
1734
1735 EmpathyContact *
1736 empathy_tp_chat_get_remote_contact (EmpathyTpChat *chat)
1737 {
1738         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1739
1740         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1741         g_return_val_if_fail (priv->ready, NULL);
1742
1743         return priv->remote_contact;
1744 }
1745
1746 TpChannel *
1747 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
1748 {
1749         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1750
1751         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1752
1753         return priv->channel;
1754 }
1755
1756 TpAccount *
1757 empathy_tp_chat_get_account (EmpathyTpChat *chat)
1758 {
1759         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1760
1761         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1762
1763         return priv->account;
1764 }
1765
1766 TpConnection *
1767 empathy_tp_chat_get_connection (EmpathyTpChat *chat)
1768 {
1769         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1770
1771         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1772
1773         return tp_channel_borrow_connection (priv->channel);
1774 }
1775 gboolean
1776 empathy_tp_chat_is_ready (EmpathyTpChat *chat)
1777 {
1778         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1779
1780         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
1781
1782         return priv->ready;
1783 }
1784
1785 void
1786 empathy_tp_chat_send (EmpathyTpChat *chat,
1787                       TpMessage *message)
1788 {
1789         EmpathyTpChatPriv        *priv = GET_PRIV (chat);
1790         gchar *message_body;
1791
1792         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1793         g_return_if_fail (TP_IS_CLIENT_MESSAGE (message));
1794         g_return_if_fail (priv->ready);
1795
1796         message_body = tp_message_to_text (message, NULL);
1797
1798         DEBUG ("Sending message: %s", message_body);
1799
1800         tp_text_channel_send_message_async (TP_TEXT_CHANNEL (priv->channel),
1801                 message, TP_MESSAGE_SENDING_FLAG_REPORT_DELIVERY,
1802                 message_send_cb, chat);
1803
1804         g_free (message_body);
1805 }
1806
1807 void
1808 empathy_tp_chat_set_state (EmpathyTpChat      *chat,
1809                            TpChannelChatState  state)
1810 {
1811         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1812
1813         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1814         g_return_if_fail (priv->ready);
1815
1816         if (tp_proxy_has_interface_by_id (priv->channel,
1817                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_CHAT_STATE)) {
1818                 DEBUG ("Set state: %d", state);
1819                 tp_cli_channel_interface_chat_state_call_set_chat_state (priv->channel, -1,
1820                                                                          state,
1821                                                                          tp_chat_async_cb,
1822                                                                          "setting chat state",
1823                                                                          NULL,
1824                                                                          G_OBJECT (chat));
1825         }
1826 }
1827
1828
1829 const GList *
1830 empathy_tp_chat_get_pending_messages (EmpathyTpChat *chat)
1831 {
1832         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1833
1834         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1835         g_return_val_if_fail (priv->ready, NULL);
1836
1837         return priv->pending_messages_queue->head;
1838 }
1839
1840 void
1841 empathy_tp_chat_acknowledge_message (EmpathyTpChat *chat,
1842                                      EmpathyMessage *message) {
1843         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1844         TpMessage *tp_msg;
1845
1846         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1847         g_return_if_fail (priv->ready);
1848
1849         if (!empathy_message_is_incoming (message))
1850                 return;
1851
1852         tp_msg = empathy_message_get_tp_message (message);
1853         tp_text_channel_ack_message_async (TP_TEXT_CHANNEL (priv->channel),
1854                                            tp_msg, NULL, NULL);
1855 }
1856
1857 void
1858 empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat,
1859                                       const GSList *messages) {
1860         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1861         const GSList *l;
1862         GList *messages_to_ack = NULL;
1863
1864         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1865         g_return_if_fail (priv->ready);
1866
1867         if (messages == NULL)
1868                 return;
1869
1870         for (l = messages; l != NULL; l = g_slist_next (l)) {
1871                 EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
1872
1873                 if (empathy_message_is_incoming (message)) {
1874                         TpMessage *tp_msg = empathy_message_get_tp_message (message);
1875                         messages_to_ack = g_list_append (messages_to_ack, tp_msg);
1876                 }
1877         }
1878
1879         if (messages_to_ack != NULL) {
1880                 tp_text_channel_ack_messages_async (TP_TEXT_CHANNEL (priv->channel),
1881                                                     messages_to_ack, NULL, NULL);
1882         }
1883
1884         g_list_free (messages_to_ack);
1885 }
1886
1887 void
1888 empathy_tp_chat_acknowledge_all_messages (EmpathyTpChat *chat)
1889 {
1890   empathy_tp_chat_acknowledge_messages (chat,
1891     (GSList *) empathy_tp_chat_get_pending_messages (chat));
1892 }
1893
1894 gboolean
1895 empathy_tp_chat_password_needed (EmpathyTpChat *self)
1896 {
1897         EmpathyTpChatPriv *priv = GET_PRIV (self);
1898
1899         return priv->password_flags & TP_CHANNEL_PASSWORD_FLAG_PROVIDE;
1900 }
1901
1902 static void
1903 provide_password_cb (TpChannel *channel,
1904                                       gboolean correct,
1905                                       const GError *error,
1906                                       gpointer user_data,
1907                                       GObject *weak_object)
1908 {
1909         GSimpleAsyncResult *result = user_data;
1910
1911         if (error != NULL) {
1912                 g_simple_async_result_set_from_error (result, error);
1913         }
1914         else if (!correct) {
1915                 /* The current D-Bus API is a bit weird so re-use the
1916                  * AuthenticationFailed error */
1917                 g_simple_async_result_set_error (result, TP_ERRORS,
1918                                                  TP_ERROR_AUTHENTICATION_FAILED, "Wrong password");
1919         }
1920
1921         g_simple_async_result_complete (result);
1922         g_object_unref (result);
1923 }
1924
1925 void
1926 empathy_tp_chat_provide_password_async (EmpathyTpChat *self,
1927                                                      const gchar *password,
1928                                                      GAsyncReadyCallback callback,
1929                                                      gpointer user_data)
1930 {
1931         EmpathyTpChatPriv *priv = GET_PRIV (self);
1932         GSimpleAsyncResult *result;
1933
1934         result = g_simple_async_result_new (G_OBJECT (self),
1935                                             callback, user_data,
1936                                             empathy_tp_chat_provide_password_finish);
1937
1938         tp_cli_channel_interface_password_call_provide_password
1939                 (priv->channel, -1, password, provide_password_cb, result,
1940                  NULL, G_OBJECT (self));
1941 }
1942
1943 gboolean
1944 empathy_tp_chat_provide_password_finish (EmpathyTpChat *self,
1945                                                       GAsyncResult *result,
1946                                                       GError **error)
1947 {
1948         if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
1949                 error))
1950                 return FALSE;
1951
1952         g_return_val_if_fail (g_simple_async_result_is_valid (result,
1953                                                               G_OBJECT (self), empathy_tp_chat_provide_password_finish), FALSE);
1954
1955         return TRUE;
1956 }
1957
1958 /**
1959  * empathy_tp_chat_can_add_contact:
1960  *
1961  * Returns: %TRUE if empathy_contact_list_add() will work for this channel.
1962  * That is if this chat is a 1-to-1 channel that can be upgraded to
1963  * a MUC using the Conference interface or if the channel is a MUC.
1964  */
1965 gboolean
1966 empathy_tp_chat_can_add_contact (EmpathyTpChat *self)
1967 {
1968         EmpathyTpChatPriv *priv;
1969
1970         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), FALSE);
1971
1972         priv = GET_PRIV (self);
1973
1974         return priv->can_upgrade_to_muc ||
1975                 tp_proxy_has_interface_by_id (priv->channel,
1976                         TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP);;
1977 }
1978
1979 static void
1980 tp_channel_leave_async_cb (GObject *source_object,
1981         GAsyncResult *res,
1982         gpointer user_data)
1983 {
1984         GError *error = NULL;
1985
1986         if (!tp_channel_leave_finish (TP_CHANNEL (source_object), res, &error)) {
1987                 DEBUG ("Could not leave channel properly: (%s); closing the channel",
1988                         error->message);
1989                 g_error_free (error);
1990         }
1991 }
1992
1993 void
1994 empathy_tp_chat_leave (EmpathyTpChat *self,
1995                 const gchar *message)
1996 {
1997         EmpathyTpChatPriv *priv = GET_PRIV (self);
1998
1999         DEBUG ("Leaving channel %s with message \"%s\"",
2000                 tp_channel_get_identifier (priv->channel), message);
2001
2002         tp_channel_leave_async (priv->channel, TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
2003                 message, tp_channel_leave_async_cb, self);
2004 }
2005
2006 static void
2007 add_members_cb (TpChannel *proxy,
2008                 const GError *error,
2009                 gpointer user_data,
2010                 GObject *weak_object)
2011 {
2012         EmpathyTpChatPriv *priv = GET_PRIV (weak_object);
2013
2014         if (error != NULL) {
2015                 DEBUG ("Failed to join chat (%s): %s",
2016                         tp_channel_get_identifier (priv->channel), error->message);
2017         }
2018 }
2019
2020 void
2021 empathy_tp_chat_join (EmpathyTpChat *self)
2022 {
2023         EmpathyTpChatPriv *priv = GET_PRIV (self);
2024         TpHandle self_handle;
2025         GArray *members;
2026
2027         self_handle = tp_channel_group_get_self_handle (priv->channel);
2028
2029         members = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
2030         g_array_append_val (members, self_handle);
2031
2032         tp_cli_channel_interface_group_call_add_members (priv->channel, -1, members,
2033                 "", add_members_cb, NULL, NULL, G_OBJECT (self));
2034
2035         g_array_free (members, TRUE);
2036 }
2037
2038 gboolean
2039 empathy_tp_chat_is_invited (EmpathyTpChat *self,
2040                             TpHandle *inviter)
2041 {
2042         EmpathyTpChatPriv *priv = GET_PRIV (self);
2043         TpHandle self_handle;
2044
2045         if (!tp_proxy_has_interface (priv->channel, TP_IFACE_CHANNEL_INTERFACE_GROUP))
2046                 return FALSE;
2047
2048         self_handle = tp_channel_group_get_self_handle (priv->channel);
2049         if (self_handle == 0)
2050                 return FALSE;
2051
2052         return tp_channel_group_get_local_pending_info (priv->channel, self_handle,
2053                 inviter, NULL, NULL);
2054 }
2055
2056 TpChannelChatState
2057 empathy_tp_chat_get_chat_state (EmpathyTpChat *chat,
2058                             EmpathyContact *contact)
2059 {
2060         EmpathyTpChatPriv *priv = GET_PRIV (chat);
2061         return tp_channel_get_chat_state (priv->channel,
2062                 empathy_contact_get_handle (contact));
2063 }
2064
2065 EmpathyContact *
2066 empathy_tp_chat_get_self_contact (EmpathyTpChat *self)
2067 {
2068         EmpathyTpChatPriv *priv = GET_PRIV (self);
2069
2070         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), NULL);
2071
2072         return priv->user;
2073 }
2074
2075 gboolean
2076 empathy_tp_chat_is_sms_channel (EmpathyTpChat *self)
2077 {
2078         EmpathyTpChatPriv *priv;
2079
2080         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), FALSE);
2081
2082         priv = GET_PRIV (self);
2083
2084         return priv->sms_channel;
2085 }