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