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