]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-chat.c
bba6733a6eafc0874296d905a0e4bcdbc3ac053d
[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-monitor.h"
33 #include "empathy-contact-list.h"
34 #include "empathy-dispatcher.h"
35 #include "empathy-marshal.h"
36 #include "empathy-time.h"
37 #include "empathy-utils.h"
38
39 #define DEBUG_FLAG EMPATHY_DEBUG_TP | EMPATHY_DEBUG_CHAT
40 #include "empathy-debug.h"
41
42 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpChat)
43 typedef struct {
44         gboolean               dispose_has_run;
45         EmpathyTpContactFactory *factory;
46         EmpathyContactMonitor *contact_monitor;
47         EmpathyContact        *user;
48         EmpathyContact        *remote_contact;
49         GList                 *members;
50         TpChannel             *channel;
51         gboolean               listing_pending_messages;
52         /* Queue of messages not signalled yet */
53         GQueue                *messages_queue;
54         /* Queue of messages signalled but not acked yet */
55         GQueue                *pending_messages_queue;
56         gboolean               had_properties_list;
57         GPtrArray             *properties;
58         TpChannelPasswordFlags password_flags;
59         /* TRUE if we fetched the password flag of the channel or if it's not needed
60          * (channel doesn't implement the Password interface) */
61         gboolean               got_password_flags;
62         gboolean               ready;
63         gboolean               can_upgrade_to_muc;
64 } EmpathyTpChatPriv;
65
66 static void tp_chat_iface_init         (EmpathyContactListIface *iface);
67
68 enum {
69         PROP_0,
70         PROP_CHANNEL,
71         PROP_REMOTE_CONTACT,
72         PROP_PASSWORD_NEEDED,
73         PROP_READY,
74 };
75
76 enum {
77         MESSAGE_RECEIVED,
78         SEND_ERROR,
79         CHAT_STATE_CHANGED,
80         PROPERTY_CHANGED,
81         DESTROY,
82         LAST_SIGNAL
83 };
84
85 static guint signals[LAST_SIGNAL];
86
87 G_DEFINE_TYPE_WITH_CODE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT,
88                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST,
89                                                 tp_chat_iface_init));
90
91 static void acknowledge_messages (EmpathyTpChat *chat, GArray *ids);
92
93 static void
94 tp_chat_invalidated_cb (TpProxy       *proxy,
95                         guint          domain,
96                         gint           code,
97                         gchar         *message,
98                         EmpathyTpChat *chat)
99 {
100         DEBUG ("Channel invalidated: %s", message);
101         g_signal_emit (chat, signals[DESTROY], 0);
102 }
103
104 static void
105 tp_chat_async_cb (TpChannel *proxy,
106                   const GError *error,
107                   gpointer user_data,
108                   GObject *weak_object)
109 {
110         if (error) {
111                 DEBUG ("Error %s: %s", (gchar *) user_data, error->message);
112         }
113 }
114
115 static void
116 tp_chat_add (EmpathyContactList *list,
117              EmpathyContact     *contact,
118              const gchar        *message)
119 {
120         EmpathyTpChatPriv *priv = GET_PRIV (list);
121
122         if (tp_proxy_has_interface_by_id (priv->channel,
123                 TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
124                 TpHandle           handle;
125                 GArray             handles = {(gchar *) &handle, 1};
126
127                 g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
128                 g_return_if_fail (EMPATHY_IS_CONTACT (contact));
129
130                 handle = empathy_contact_get_handle (contact);
131                 tp_cli_channel_interface_group_call_add_members (priv->channel,
132                         -1, &handles, NULL, NULL, NULL, NULL, NULL);
133         } else if (priv->can_upgrade_to_muc) {
134                 EmpathyDispatcher *dispatcher;
135                 TpConnection      *connection;
136                 GHashTable        *props;
137                 const char        *object_path;
138                 GPtrArray          channels = { (gpointer *) &object_path, 1 };
139                 const char        *invitees[2] = { NULL, };
140
141                 dispatcher = empathy_dispatcher_dup_singleton ();
142                 connection = tp_channel_borrow_connection (priv->channel);
143
144                 invitees[0] = empathy_contact_get_id (contact);
145                 object_path = tp_proxy_get_object_path (priv->channel);
146
147                 props = tp_asv_new (
148                     TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING,
149                         TP_IFACE_CHANNEL_TYPE_TEXT,
150                     TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_UINT,
151                         TP_HANDLE_TYPE_NONE,
152                     EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialChannels",
153                         TP_ARRAY_TYPE_OBJECT_PATH_LIST, &channels,
154                     EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialInviteeIDs",
155                         G_TYPE_STRV, invitees,
156                     /* FIXME: InvitationMessage ? */
157                     NULL);
158
159                 /* Although this is a MUC, it's anonymous, so CreateChannel is
160                  * valid.
161                  * props now belongs to EmpathyDispatcher, don't free it */
162                 empathy_dispatcher_create_channel (dispatcher, connection,
163                                 props, NULL, NULL);
164
165                 g_object_unref (dispatcher);
166         } else {
167                 g_warning ("Cannot add to this channel");
168         }
169 }
170
171 static void
172 tp_chat_remove (EmpathyContactList *list,
173                 EmpathyContact     *contact,
174                 const gchar        *message)
175 {
176         EmpathyTpChatPriv *priv = GET_PRIV (list);
177         TpHandle           handle;
178         GArray             handles = {(gchar *) &handle, 1};
179
180         g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
181         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
182
183         handle = empathy_contact_get_handle (contact);
184         tp_cli_channel_interface_group_call_remove_members (priv->channel, -1,
185                                                             &handles, NULL,
186                                                             NULL, NULL, NULL,
187                                                             NULL);
188 }
189
190 static GList *
191 tp_chat_get_members (EmpathyContactList *list)
192 {
193         EmpathyTpChatPriv *priv = GET_PRIV (list);
194         GList             *members = NULL;
195
196         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL);
197
198         if (priv->members) {
199                 members = g_list_copy (priv->members);
200                 g_list_foreach (members, (GFunc) g_object_ref, NULL);
201         } else {
202                 members = g_list_prepend (members, g_object_ref (priv->user));
203                 if (priv->remote_contact != NULL)
204                         members = g_list_prepend (members, g_object_ref (priv->remote_contact));
205         }
206
207         return members;
208 }
209
210 static EmpathyContactMonitor *
211 tp_chat_get_monitor (EmpathyContactList *list)
212 {
213         EmpathyTpChatPriv *priv;
214
215         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL);
216
217         priv = GET_PRIV (list);
218
219         if (priv->contact_monitor == NULL) {
220                 priv->contact_monitor = empathy_contact_monitor_new_for_iface (list);
221         }
222
223         return priv->contact_monitor;
224 }
225
226 static void
227 tp_chat_emit_queued_messages (EmpathyTpChat *chat)
228 {
229         EmpathyTpChatPriv *priv = GET_PRIV (chat);
230         EmpathyMessage    *message;
231
232         /* Check if we can now emit some queued messages */
233         while ((message = g_queue_peek_head (priv->messages_queue)) != NULL) {
234                 if (empathy_message_get_sender (message) == NULL) {
235                         break;
236                 }
237
238                 DEBUG ("Queued message ready");
239                 g_queue_pop_head (priv->messages_queue);
240                 g_queue_push_tail (priv->pending_messages_queue, message);
241                 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
242         }
243 }
244
245 static void
246 tp_chat_got_sender_cb (EmpathyTpContactFactory *factory,
247                        EmpathyContact          *contact,
248                        const GError            *error,
249                        gpointer                 message,
250                        GObject                 *chat)
251 {
252         EmpathyTpChatPriv *priv = GET_PRIV (chat);
253
254         if (error) {
255                 DEBUG ("Error: %s", error->message);
256                 /* Do not block the message queue, just drop this message */
257                 g_queue_remove (priv->messages_queue, message);
258         } else {
259                 empathy_message_set_sender (message, contact);
260         }
261
262         tp_chat_emit_queued_messages (EMPATHY_TP_CHAT (chat));
263 }
264
265 static void
266 tp_chat_build_message (EmpathyTpChat *chat,
267                        gboolean       incoming,
268                        guint          id,
269                        guint          type,
270                        guint          timestamp,
271                        guint          from_handle,
272                        const gchar   *message_body,
273                        TpChannelTextMessageFlags flags)
274 {
275         EmpathyTpChatPriv *priv;
276         EmpathyMessage    *message;
277
278         priv = GET_PRIV (chat);
279
280         message = empathy_message_new (message_body);
281         empathy_message_set_tptype (message, type);
282         empathy_message_set_receiver (message, priv->user);
283         empathy_message_set_timestamp (message, timestamp);
284         empathy_message_set_id (message, id);
285         empathy_message_set_incoming (message, incoming);
286         empathy_message_set_flags (message, flags);
287
288         g_queue_push_tail (priv->messages_queue, message);
289
290         if (from_handle == 0) {
291                 empathy_message_set_sender (message, priv->user);
292                 tp_chat_emit_queued_messages (chat);
293         } else {
294                 empathy_tp_contact_factory_get_from_handle (priv->factory,
295                         from_handle,
296                         tp_chat_got_sender_cb,
297                         message, NULL, G_OBJECT (chat));
298         }
299 }
300
301 static void
302 tp_chat_received_cb (TpChannel   *channel,
303                      guint        message_id,
304                      guint        timestamp,
305                      guint        from_handle,
306                      guint        message_type,
307                      guint        message_flags,
308                      const gchar *message_body,
309                      gpointer     user_data,
310                      GObject     *chat_)
311 {
312         EmpathyTpChat *chat = EMPATHY_TP_CHAT (chat_);
313         EmpathyTpChatPriv *priv = GET_PRIV (chat);
314
315         if (priv->channel == NULL)
316                 return;
317
318         if (priv->listing_pending_messages) {
319                 return;
320         }
321
322         DEBUG ("Message received: %s", message_body);
323
324         if (message_flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT &&
325             !tp_strdiff (message_body, "")) {
326                 GArray *ids;
327
328                 DEBUG ("Empty message with NonTextContent, ignoring and acking.");
329
330                 ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
331                 g_array_append_val (ids, message_id);
332                 acknowledge_messages (chat, ids);
333                 g_array_free (ids, TRUE);
334
335                 return;
336         }
337
338         tp_chat_build_message (chat,
339                                TRUE,
340                                message_id,
341                                message_type,
342                                timestamp,
343                                from_handle,
344                                message_body,
345                                message_flags);
346 }
347
348 static void
349 tp_chat_sent_cb (TpChannel   *channel,
350                  guint        timestamp,
351                  guint        message_type,
352                  const gchar *message_body,
353                  gpointer     user_data,
354                  GObject     *chat_)
355 {
356         EmpathyTpChat *chat = EMPATHY_TP_CHAT (chat_);
357         EmpathyTpChatPriv *priv = GET_PRIV (chat);
358
359         if (priv->channel == NULL)
360                 return;
361
362         DEBUG ("Message sent: %s", message_body);
363
364         tp_chat_build_message (chat,
365                                FALSE,
366                                0,
367                                message_type,
368                                timestamp,
369                                0,
370                                message_body,
371                                0);
372 }
373
374 static void
375 tp_chat_send_error_cb (TpChannel   *channel,
376                        guint        error_code,
377                        guint        timestamp,
378                        guint        message_type,
379                        const gchar *message_body,
380                        gpointer     user_data,
381                        GObject     *chat)
382 {
383         EmpathyTpChatPriv *priv = GET_PRIV (chat);
384
385         if (priv->channel == NULL)
386                 return;
387
388         DEBUG ("Error sending '%s' (%d)", message_body, error_code);
389
390         g_signal_emit (chat, signals[SEND_ERROR], 0, message_body, error_code);
391 }
392
393 static void
394 tp_chat_send_cb (TpChannel    *proxy,
395                  const GError *error,
396                  gpointer      user_data,
397                  GObject      *chat)
398 {
399         EmpathyMessage *message = EMPATHY_MESSAGE (user_data);
400
401         if (error) {
402                 DEBUG ("Error: %s", error->message);
403                 g_signal_emit (chat, signals[SEND_ERROR], 0,
404                                empathy_message_get_body (message),
405                                TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN);
406         }
407 }
408
409 typedef struct {
410         EmpathyTpChat *chat;
411         TpChannelChatState state;
412 } StateChangedData;
413
414 static void
415 tp_chat_state_changed_got_contact_cb (EmpathyTpContactFactory *factory,
416                                       EmpathyContact          *contact,
417                                       const GError            *error,
418                                       gpointer                 user_data,
419                                       GObject                 *chat)
420 {
421         TpChannelChatState state;
422
423         if (error) {
424                 DEBUG ("Error: %s", error->message);
425                 return;
426         }
427
428         state = GPOINTER_TO_UINT (user_data);
429         DEBUG ("Chat state changed for %s (%d): %d",
430                 empathy_contact_get_name (contact),
431                 empathy_contact_get_handle (contact), state);
432
433         g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
434 }
435
436 static void
437 tp_chat_state_changed_cb (TpChannel *channel,
438                           TpHandle   handle,
439                           TpChannelChatState state,
440                           gpointer   user_data,
441                           GObject   *chat)
442 {
443         EmpathyTpChatPriv *priv = GET_PRIV (chat);
444
445         empathy_tp_contact_factory_get_from_handle (priv->factory, handle,
446                 tp_chat_state_changed_got_contact_cb, GUINT_TO_POINTER (state),
447                 NULL, chat);
448 }
449
450 static void
451 tp_chat_list_pending_messages_cb (TpChannel       *channel,
452                                   const GPtrArray *messages_list,
453                                   const GError    *error,
454                                   gpointer         user_data,
455                                   GObject         *chat_)
456 {
457         EmpathyTpChat     *chat = EMPATHY_TP_CHAT (chat_);
458         EmpathyTpChatPriv *priv = GET_PRIV (chat);
459         guint              i;
460         GArray            *empty_non_text_content_ids = NULL;
461
462         priv->listing_pending_messages = FALSE;
463
464         if (priv->channel == NULL)
465                 return;
466
467         if (error) {
468                 DEBUG ("Error listing pending messages: %s", error->message);
469                 return;
470         }
471
472         for (i = 0; i < messages_list->len; i++) {
473                 GValueArray    *message_struct;
474                 const gchar    *message_body;
475                 guint           message_id;
476                 guint           timestamp;
477                 guint           from_handle;
478                 guint           message_type;
479                 guint           message_flags;
480
481                 message_struct = g_ptr_array_index (messages_list, i);
482
483                 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
484                 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
485                 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
486                 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
487                 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
488                 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
489
490                 DEBUG ("Message pending: %s", message_body);
491
492                 if (message_flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT &&
493                     !tp_strdiff (message_body, "")) {
494                         DEBUG ("Empty message with NonTextContent, ignoring and acking.");
495
496                         if (empty_non_text_content_ids == NULL) {
497                                 empty_non_text_content_ids = g_array_new (FALSE, FALSE, sizeof (guint));
498                         }
499
500                         g_array_append_val (empty_non_text_content_ids, message_id);
501                         continue;
502                 }
503
504                 tp_chat_build_message (chat,
505                                        TRUE,
506                                        message_id,
507                                        message_type,
508                                        timestamp,
509                                        from_handle,
510                                        message_body,
511                                        message_flags);
512         }
513
514         if (empty_non_text_content_ids != NULL) {
515                 acknowledge_messages (chat, empty_non_text_content_ids);
516                 g_array_free (empty_non_text_content_ids, TRUE);
517         }
518 }
519
520 static void
521 tp_chat_property_flags_changed_cb (TpProxy         *proxy,
522                                    const GPtrArray *properties,
523                                    gpointer         user_data,
524                                    GObject         *chat)
525 {
526         EmpathyTpChatPriv *priv = GET_PRIV (chat);
527         guint              i, j;
528
529         if (priv->channel == NULL)
530                 return;
531
532         if (!priv->had_properties_list || !properties) {
533                 return;
534         }
535
536         for (i = 0; i < properties->len; i++) {
537                 GValueArray           *prop_struct;
538                 EmpathyTpChatProperty *property;
539                 guint                  id;
540                 guint                  flags;
541
542                 prop_struct = g_ptr_array_index (properties, i);
543                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
544                 flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 1));
545
546                 for (j = 0; j < priv->properties->len; j++) {
547                         property = g_ptr_array_index (priv->properties, j);
548                         if (property->id == id) {
549                                 property->flags = flags;
550                                 DEBUG ("property %s flags changed: %d",
551                                         property->name, property->flags);
552                                 break;
553                         }
554                 }
555         }
556 }
557
558 static void
559 tp_chat_properties_changed_cb (TpProxy         *proxy,
560                                const GPtrArray *properties,
561                                gpointer         user_data,
562                                GObject         *chat)
563 {
564         EmpathyTpChatPriv *priv = GET_PRIV (chat);
565         guint              i, j;
566
567         if (priv->channel == NULL)
568                 return;
569
570         if (!priv->had_properties_list || !properties) {
571                 return;
572         }
573
574         for (i = 0; i < properties->len; i++) {
575                 GValueArray           *prop_struct;
576                 EmpathyTpChatProperty *property;
577                 guint                  id;
578                 GValue                *src_value;
579
580                 prop_struct = g_ptr_array_index (properties, i);
581                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
582                 src_value = g_value_get_boxed (g_value_array_get_nth (prop_struct, 1));
583
584                 for (j = 0; j < priv->properties->len; j++) {
585                         property = g_ptr_array_index (priv->properties, j);
586                         if (property->id == id) {
587                                 if (property->value) {
588                                         g_value_copy (src_value, property->value);
589                                 } else {
590                                         property->value = tp_g_value_slice_dup (src_value);
591                                 }
592
593                                 DEBUG ("property %s changed", property->name);
594                                 g_signal_emit (chat, signals[PROPERTY_CHANGED], 0,
595                                                property->name, property->value);
596                                 break;
597                         }
598                 }
599         }
600 }
601
602 static void
603 tp_chat_get_properties_cb (TpProxy         *proxy,
604                            const GPtrArray *properties,
605                            const GError    *error,
606                            gpointer         user_data,
607                            GObject         *chat)
608 {
609         if (error) {
610                 DEBUG ("Error getting properties: %s", error->message);
611                 return;
612         }
613
614         tp_chat_properties_changed_cb (proxy, properties, user_data, chat);
615 }
616
617 static void
618 tp_chat_list_properties_cb (TpProxy         *proxy,
619                             const GPtrArray *properties,
620                             const GError    *error,
621                             gpointer         user_data,
622                             GObject         *chat)
623 {
624         EmpathyTpChatPriv *priv = GET_PRIV (chat);
625         GArray            *ids;
626         guint              i;
627
628         if (priv->channel == NULL)
629                 return;
630
631         priv->had_properties_list = TRUE;
632
633         if (error) {
634                 DEBUG ("Error listing properties: %s", error->message);
635                 return;
636         }
637
638         ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), properties->len);
639         priv->properties = g_ptr_array_sized_new (properties->len);
640         for (i = 0; i < properties->len; i++) {
641                 GValueArray           *prop_struct;
642                 EmpathyTpChatProperty *property;
643
644                 prop_struct = g_ptr_array_index (properties, i);
645                 property = g_slice_new0 (EmpathyTpChatProperty);
646                 property->id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
647                 property->name = g_value_dup_string (g_value_array_get_nth (prop_struct, 1));
648                 property->flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 3));
649
650                 DEBUG ("Adding property name=%s id=%d flags=%d",
651                         property->name, property->id, property->flags);
652                 g_ptr_array_add (priv->properties, property);
653                 if (property->flags & TP_PROPERTY_FLAG_READ) {
654                         g_array_append_val (ids, property->id);
655                 }
656         }
657
658         tp_cli_properties_interface_call_get_properties (proxy, -1,
659                                                          ids,
660                                                          tp_chat_get_properties_cb,
661                                                          NULL, NULL,
662                                                          chat);
663
664         g_array_free (ids, TRUE);
665 }
666
667 void
668 empathy_tp_chat_set_property (EmpathyTpChat *chat,
669                               const gchar   *name,
670                               const GValue  *value)
671 {
672         EmpathyTpChatPriv     *priv = GET_PRIV (chat);
673         EmpathyTpChatProperty *property;
674         guint                  i;
675
676         if (!priv->had_properties_list) {
677                 return;
678         }
679
680         for (i = 0; i < priv->properties->len; i++) {
681                 property = g_ptr_array_index (priv->properties, i);
682                 if (!tp_strdiff (property->name, name)) {
683                         GPtrArray   *properties;
684                         GValueArray *prop;
685                         GValue       id = {0, };
686                         GValue       dest_value = {0, };
687
688                         if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) {
689                                 break;
690                         }
691
692                         g_value_init (&id, G_TYPE_UINT);
693                         g_value_init (&dest_value, G_TYPE_VALUE);
694                         g_value_set_uint (&id, property->id);
695                         g_value_set_boxed (&dest_value, value);
696
697                         prop = g_value_array_new (2);
698                         g_value_array_append (prop, &id);
699                         g_value_array_append (prop, &dest_value);
700
701                         properties = g_ptr_array_sized_new (1);
702                         g_ptr_array_add (properties, prop);
703
704                         DEBUG ("Set property %s", name);
705                         tp_cli_properties_interface_call_set_properties (priv->channel, -1,
706                                                                          properties,
707                                                                          (tp_cli_properties_interface_callback_for_set_properties)
708                                                                          tp_chat_async_cb,
709                                                                          "Seting property", NULL,
710                                                                          G_OBJECT (chat));
711
712                         g_ptr_array_free (properties, TRUE);
713                         g_value_array_free (prop);
714
715                         break;
716                 }
717         }
718 }
719
720 EmpathyTpChatProperty *
721 empathy_tp_chat_get_property (EmpathyTpChat *chat,
722                               const gchar   *name)
723 {
724         EmpathyTpChatPriv     *priv = GET_PRIV (chat);
725         EmpathyTpChatProperty *property;
726         guint                  i;
727
728         if (!priv->had_properties_list) {
729                 return NULL;
730         }
731
732         for (i = 0; i < priv->properties->len; i++) {
733                 property = g_ptr_array_index (priv->properties, i);
734                 if (!tp_strdiff (property->name, name)) {
735                         return property;
736                 }
737         }
738
739         return NULL;
740 }
741
742 GPtrArray *
743 empathy_tp_chat_get_properties (EmpathyTpChat *chat)
744 {
745         EmpathyTpChatPriv *priv = GET_PRIV (chat);
746
747         return priv->properties;
748 }
749
750 static void
751 tp_chat_dispose (GObject *object)
752 {
753         EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
754         EmpathyTpChatPriv *priv = GET_PRIV (self);
755
756         if (priv->dispose_has_run)
757                 return;
758
759         priv->dispose_has_run = TRUE;
760
761         if (priv->channel != NULL) {
762                 g_signal_handlers_disconnect_by_func (priv->channel,
763                         tp_chat_invalidated_cb, self);
764                 g_object_unref (priv->channel);
765         }
766         priv->channel = NULL;
767
768         if (priv->remote_contact != NULL)
769                 g_object_unref (priv->remote_contact);
770         priv->remote_contact = NULL;
771
772         if (priv->factory != NULL)
773                 g_object_unref (priv->factory);
774         priv->factory = NULL;
775
776         if (priv->user != NULL)
777                 g_object_unref (priv->user);
778         priv->user = NULL;
779
780         if (priv->contact_monitor)
781                 g_object_unref (priv->contact_monitor);
782         priv->contact_monitor = 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 tp_chat_check_if_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         /* We need either the members (room) or the remote contact (private chat).
838          * If the chat is protected by a password we can't get these information so
839          * consider the chat as ready so it can be presented to the user. */
840         if (!empathy_tp_chat_password_needed (chat) && priv->members == NULL &&
841             priv->remote_contact == NULL)
842                 return;
843
844         DEBUG ("Ready!");
845
846         tp_cli_channel_type_text_connect_to_received (priv->channel,
847                                                       tp_chat_received_cb,
848                                                       NULL, NULL,
849                                                       G_OBJECT (chat), NULL);
850         priv->listing_pending_messages = TRUE;
851         tp_cli_channel_type_text_call_list_pending_messages (priv->channel, -1,
852                                                              FALSE,
853                                                              tp_chat_list_pending_messages_cb,
854                                                              NULL, NULL,
855                                                              G_OBJECT (chat));
856
857         tp_cli_channel_type_text_connect_to_sent (priv->channel,
858                                                   tp_chat_sent_cb,
859                                                   NULL, NULL,
860                                                   G_OBJECT (chat), NULL);
861         tp_cli_channel_type_text_connect_to_send_error (priv->channel,
862                                                         tp_chat_send_error_cb,
863                                                         NULL, NULL,
864                                                         G_OBJECT (chat), NULL);
865         tp_cli_channel_interface_chat_state_connect_to_chat_state_changed (priv->channel,
866                                                                            tp_chat_state_changed_cb,
867                                                                            NULL, NULL,
868                                                                            G_OBJECT (chat), NULL);
869         priv->ready = TRUE;
870         g_object_notify (G_OBJECT (chat), "ready");
871 }
872
873 static void
874 tp_chat_update_remote_contact (EmpathyTpChat *chat)
875 {
876         EmpathyTpChatPriv *priv = GET_PRIV (chat);
877         EmpathyContact *contact = NULL;
878         TpHandle self_handle;
879         TpHandleType handle_type;
880         GList *l;
881
882         /* If this is a named chatroom, never pretend it is a private chat */
883         tp_channel_get_handle (priv->channel, &handle_type);
884         if (handle_type == TP_HANDLE_TYPE_ROOM) {
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 (EmpathyTpContactFactory *factory,
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         tp_chat_check_if_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 (EmpathyTpContactFactory *factory,
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         tp_chat_update_remote_contact (EMPATHY_TP_CHAT (chat));
1065         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
1066 }
1067
1068
1069 static void
1070 tp_chat_group_members_changed_cb (TpChannel     *self,
1071                                   gchar         *message,
1072                                   GArray        *added,
1073                                   GArray        *removed,
1074                                   GArray        *local_pending,
1075                                   GArray        *remote_pending,
1076                                   guint          actor,
1077                                   guint          reason,
1078                                   EmpathyTpChat *chat)
1079 {
1080         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1081         EmpathyContact *contact;
1082         EmpathyContact *actor_contact = NULL;
1083         guint i;
1084         ContactRenameData *rename_data;
1085         TpHandle old_handle;
1086
1087         /* Contact renamed */
1088         if (reason == TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED) {
1089                 /* there can only be a single 'added' and a single 'removed' handle */
1090                 g_warn_if_fail (removed->len == 1);
1091                 g_warn_if_fail (added->len == 1);
1092
1093                 old_handle = g_array_index (removed, guint, 0);
1094
1095                 rename_data = contact_rename_data_new (old_handle, reason, message);
1096                 empathy_tp_contact_factory_get_from_handles (priv->factory,
1097                         added->len, (TpHandle *) added->data,
1098                         tp_chat_got_renamed_contacts_cb,
1099                         rename_data, (GDestroyNotify) contact_rename_data_free,
1100                         G_OBJECT (chat));
1101                 return;
1102         }
1103
1104         if (actor != 0) {
1105                 actor_contact = chat_lookup_contact (chat, actor, FALSE);
1106                 if (actor_contact == NULL) {
1107                         /* FIXME: handle this a tad more gracefully: perhaps
1108                          * the actor was a server op. We could use the
1109                          * contact-ids detail of MembersChangedDetailed.
1110                          */
1111                         DEBUG ("actor %u not a channel member", actor);
1112                 }
1113         }
1114
1115         /* Remove contacts that are not members anymore */
1116         for (i = 0; i < removed->len; i++) {
1117                 contact = chat_lookup_contact (chat,
1118                         g_array_index (removed, TpHandle, i), TRUE);
1119
1120                 if (contact != NULL) {
1121                         g_signal_emit_by_name (chat, "members-changed", contact,
1122                                                actor_contact, reason, message,
1123                                                FALSE);
1124                         g_object_unref (contact);
1125                 }
1126         }
1127
1128         /* Request added contacts */
1129         if (added->len > 0) {
1130                 empathy_tp_contact_factory_get_from_handles (priv->factory,
1131                         added->len, (TpHandle *) added->data,
1132                         tp_chat_got_added_contacts_cb, NULL, NULL,
1133                         G_OBJECT (chat));
1134         }
1135
1136         tp_chat_update_remote_contact (chat);
1137
1138         if (actor_contact != NULL) {
1139                 g_object_unref (actor_contact);
1140         }
1141 }
1142
1143 static void
1144 tp_chat_got_remote_contact_cb (EmpathyTpContactFactory *factory,
1145                                EmpathyContact          *contact,
1146                                const GError            *error,
1147                                gpointer                 user_data,
1148                                GObject                 *chat)
1149 {
1150         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1151
1152         if (error) {
1153                 DEBUG ("Error: %s", error->message);
1154                 empathy_tp_chat_leave (EMPATHY_TP_CHAT (chat));
1155                 return;
1156         }
1157
1158         priv->remote_contact = g_object_ref (contact);
1159         g_object_notify (chat, "remote-contact");
1160
1161         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
1162 }
1163
1164 static void
1165 tp_chat_got_self_contact_cb (EmpathyTpContactFactory *factory,
1166                              EmpathyContact          *contact,
1167                              const GError            *error,
1168                              gpointer                 user_data,
1169                              GObject                 *chat)
1170 {
1171         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1172
1173         if (error) {
1174                 DEBUG ("Error: %s", error->message);
1175                 empathy_tp_chat_leave (EMPATHY_TP_CHAT (chat));
1176                 return;
1177         }
1178
1179         priv->user = g_object_ref (contact);
1180         empathy_contact_set_is_user (priv->user, TRUE);
1181         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
1182 }
1183
1184 static void
1185 password_flags_changed_cb (TpChannel *channel,
1186     guint added,
1187     guint removed,
1188     gpointer user_data,
1189     GObject *weak_object)
1190 {
1191         EmpathyTpChat *self = EMPATHY_TP_CHAT (weak_object);
1192         EmpathyTpChatPriv *priv = GET_PRIV (self);
1193         gboolean was_needed, needed;
1194
1195         was_needed = empathy_tp_chat_password_needed (self);
1196
1197         priv->password_flags |= added;
1198         priv->password_flags ^= removed;
1199
1200         needed = empathy_tp_chat_password_needed (self);
1201
1202         if (was_needed != needed)
1203                 g_object_notify (G_OBJECT (self), "password-needed");
1204 }
1205
1206 static void
1207 got_password_flags_cb (TpChannel *proxy,
1208                              guint password_flags,
1209                              const GError *error,
1210                              gpointer user_data,
1211                              GObject *weak_object)
1212 {
1213         EmpathyTpChat *self = EMPATHY_TP_CHAT (weak_object);
1214         EmpathyTpChatPriv *priv = GET_PRIV (self);
1215
1216         priv->got_password_flags = TRUE;
1217         priv->password_flags = password_flags;
1218
1219         tp_chat_check_if_ready (EMPATHY_TP_CHAT (self));
1220 }
1221
1222 static GObject *
1223 tp_chat_constructor (GType                  type,
1224                      guint                  n_props,
1225                      GObjectConstructParam *props)
1226 {
1227         GObject           *chat;
1228         EmpathyTpChatPriv *priv;
1229         TpConnection      *connection;
1230         TpHandle           handle;
1231
1232         chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
1233
1234         priv = GET_PRIV (chat);
1235
1236         connection = tp_channel_borrow_connection (priv->channel);
1237         priv->factory = empathy_tp_contact_factory_dup_singleton (connection);
1238         g_signal_connect (priv->channel, "invalidated",
1239                           G_CALLBACK (tp_chat_invalidated_cb),
1240                           chat);
1241
1242         if (tp_proxy_has_interface_by_id (priv->channel,
1243                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
1244                 const TpIntSet *members;
1245                 GArray *handles;
1246
1247                 /* Get self contact from the group's self handle */
1248                 handle = tp_channel_group_get_self_handle (priv->channel);
1249                 empathy_tp_contact_factory_get_from_handle (priv->factory,
1250                         handle, tp_chat_got_self_contact_cb,
1251                         NULL, NULL, chat);
1252
1253                 /* Get initial member contacts */
1254                 members = tp_channel_group_get_members (priv->channel);
1255                 handles = tp_intset_to_array (members);
1256                 empathy_tp_contact_factory_get_from_handles (priv->factory,
1257                         handles->len, (TpHandle *) handles->data,
1258                         tp_chat_got_added_contacts_cb, NULL, NULL, chat);
1259
1260                 priv->can_upgrade_to_muc = FALSE;
1261
1262                 g_signal_connect (priv->channel, "group-members-changed",
1263                         G_CALLBACK (tp_chat_group_members_changed_cb), chat);
1264         } else {
1265                 EmpathyDispatcher *dispatcher = empathy_dispatcher_dup_singleton ();
1266                 GList *list, *ptr;
1267
1268                 /* Get the self contact from the connection's self handle */
1269                 handle = tp_connection_get_self_handle (connection);
1270                 empathy_tp_contact_factory_get_from_handle (priv->factory,
1271                         handle, tp_chat_got_self_contact_cb,
1272                         NULL, NULL, chat);
1273
1274                 /* Get the remote contact */
1275                 handle = tp_channel_get_handle (priv->channel, NULL);
1276                 empathy_tp_contact_factory_get_from_handle (priv->factory,
1277                         handle, tp_chat_got_remote_contact_cb,
1278                         NULL, NULL, chat);
1279
1280                 list = empathy_dispatcher_find_requestable_channel_classes (
1281                         dispatcher, connection,
1282                         tp_channel_get_channel_type (priv->channel),
1283                         TP_UNKNOWN_HANDLE_TYPE, NULL);
1284
1285                 for (ptr = list; ptr; ptr = ptr->next) {
1286                         GValueArray *array = ptr->data;
1287                         const char **oprops = g_value_get_boxed (
1288                                 g_value_array_get_nth (array, 1));
1289
1290                         if (tp_strv_contains (oprops, EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialChannels")) {
1291                                 priv->can_upgrade_to_muc = TRUE;
1292                                 break;
1293                         }
1294                 }
1295
1296                 g_list_free (list);
1297                 g_object_unref (dispatcher);
1298         }
1299
1300         if (tp_proxy_has_interface_by_id (priv->channel,
1301                                           TP_IFACE_QUARK_PROPERTIES_INTERFACE)) {
1302                 tp_cli_properties_interface_call_list_properties (priv->channel, -1,
1303                                                                   tp_chat_list_properties_cb,
1304                                                                   NULL, NULL,
1305                                                                   G_OBJECT (chat));
1306                 tp_cli_properties_interface_connect_to_properties_changed (priv->channel,
1307                                                                            tp_chat_properties_changed_cb,
1308                                                                            NULL, NULL,
1309                                                                            G_OBJECT (chat), NULL);
1310                 tp_cli_properties_interface_connect_to_property_flags_changed (priv->channel,
1311                                                                                tp_chat_property_flags_changed_cb,
1312                                                                                NULL, NULL,
1313                                                                                G_OBJECT (chat), NULL);
1314         }
1315
1316         /* Check if the chat is password protected */
1317         if (tp_proxy_has_interface_by_id (priv->channel,
1318                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_PASSWORD)) {
1319                 priv->got_password_flags = FALSE;
1320
1321                 tp_cli_channel_interface_password_connect_to_password_flags_changed
1322                         (priv->channel, password_flags_changed_cb, chat, NULL,
1323                          G_OBJECT (chat), NULL);
1324
1325                 tp_cli_channel_interface_password_call_get_password_flags
1326                         (priv->channel, -1, got_password_flags_cb, chat, NULL, chat);
1327         } else {
1328                 /* No Password interface, so no need to fetch the password flags */
1329                 priv->got_password_flags = TRUE;
1330         }
1331
1332         return chat;
1333 }
1334
1335 static void
1336 tp_chat_get_property (GObject    *object,
1337                       guint       param_id,
1338                       GValue     *value,
1339                       GParamSpec *pspec)
1340 {
1341         EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
1342         EmpathyTpChatPriv *priv = GET_PRIV (object);
1343
1344         switch (param_id) {
1345         case PROP_CHANNEL:
1346                 g_value_set_object (value, priv->channel);
1347                 break;
1348         case PROP_REMOTE_CONTACT:
1349                 g_value_set_object (value, priv->remote_contact);
1350                 break;
1351         case PROP_READY:
1352                 g_value_set_boolean (value, priv->ready);
1353                 break;
1354         case PROP_PASSWORD_NEEDED:
1355                 g_value_set_boolean (value, empathy_tp_chat_password_needed (self));
1356                 break;
1357         default:
1358                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1359                 break;
1360         };
1361 }
1362
1363 static void
1364 tp_chat_set_property (GObject      *object,
1365                       guint         param_id,
1366                       const GValue *value,
1367                       GParamSpec   *pspec)
1368 {
1369         EmpathyTpChatPriv *priv = GET_PRIV (object);
1370
1371         switch (param_id) {
1372         case PROP_CHANNEL:
1373                 priv->channel = g_value_dup_object (value);
1374                 break;
1375         default:
1376                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1377                 break;
1378         };
1379 }
1380
1381 static void
1382 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
1383 {
1384         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1385
1386         object_class->dispose = tp_chat_dispose;
1387         object_class->finalize = tp_chat_finalize;
1388         object_class->constructor = tp_chat_constructor;
1389         object_class->get_property = tp_chat_get_property;
1390         object_class->set_property = tp_chat_set_property;
1391
1392         g_object_class_install_property (object_class,
1393                                          PROP_CHANNEL,
1394                                          g_param_spec_object ("channel",
1395                                                               "telepathy channel",
1396                                                               "The text channel for the chat",
1397                                                               TP_TYPE_CHANNEL,
1398                                                               G_PARAM_READWRITE |
1399                                                               G_PARAM_CONSTRUCT_ONLY));
1400
1401         g_object_class_install_property (object_class,
1402                                          PROP_REMOTE_CONTACT,
1403                                          g_param_spec_object ("remote-contact",
1404                                                               "The remote contact",
1405                                                               "The remote contact if there is no group iface on the channel",
1406                                                               EMPATHY_TYPE_CONTACT,
1407                                                               G_PARAM_READABLE));
1408
1409         g_object_class_install_property (object_class,
1410                                          PROP_READY,
1411                                          g_param_spec_boolean ("ready",
1412                                                                "Is the object ready",
1413                                                                "This object can't be used until this becomes true",
1414                                                                FALSE,
1415                                                                G_PARAM_READABLE));
1416
1417         g_object_class_install_property (object_class,
1418                                          PROP_PASSWORD_NEEDED,
1419                                          g_param_spec_boolean ("password-needed",
1420                                                                "password needed",
1421                                                                "TRUE if a password is needed to join the channel",
1422                                                                FALSE,
1423                                                                G_PARAM_READABLE));
1424
1425         /* Signals */
1426         signals[MESSAGE_RECEIVED] =
1427                 g_signal_new ("message-received",
1428                               G_TYPE_FROM_CLASS (klass),
1429                               G_SIGNAL_RUN_LAST,
1430                               0,
1431                               NULL, NULL,
1432                               g_cclosure_marshal_VOID__OBJECT,
1433                               G_TYPE_NONE,
1434                               1, EMPATHY_TYPE_MESSAGE);
1435
1436         signals[SEND_ERROR] =
1437                 g_signal_new ("send-error",
1438                               G_TYPE_FROM_CLASS (klass),
1439                               G_SIGNAL_RUN_LAST,
1440                               0,
1441                               NULL, NULL,
1442                               _empathy_marshal_VOID__STRING_UINT,
1443                               G_TYPE_NONE,
1444                               2, G_TYPE_STRING, G_TYPE_UINT);
1445
1446         signals[CHAT_STATE_CHANGED] =
1447                 g_signal_new ("chat-state-changed",
1448                               G_TYPE_FROM_CLASS (klass),
1449                               G_SIGNAL_RUN_LAST,
1450                               0,
1451                               NULL, NULL,
1452                               _empathy_marshal_VOID__OBJECT_UINT,
1453                               G_TYPE_NONE,
1454                               2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT);
1455
1456         signals[PROPERTY_CHANGED] =
1457                 g_signal_new ("property-changed",
1458                               G_TYPE_FROM_CLASS (klass),
1459                               G_SIGNAL_RUN_LAST,
1460                               0,
1461                               NULL, NULL,
1462                               _empathy_marshal_VOID__STRING_BOXED,
1463                               G_TYPE_NONE,
1464                               2, G_TYPE_STRING, G_TYPE_VALUE);
1465
1466         signals[DESTROY] =
1467                 g_signal_new ("destroy",
1468                               G_TYPE_FROM_CLASS (klass),
1469                               G_SIGNAL_RUN_LAST,
1470                               0,
1471                               NULL, NULL,
1472                               g_cclosure_marshal_VOID__VOID,
1473                               G_TYPE_NONE,
1474                               0);
1475
1476         g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
1477 }
1478
1479 static void
1480 empathy_tp_chat_init (EmpathyTpChat *chat)
1481 {
1482         EmpathyTpChatPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chat,
1483                 EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv);
1484
1485         chat->priv = priv;
1486         priv->contact_monitor = NULL;
1487         priv->messages_queue = g_queue_new ();
1488         priv->pending_messages_queue = g_queue_new ();
1489 }
1490
1491 static void
1492 tp_chat_iface_init (EmpathyContactListIface *iface)
1493 {
1494         iface->add         = tp_chat_add;
1495         iface->remove      = tp_chat_remove;
1496         iface->get_members = tp_chat_get_members;
1497         iface->get_monitor = tp_chat_get_monitor;
1498 }
1499
1500 EmpathyTpChat *
1501 empathy_tp_chat_new (TpChannel *channel)
1502 {
1503         return g_object_new (EMPATHY_TYPE_TP_CHAT,
1504                              "channel", channel,
1505                              NULL);
1506 }
1507
1508 static void
1509 empathy_tp_chat_close (EmpathyTpChat *chat) {
1510         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1511
1512         /* If there are still messages left, it'll come back..
1513          * We loose the ordering of sent messages though */
1514         tp_cli_channel_call_close (priv->channel, -1, tp_chat_async_cb,
1515                 "closing channel", NULL, NULL);
1516 }
1517
1518 const gchar *
1519 empathy_tp_chat_get_id (EmpathyTpChat *chat)
1520 {
1521         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1522         const gchar *id;
1523
1524
1525         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1526
1527         id = tp_channel_get_identifier (priv->channel);
1528         if (!EMP_STR_EMPTY (id))
1529                 return id;
1530         else if (priv->remote_contact)
1531                 return empathy_contact_get_id (priv->remote_contact);
1532         else
1533                 return NULL;
1534
1535 }
1536
1537 EmpathyContact *
1538 empathy_tp_chat_get_remote_contact (EmpathyTpChat *chat)
1539 {
1540         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1541
1542         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1543         g_return_val_if_fail (priv->ready, NULL);
1544
1545         return priv->remote_contact;
1546 }
1547
1548 TpChannel *
1549 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
1550 {
1551         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1552
1553         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1554
1555         return priv->channel;
1556 }
1557
1558 TpConnection *
1559 empathy_tp_chat_get_connection (EmpathyTpChat *chat)
1560 {
1561         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1562
1563         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1564
1565         return tp_channel_borrow_connection (priv->channel);
1566 }
1567
1568 gboolean
1569 empathy_tp_chat_is_ready (EmpathyTpChat *chat)
1570 {
1571         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1572
1573         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
1574
1575         return priv->ready;
1576 }
1577
1578 void
1579 empathy_tp_chat_send (EmpathyTpChat *chat,
1580                       EmpathyMessage *message)
1581 {
1582         EmpathyTpChatPriv        *priv = GET_PRIV (chat);
1583         const gchar              *message_body;
1584         TpChannelTextMessageType  message_type;
1585
1586         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1587         g_return_if_fail (EMPATHY_IS_MESSAGE (message));
1588         g_return_if_fail (priv->ready);
1589
1590         message_body = empathy_message_get_body (message);
1591         message_type = empathy_message_get_tptype (message);
1592
1593         DEBUG ("Sending message: %s", message_body);
1594         tp_cli_channel_type_text_call_send (priv->channel, -1,
1595                                             message_type,
1596                                             message_body,
1597                                             tp_chat_send_cb,
1598                                             g_object_ref (message),
1599                                             (GDestroyNotify) g_object_unref,
1600                                             G_OBJECT (chat));
1601 }
1602
1603 void
1604 empathy_tp_chat_set_state (EmpathyTpChat      *chat,
1605                            TpChannelChatState  state)
1606 {
1607         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1608
1609         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1610         g_return_if_fail (priv->ready);
1611
1612         if (tp_proxy_has_interface_by_id (priv->channel,
1613                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_CHAT_STATE)) {
1614                 DEBUG ("Set state: %d", state);
1615                 tp_cli_channel_interface_chat_state_call_set_chat_state (priv->channel, -1,
1616                                                                          state,
1617                                                                          tp_chat_async_cb,
1618                                                                          "setting chat state",
1619                                                                          NULL,
1620                                                                          G_OBJECT (chat));
1621         }
1622 }
1623
1624
1625 const GList *
1626 empathy_tp_chat_get_pending_messages (EmpathyTpChat *chat)
1627 {
1628         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1629
1630         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1631         g_return_val_if_fail (priv->ready, NULL);
1632
1633         return priv->pending_messages_queue->head;
1634 }
1635
1636 static void
1637 acknowledge_messages (EmpathyTpChat *chat, GArray *ids) {
1638         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1639
1640         tp_cli_channel_type_text_call_acknowledge_pending_messages (
1641                 priv->channel, -1, ids, tp_chat_async_cb,
1642                 "acknowledging received message", NULL, G_OBJECT (chat));
1643 }
1644
1645 void
1646 empathy_tp_chat_acknowledge_message (EmpathyTpChat *chat,
1647                                      EmpathyMessage *message) {
1648         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1649         GArray *message_ids;
1650         GList *m;
1651         guint id;
1652
1653         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1654         g_return_if_fail (priv->ready);
1655
1656         if (!empathy_message_is_incoming (message))
1657                 goto out;
1658
1659         message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
1660
1661         id = empathy_message_get_id (message);
1662         g_array_append_val (message_ids, id);
1663         acknowledge_messages (chat, message_ids);
1664         g_array_free (message_ids, TRUE);
1665
1666 out:
1667         m = g_queue_find (priv->pending_messages_queue, message);
1668         g_assert (m != NULL);
1669         g_queue_delete_link (priv->pending_messages_queue, m);
1670         g_object_unref (message);
1671 }
1672
1673 void
1674 empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat,
1675                                       const GSList *messages) {
1676         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1677         /* Copy messages as the messges list (probably is) our own */
1678         GSList *msgs = g_slist_copy ((GSList *) messages);
1679         GSList *l;
1680         guint length;
1681         GArray *message_ids;
1682
1683         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1684         g_return_if_fail (priv->ready);
1685
1686         length = g_slist_length ((GSList *) messages);
1687
1688         if (length == 0)
1689                 return;
1690
1691         message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), length);
1692
1693         for (l = msgs; l != NULL; l = g_slist_next (l)) {
1694                 GList *m;
1695
1696                 EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
1697
1698                 m = g_queue_find (priv->pending_messages_queue, message);
1699                 g_assert (m != NULL);
1700                 g_queue_delete_link (priv->pending_messages_queue, m);
1701
1702                 if (empathy_message_is_incoming (message)) {
1703                         guint id = empathy_message_get_id (message);
1704                         g_array_append_val (message_ids, id);
1705                 }
1706                 g_object_unref (message);
1707         }
1708
1709         if (message_ids->len > 0)
1710                 acknowledge_messages (chat, message_ids);
1711
1712         g_array_free (message_ids, TRUE);
1713         g_slist_free (msgs);
1714 }
1715
1716 void
1717 empathy_tp_chat_acknowledge_all_messages (EmpathyTpChat *chat)
1718 {
1719   empathy_tp_chat_acknowledge_messages (chat,
1720     (GSList *) empathy_tp_chat_get_pending_messages (chat));
1721 }
1722
1723 gboolean
1724 empathy_tp_chat_password_needed (EmpathyTpChat *self)
1725 {
1726         EmpathyTpChatPriv *priv = GET_PRIV (self);
1727
1728         return priv->password_flags & TP_CHANNEL_PASSWORD_FLAG_PROVIDE;
1729 }
1730
1731 static void
1732 provide_password_cb (TpChannel *channel,
1733                                       gboolean correct,
1734                                       const GError *error,
1735                                       gpointer user_data,
1736                                       GObject *weak_object)
1737 {
1738         GSimpleAsyncResult *result = user_data;
1739
1740         if (error != NULL) {
1741                 g_simple_async_result_set_from_error (result, error);
1742         }
1743         else if (!correct) {
1744                 /* The current D-Bus API is a bit weird so re-use the
1745                  * AuthenticationFailed error */
1746                 g_simple_async_result_set_error (result, TP_ERRORS,
1747                                                  TP_ERROR_AUTHENTICATION_FAILED, "Wrong password");
1748         }
1749
1750         g_simple_async_result_complete (result);
1751         g_object_unref (result);
1752 }
1753
1754 void
1755 empathy_tp_chat_provide_password_async (EmpathyTpChat *self,
1756                                                      const gchar *password,
1757                                                      GAsyncReadyCallback callback,
1758                                                      gpointer user_data)
1759 {
1760         EmpathyTpChatPriv *priv = GET_PRIV (self);
1761         GSimpleAsyncResult *result;
1762
1763         result = g_simple_async_result_new (G_OBJECT (self),
1764                                             callback, user_data,
1765                                             empathy_tp_chat_provide_password_finish);
1766
1767         tp_cli_channel_interface_password_call_provide_password
1768                 (priv->channel, -1, password, provide_password_cb, result,
1769                  NULL, G_OBJECT (self));
1770 }
1771
1772 gboolean
1773 empathy_tp_chat_provide_password_finish (EmpathyTpChat *self,
1774                                                       GAsyncResult *result,
1775                                                       GError **error)
1776 {
1777         if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
1778                 error))
1779                 return FALSE;
1780
1781         g_return_val_if_fail (g_simple_async_result_is_valid (result,
1782                                                               G_OBJECT (self), empathy_tp_chat_provide_password_finish), FALSE);
1783
1784         return TRUE;
1785 }
1786
1787 /**
1788  * empathy_tp_chat_can_add_contact:
1789  *
1790  * Returns: %TRUE if empathy_contact_list_add() will work for this channel.
1791  * That is if this chat is a 1-to-1 channel that can be upgraded to
1792  * a MUC using the Conference interface or if the channel is a MUC.
1793  */
1794 gboolean
1795 empathy_tp_chat_can_add_contact (EmpathyTpChat *self)
1796 {
1797         EmpathyTpChatPriv *priv;
1798
1799         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), FALSE);
1800
1801         priv = GET_PRIV (self);
1802
1803         return priv->can_upgrade_to_muc ||
1804                 tp_proxy_has_interface_by_id (priv->channel,
1805                         TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP);;
1806 }
1807
1808 static void
1809 leave_remove_members_cb (TpChannel *proxy,
1810                          const GError *error,
1811                          gpointer user_data,
1812                          GObject *weak_object)
1813 {
1814         EmpathyTpChat *self = user_data;
1815
1816         if (error == NULL)
1817                 return;
1818
1819         DEBUG ("RemoveMembers failed (%s); closing the channel", error->message);
1820         empathy_tp_chat_close (self);
1821 }
1822
1823 void
1824 empathy_tp_chat_leave (EmpathyTpChat *self)
1825 {
1826         EmpathyTpChatPriv *priv = GET_PRIV (self);
1827         TpHandle self_handle;
1828         GArray *array;
1829
1830         if (!tp_proxy_has_interface_by_id (priv->channel,
1831                 TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
1832                 empathy_tp_chat_close (self);
1833                 return;
1834         }
1835
1836         self_handle = tp_channel_group_get_self_handle (priv->channel);
1837         if (self_handle == 0) {
1838                 /* we are not member of the channel */
1839                 empathy_tp_chat_close (self);
1840                 return;
1841         }
1842
1843         array = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
1844         g_array_insert_val (array, 0, self_handle);
1845
1846         tp_cli_channel_interface_group_call_remove_members (priv->channel, -1, array,
1847                 "", leave_remove_members_cb, self, NULL, G_OBJECT (self));
1848
1849         g_array_free (array, TRUE);
1850 }