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