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