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