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