]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-chat.c
tp-chat: remove butterfly MSN HandleType=NONE workaround
[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 chat, but it's the new style where 1-1 chats don't
889          * have the group interface. If it has the conference interface, then
890          * it is indeed a MUC. */
891         if (tp_proxy_has_interface_by_id (priv->channel,
892                                           EMP_IFACE_QUARK_CHANNEL_INTERFACE_CONFERENCE)) {
893                 return;
894         }
895
896         /* This is an MSN-like chat where anyone can join the chat at anytime.
897          * If there is only one non-self contact member, we are in a private
898          * chat and we set the "remote-contact" property to that contact. If
899          * there are more, set the "remote-contact" property to NULL and the
900          * UI will display a contact list. */
901         self_handle = tp_channel_group_get_self_handle (priv->channel);
902         for (l = priv->members; l; l = l->next) {
903                 /* Skip self contact if member */
904                 if (empathy_contact_get_handle (l->data) == self_handle) {
905                         continue;
906                 }
907
908                 /* We have more than one remote contact, break */
909                 if (contact != NULL) {
910                         contact = NULL;
911                         break;
912                 }
913
914                 /* If we didn't find yet a remote contact, keep this one */
915                 contact = l->data;
916         }
917
918         if (priv->remote_contact == contact) {
919                 return;
920         }
921
922         DEBUG ("Changing remote contact from %p to %p",
923                 priv->remote_contact, contact);
924
925         if (priv->remote_contact) {
926                 g_object_unref (priv->remote_contact);
927         }
928
929         priv->remote_contact = contact ? g_object_ref (contact) : NULL;
930         g_object_notify (G_OBJECT (chat), "remote-contact");
931 }
932
933 static void
934 tp_chat_got_added_contacts_cb (EmpathyTpContactFactory *factory,
935                                guint                    n_contacts,
936                                EmpathyContact * const * contacts,
937                                guint                    n_failed,
938                                const TpHandle          *failed,
939                                const GError            *error,
940                                gpointer                 user_data,
941                                GObject                 *chat)
942 {
943         EmpathyTpChatPriv *priv = GET_PRIV (chat);
944         guint i;
945         const TpIntSet *members;
946         TpHandle handle;
947         EmpathyContact *contact;
948
949         if (error) {
950                 DEBUG ("Error: %s", error->message);
951                 return;
952         }
953
954         members = tp_channel_group_get_members (priv->channel);
955         for (i = 0; i < n_contacts; i++) {
956                 contact = contacts[i];
957                 handle = empathy_contact_get_handle (contact);
958
959                 /* Make sure the contact is still member */
960                 if (tp_intset_is_member (members, handle)) {
961                         priv->members = g_list_prepend (priv->members,
962                                 g_object_ref (contact));
963                         g_signal_emit_by_name (chat, "members-changed",
964                                                contact, NULL, 0, NULL, TRUE);
965                 }
966         }
967
968         tp_chat_update_remote_contact (EMPATHY_TP_CHAT (chat));
969         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
970 }
971
972 static EmpathyContact *
973 chat_lookup_contact (EmpathyTpChat *chat,
974                      TpHandle       handle,
975                      gboolean       remove_)
976 {
977         EmpathyTpChatPriv *priv = GET_PRIV (chat);
978         GList *l;
979
980         for (l = priv->members; l; l = l->next) {
981                 EmpathyContact *c = l->data;
982
983                 if (empathy_contact_get_handle (c) != handle) {
984                         continue;
985                 }
986
987                 if (remove_) {
988                         /* Caller takes the reference. */
989                         priv->members = g_list_delete_link (priv->members, l);
990                 } else {
991                         g_object_ref (c);
992                 }
993
994                 return c;
995         }
996
997         return NULL;
998 }
999
1000 typedef struct
1001 {
1002     TpHandle old_handle;
1003     guint reason;
1004     gchar *message;
1005 } ContactRenameData;
1006
1007 static ContactRenameData *
1008 contact_rename_data_new (TpHandle handle,
1009                          guint reason,
1010                          const gchar* message)
1011 {
1012         ContactRenameData *data = g_new (ContactRenameData, 1);
1013         data->old_handle = handle;
1014         data->reason = reason;
1015         data->message = g_strdup (message);
1016
1017         return data;
1018 }
1019
1020 static void
1021 contact_rename_data_free (ContactRenameData* data)
1022 {
1023         g_free (data->message);
1024         g_free (data);
1025 }
1026
1027 static void
1028 tp_chat_got_renamed_contacts_cb (EmpathyTpContactFactory *factory,
1029                                  guint                    n_contacts,
1030                                  EmpathyContact * const * contacts,
1031                                  guint                    n_failed,
1032                                  const TpHandle          *failed,
1033                                  const GError            *error,
1034                                  gpointer                 user_data,
1035                                  GObject                 *chat)
1036 {
1037         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1038         const TpIntSet *members;
1039         TpHandle handle;
1040         EmpathyContact *old = NULL, *new = NULL;
1041         ContactRenameData *rename_data = (ContactRenameData *) user_data;
1042
1043         if (error) {
1044                 DEBUG ("Error: %s", error->message);
1045                 return;
1046         }
1047
1048         /* renamed members can only be delivered one at a time */
1049         g_warn_if_fail (n_contacts == 1);
1050
1051         new = contacts[0];
1052
1053         members = tp_channel_group_get_members (priv->channel);
1054         handle = empathy_contact_get_handle (new);
1055
1056         old = chat_lookup_contact (EMPATHY_TP_CHAT (chat),
1057                                    rename_data->old_handle, TRUE);
1058
1059         /* Make sure the contact is still member */
1060         if (tp_intset_is_member (members, handle)) {
1061                 priv->members = g_list_prepend (priv->members,
1062                         g_object_ref (new));
1063
1064                 if (old != NULL) {
1065                         g_signal_emit_by_name (chat, "member-renamed",
1066                                                old, new, rename_data->reason,
1067                                                rename_data->message);
1068                         g_object_unref (old);
1069                 }
1070         }
1071
1072         tp_chat_update_remote_contact (EMPATHY_TP_CHAT (chat));
1073         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
1074 }
1075
1076
1077 static void
1078 tp_chat_group_members_changed_cb (TpChannel     *self,
1079                                   gchar         *message,
1080                                   GArray        *added,
1081                                   GArray        *removed,
1082                                   GArray        *local_pending,
1083                                   GArray        *remote_pending,
1084                                   guint          actor,
1085                                   guint          reason,
1086                                   EmpathyTpChat *chat)
1087 {
1088         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1089         EmpathyContact *contact;
1090         EmpathyContact *actor_contact = NULL;
1091         guint i;
1092         ContactRenameData *rename_data;
1093         TpHandle old_handle;
1094
1095         /* Contact renamed */
1096         if (reason == TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED) {
1097                 /* there can only be a single 'added' and a single 'removed' handle */
1098                 g_warn_if_fail (removed->len == 1);
1099                 g_warn_if_fail (added->len == 1);
1100
1101                 old_handle = g_array_index (removed, guint, 0);
1102
1103                 rename_data = contact_rename_data_new (old_handle, reason, message);
1104                 empathy_tp_contact_factory_get_from_handles (priv->factory,
1105                         added->len, (TpHandle *) added->data,
1106                         tp_chat_got_renamed_contacts_cb,
1107                         rename_data, (GDestroyNotify) contact_rename_data_free,
1108                         G_OBJECT (chat));
1109                 return;
1110         }
1111
1112         if (actor != 0) {
1113                 actor_contact = chat_lookup_contact (chat, actor, FALSE);
1114                 if (actor_contact == NULL) {
1115                         /* FIXME: handle this a tad more gracefully: perhaps
1116                          * the actor was a server op. We could use the
1117                          * contact-ids detail of MembersChangedDetailed.
1118                          */
1119                         DEBUG ("actor %u not a channel member", actor);
1120                 }
1121         }
1122
1123         /* Remove contacts that are not members anymore */
1124         for (i = 0; i < removed->len; i++) {
1125                 contact = chat_lookup_contact (chat,
1126                         g_array_index (removed, TpHandle, i), TRUE);
1127
1128                 if (contact != NULL) {
1129                         g_signal_emit_by_name (chat, "members-changed", contact,
1130                                                actor_contact, reason, message,
1131                                                FALSE);
1132                         g_object_unref (contact);
1133                 }
1134         }
1135
1136         /* Request added contacts */
1137         if (added->len > 0) {
1138                 empathy_tp_contact_factory_get_from_handles (priv->factory,
1139                         added->len, (TpHandle *) added->data,
1140                         tp_chat_got_added_contacts_cb, NULL, NULL,
1141                         G_OBJECT (chat));
1142         }
1143
1144         tp_chat_update_remote_contact (chat);
1145
1146         if (actor_contact != NULL) {
1147                 g_object_unref (actor_contact);
1148         }
1149 }
1150
1151 static void
1152 tp_chat_got_remote_contact_cb (EmpathyTpContactFactory *factory,
1153                                EmpathyContact          *contact,
1154                                const GError            *error,
1155                                gpointer                 user_data,
1156                                GObject                 *chat)
1157 {
1158         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1159
1160         if (error) {
1161                 DEBUG ("Error: %s", error->message);
1162                 empathy_tp_chat_leave (EMPATHY_TP_CHAT (chat));
1163                 return;
1164         }
1165
1166         priv->remote_contact = g_object_ref (contact);
1167         g_object_notify (chat, "remote-contact");
1168
1169         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
1170 }
1171
1172 static void
1173 tp_chat_got_self_contact_cb (EmpathyTpContactFactory *factory,
1174                              EmpathyContact          *contact,
1175                              const GError            *error,
1176                              gpointer                 user_data,
1177                              GObject                 *chat)
1178 {
1179         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1180
1181         if (error) {
1182                 DEBUG ("Error: %s", error->message);
1183                 empathy_tp_chat_leave (EMPATHY_TP_CHAT (chat));
1184                 return;
1185         }
1186
1187         priv->user = g_object_ref (contact);
1188         empathy_contact_set_is_user (priv->user, TRUE);
1189         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
1190 }
1191
1192 static void
1193 password_flags_changed_cb (TpChannel *channel,
1194     guint added,
1195     guint removed,
1196     gpointer user_data,
1197     GObject *weak_object)
1198 {
1199         EmpathyTpChat *self = EMPATHY_TP_CHAT (weak_object);
1200         EmpathyTpChatPriv *priv = GET_PRIV (self);
1201         gboolean was_needed, needed;
1202
1203         was_needed = empathy_tp_chat_password_needed (self);
1204
1205         priv->password_flags |= added;
1206         priv->password_flags ^= removed;
1207
1208         needed = empathy_tp_chat_password_needed (self);
1209
1210         if (was_needed != needed)
1211                 g_object_notify (G_OBJECT (self), "password-needed");
1212 }
1213
1214 static void
1215 got_password_flags_cb (TpChannel *proxy,
1216                              guint password_flags,
1217                              const GError *error,
1218                              gpointer user_data,
1219                              GObject *weak_object)
1220 {
1221         EmpathyTpChat *self = EMPATHY_TP_CHAT (weak_object);
1222         EmpathyTpChatPriv *priv = GET_PRIV (self);
1223
1224         priv->got_password_flags = TRUE;
1225         priv->password_flags = password_flags;
1226
1227         tp_chat_check_if_ready (EMPATHY_TP_CHAT (self));
1228 }
1229
1230 static GObject *
1231 tp_chat_constructor (GType                  type,
1232                      guint                  n_props,
1233                      GObjectConstructParam *props)
1234 {
1235         GObject           *chat;
1236         EmpathyTpChatPriv *priv;
1237         TpConnection      *connection;
1238         TpHandle           handle;
1239
1240         chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
1241
1242         priv = GET_PRIV (chat);
1243
1244         connection = tp_channel_borrow_connection (priv->channel);
1245         priv->factory = empathy_tp_contact_factory_dup_singleton (connection);
1246         g_signal_connect (priv->channel, "invalidated",
1247                           G_CALLBACK (tp_chat_invalidated_cb),
1248                           chat);
1249
1250         if (tp_proxy_has_interface_by_id (priv->channel,
1251                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
1252                 const TpIntSet *members;
1253                 GArray *handles;
1254
1255                 /* Get self contact from the group's self handle */
1256                 handle = tp_channel_group_get_self_handle (priv->channel);
1257                 empathy_tp_contact_factory_get_from_handle (priv->factory,
1258                         handle, tp_chat_got_self_contact_cb,
1259                         NULL, NULL, chat);
1260
1261                 /* Get initial member contacts */
1262                 members = tp_channel_group_get_members (priv->channel);
1263                 handles = tp_intset_to_array (members);
1264                 empathy_tp_contact_factory_get_from_handles (priv->factory,
1265                         handles->len, (TpHandle *) handles->data,
1266                         tp_chat_got_added_contacts_cb, NULL, NULL, chat);
1267
1268                 priv->can_upgrade_to_muc = FALSE;
1269
1270                 g_signal_connect (priv->channel, "group-members-changed",
1271                         G_CALLBACK (tp_chat_group_members_changed_cb), chat);
1272         } else {
1273                 EmpathyDispatcher *dispatcher = empathy_dispatcher_dup_singleton ();
1274                 GList *list, *ptr;
1275
1276                 /* Get the self contact from the connection's self handle */
1277                 handle = tp_connection_get_self_handle (connection);
1278                 empathy_tp_contact_factory_get_from_handle (priv->factory,
1279                         handle, tp_chat_got_self_contact_cb,
1280                         NULL, NULL, chat);
1281
1282                 /* Get the remote contact */
1283                 handle = tp_channel_get_handle (priv->channel, NULL);
1284                 empathy_tp_contact_factory_get_from_handle (priv->factory,
1285                         handle, tp_chat_got_remote_contact_cb,
1286                         NULL, NULL, chat);
1287
1288                 list = empathy_dispatcher_find_requestable_channel_classes (
1289                         dispatcher, connection,
1290                         tp_channel_get_channel_type (priv->channel),
1291                         TP_UNKNOWN_HANDLE_TYPE, NULL);
1292
1293                 for (ptr = list; ptr; ptr = ptr->next) {
1294                         GValueArray *array = ptr->data;
1295                         const char **oprops = g_value_get_boxed (
1296                                 g_value_array_get_nth (array, 1));
1297
1298                         if (tp_strv_contains (oprops, EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialChannels")) {
1299                                 priv->can_upgrade_to_muc = TRUE;
1300                                 break;
1301                         }
1302                 }
1303
1304                 g_list_free (list);
1305                 g_object_unref (dispatcher);
1306         }
1307
1308         if (tp_proxy_has_interface_by_id (priv->channel,
1309                                           TP_IFACE_QUARK_PROPERTIES_INTERFACE)) {
1310                 tp_cli_properties_interface_call_list_properties (priv->channel, -1,
1311                                                                   tp_chat_list_properties_cb,
1312                                                                   NULL, NULL,
1313                                                                   G_OBJECT (chat));
1314                 tp_cli_properties_interface_connect_to_properties_changed (priv->channel,
1315                                                                            tp_chat_properties_changed_cb,
1316                                                                            NULL, NULL,
1317                                                                            G_OBJECT (chat), NULL);
1318                 tp_cli_properties_interface_connect_to_property_flags_changed (priv->channel,
1319                                                                                tp_chat_property_flags_changed_cb,
1320                                                                                NULL, NULL,
1321                                                                                G_OBJECT (chat), NULL);
1322         }
1323
1324         /* Check if the chat is password protected */
1325         if (tp_proxy_has_interface_by_id (priv->channel,
1326                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_PASSWORD)) {
1327                 priv->got_password_flags = FALSE;
1328
1329                 tp_cli_channel_interface_password_connect_to_password_flags_changed
1330                         (priv->channel, password_flags_changed_cb, chat, NULL,
1331                          G_OBJECT (chat), NULL);
1332
1333                 tp_cli_channel_interface_password_call_get_password_flags
1334                         (priv->channel, -1, got_password_flags_cb, chat, NULL, chat);
1335         } else {
1336                 /* No Password interface, so no need to fetch the password flags */
1337                 priv->got_password_flags = TRUE;
1338         }
1339
1340         return chat;
1341 }
1342
1343 static void
1344 tp_chat_get_property (GObject    *object,
1345                       guint       param_id,
1346                       GValue     *value,
1347                       GParamSpec *pspec)
1348 {
1349         EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
1350         EmpathyTpChatPriv *priv = GET_PRIV (object);
1351
1352         switch (param_id) {
1353         case PROP_CHANNEL:
1354                 g_value_set_object (value, priv->channel);
1355                 break;
1356         case PROP_REMOTE_CONTACT:
1357                 g_value_set_object (value, priv->remote_contact);
1358                 break;
1359         case PROP_READY:
1360                 g_value_set_boolean (value, priv->ready);
1361                 break;
1362         case PROP_PASSWORD_NEEDED:
1363                 g_value_set_boolean (value, empathy_tp_chat_password_needed (self));
1364                 break;
1365         default:
1366                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1367                 break;
1368         };
1369 }
1370
1371 static void
1372 tp_chat_set_property (GObject      *object,
1373                       guint         param_id,
1374                       const GValue *value,
1375                       GParamSpec   *pspec)
1376 {
1377         EmpathyTpChatPriv *priv = GET_PRIV (object);
1378
1379         switch (param_id) {
1380         case PROP_CHANNEL:
1381                 priv->channel = g_value_dup_object (value);
1382                 break;
1383         default:
1384                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1385                 break;
1386         };
1387 }
1388
1389 static void
1390 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
1391 {
1392         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1393
1394         object_class->dispose = tp_chat_dispose;
1395         object_class->finalize = tp_chat_finalize;
1396         object_class->constructor = tp_chat_constructor;
1397         object_class->get_property = tp_chat_get_property;
1398         object_class->set_property = tp_chat_set_property;
1399
1400         g_object_class_install_property (object_class,
1401                                          PROP_CHANNEL,
1402                                          g_param_spec_object ("channel",
1403                                                               "telepathy channel",
1404                                                               "The text channel for the chat",
1405                                                               TP_TYPE_CHANNEL,
1406                                                               G_PARAM_READWRITE |
1407                                                               G_PARAM_CONSTRUCT_ONLY));
1408
1409         g_object_class_install_property (object_class,
1410                                          PROP_REMOTE_CONTACT,
1411                                          g_param_spec_object ("remote-contact",
1412                                                               "The remote contact",
1413                                                               "The remote contact if there is no group iface on the channel",
1414                                                               EMPATHY_TYPE_CONTACT,
1415                                                               G_PARAM_READABLE));
1416
1417         g_object_class_install_property (object_class,
1418                                          PROP_READY,
1419                                          g_param_spec_boolean ("ready",
1420                                                                "Is the object ready",
1421                                                                "This object can't be used until this becomes true",
1422                                                                FALSE,
1423                                                                G_PARAM_READABLE));
1424
1425         g_object_class_install_property (object_class,
1426                                          PROP_PASSWORD_NEEDED,
1427                                          g_param_spec_boolean ("password-needed",
1428                                                                "password needed",
1429                                                                "TRUE if a password is needed to join the channel",
1430                                                                FALSE,
1431                                                                G_PARAM_READABLE));
1432
1433         /* Signals */
1434         signals[MESSAGE_RECEIVED] =
1435                 g_signal_new ("message-received",
1436                               G_TYPE_FROM_CLASS (klass),
1437                               G_SIGNAL_RUN_LAST,
1438                               0,
1439                               NULL, NULL,
1440                               g_cclosure_marshal_VOID__OBJECT,
1441                               G_TYPE_NONE,
1442                               1, EMPATHY_TYPE_MESSAGE);
1443
1444         signals[SEND_ERROR] =
1445                 g_signal_new ("send-error",
1446                               G_TYPE_FROM_CLASS (klass),
1447                               G_SIGNAL_RUN_LAST,
1448                               0,
1449                               NULL, NULL,
1450                               _empathy_marshal_VOID__STRING_UINT,
1451                               G_TYPE_NONE,
1452                               2, G_TYPE_STRING, G_TYPE_UINT);
1453
1454         signals[CHAT_STATE_CHANGED] =
1455                 g_signal_new ("chat-state-changed",
1456                               G_TYPE_FROM_CLASS (klass),
1457                               G_SIGNAL_RUN_LAST,
1458                               0,
1459                               NULL, NULL,
1460                               _empathy_marshal_VOID__OBJECT_UINT,
1461                               G_TYPE_NONE,
1462                               2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT);
1463
1464         signals[PROPERTY_CHANGED] =
1465                 g_signal_new ("property-changed",
1466                               G_TYPE_FROM_CLASS (klass),
1467                               G_SIGNAL_RUN_LAST,
1468                               0,
1469                               NULL, NULL,
1470                               _empathy_marshal_VOID__STRING_BOXED,
1471                               G_TYPE_NONE,
1472                               2, G_TYPE_STRING, G_TYPE_VALUE);
1473
1474         signals[DESTROY] =
1475                 g_signal_new ("destroy",
1476                               G_TYPE_FROM_CLASS (klass),
1477                               G_SIGNAL_RUN_LAST,
1478                               0,
1479                               NULL, NULL,
1480                               g_cclosure_marshal_VOID__VOID,
1481                               G_TYPE_NONE,
1482                               0);
1483
1484         g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
1485 }
1486
1487 static void
1488 empathy_tp_chat_init (EmpathyTpChat *chat)
1489 {
1490         EmpathyTpChatPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chat,
1491                 EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv);
1492
1493         chat->priv = priv;
1494         priv->contact_monitor = NULL;
1495         priv->messages_queue = g_queue_new ();
1496         priv->pending_messages_queue = g_queue_new ();
1497 }
1498
1499 static void
1500 tp_chat_iface_init (EmpathyContactListIface *iface)
1501 {
1502         iface->add         = tp_chat_add;
1503         iface->remove      = tp_chat_remove;
1504         iface->get_members = tp_chat_get_members;
1505         iface->get_monitor = tp_chat_get_monitor;
1506 }
1507
1508 EmpathyTpChat *
1509 empathy_tp_chat_new (TpChannel *channel)
1510 {
1511         return g_object_new (EMPATHY_TYPE_TP_CHAT,
1512                              "channel", channel,
1513                              NULL);
1514 }
1515
1516 static void
1517 empathy_tp_chat_close (EmpathyTpChat *chat) {
1518         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1519
1520         /* If there are still messages left, it'll come back..
1521          * We loose the ordering of sent messages though */
1522         tp_cli_channel_call_close (priv->channel, -1, tp_chat_async_cb,
1523                 "closing channel", NULL, NULL);
1524 }
1525
1526 const gchar *
1527 empathy_tp_chat_get_id (EmpathyTpChat *chat)
1528 {
1529         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1530         const gchar *id;
1531
1532
1533         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1534
1535         id = tp_channel_get_identifier (priv->channel);
1536         if (!EMP_STR_EMPTY (id))
1537                 return id;
1538         else if (priv->remote_contact)
1539                 return empathy_contact_get_id (priv->remote_contact);
1540         else
1541                 return NULL;
1542
1543 }
1544
1545 EmpathyContact *
1546 empathy_tp_chat_get_remote_contact (EmpathyTpChat *chat)
1547 {
1548         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1549
1550         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1551         g_return_val_if_fail (priv->ready, NULL);
1552
1553         return priv->remote_contact;
1554 }
1555
1556 TpChannel *
1557 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
1558 {
1559         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1560
1561         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1562
1563         return priv->channel;
1564 }
1565
1566 TpConnection *
1567 empathy_tp_chat_get_connection (EmpathyTpChat *chat)
1568 {
1569         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1570
1571         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1572
1573         return tp_channel_borrow_connection (priv->channel);
1574 }
1575
1576 gboolean
1577 empathy_tp_chat_is_ready (EmpathyTpChat *chat)
1578 {
1579         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1580
1581         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
1582
1583         return priv->ready;
1584 }
1585
1586 void
1587 empathy_tp_chat_send (EmpathyTpChat *chat,
1588                       EmpathyMessage *message)
1589 {
1590         EmpathyTpChatPriv        *priv = GET_PRIV (chat);
1591         const gchar              *message_body;
1592         TpChannelTextMessageType  message_type;
1593
1594         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1595         g_return_if_fail (EMPATHY_IS_MESSAGE (message));
1596         g_return_if_fail (priv->ready);
1597
1598         message_body = empathy_message_get_body (message);
1599         message_type = empathy_message_get_tptype (message);
1600
1601         DEBUG ("Sending message: %s", message_body);
1602         tp_cli_channel_type_text_call_send (priv->channel, -1,
1603                                             message_type,
1604                                             message_body,
1605                                             tp_chat_send_cb,
1606                                             g_object_ref (message),
1607                                             (GDestroyNotify) g_object_unref,
1608                                             G_OBJECT (chat));
1609 }
1610
1611 void
1612 empathy_tp_chat_set_state (EmpathyTpChat      *chat,
1613                            TpChannelChatState  state)
1614 {
1615         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1616
1617         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1618         g_return_if_fail (priv->ready);
1619
1620         if (tp_proxy_has_interface_by_id (priv->channel,
1621                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_CHAT_STATE)) {
1622                 DEBUG ("Set state: %d", state);
1623                 tp_cli_channel_interface_chat_state_call_set_chat_state (priv->channel, -1,
1624                                                                          state,
1625                                                                          tp_chat_async_cb,
1626                                                                          "setting chat state",
1627                                                                          NULL,
1628                                                                          G_OBJECT (chat));
1629         }
1630 }
1631
1632
1633 const GList *
1634 empathy_tp_chat_get_pending_messages (EmpathyTpChat *chat)
1635 {
1636         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1637
1638         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1639         g_return_val_if_fail (priv->ready, NULL);
1640
1641         return priv->pending_messages_queue->head;
1642 }
1643
1644 static void
1645 acknowledge_messages (EmpathyTpChat *chat, GArray *ids) {
1646         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1647
1648         tp_cli_channel_type_text_call_acknowledge_pending_messages (
1649                 priv->channel, -1, ids, tp_chat_async_cb,
1650                 "acknowledging received message", NULL, G_OBJECT (chat));
1651 }
1652
1653 void
1654 empathy_tp_chat_acknowledge_message (EmpathyTpChat *chat,
1655                                      EmpathyMessage *message) {
1656         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1657         GArray *message_ids;
1658         GList *m;
1659         guint id;
1660
1661         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1662         g_return_if_fail (priv->ready);
1663
1664         if (!empathy_message_is_incoming (message))
1665                 goto out;
1666
1667         message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
1668
1669         id = empathy_message_get_id (message);
1670         g_array_append_val (message_ids, id);
1671         acknowledge_messages (chat, message_ids);
1672         g_array_free (message_ids, TRUE);
1673
1674 out:
1675         m = g_queue_find (priv->pending_messages_queue, message);
1676         g_assert (m != NULL);
1677         g_queue_delete_link (priv->pending_messages_queue, m);
1678         g_object_unref (message);
1679 }
1680
1681 void
1682 empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat,
1683                                       const GSList *messages) {
1684         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1685         /* Copy messages as the messges list (probably is) our own */
1686         GSList *msgs = g_slist_copy ((GSList *) messages);
1687         GSList *l;
1688         guint length;
1689         GArray *message_ids;
1690
1691         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1692         g_return_if_fail (priv->ready);
1693
1694         length = g_slist_length ((GSList *) messages);
1695
1696         if (length == 0)
1697                 return;
1698
1699         message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), length);
1700
1701         for (l = msgs; l != NULL; l = g_slist_next (l)) {
1702                 GList *m;
1703
1704                 EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
1705
1706                 m = g_queue_find (priv->pending_messages_queue, message);
1707                 g_assert (m != NULL);
1708                 g_queue_delete_link (priv->pending_messages_queue, m);
1709
1710                 if (empathy_message_is_incoming (message)) {
1711                         guint id = empathy_message_get_id (message);
1712                         g_array_append_val (message_ids, id);
1713                 }
1714                 g_object_unref (message);
1715         }
1716
1717         if (message_ids->len > 0)
1718                 acknowledge_messages (chat, message_ids);
1719
1720         g_array_free (message_ids, TRUE);
1721         g_slist_free (msgs);
1722 }
1723
1724 void
1725 empathy_tp_chat_acknowledge_all_messages (EmpathyTpChat *chat)
1726 {
1727   empathy_tp_chat_acknowledge_messages (chat,
1728     (GSList *) empathy_tp_chat_get_pending_messages (chat));
1729 }
1730
1731 gboolean
1732 empathy_tp_chat_password_needed (EmpathyTpChat *self)
1733 {
1734         EmpathyTpChatPriv *priv = GET_PRIV (self);
1735
1736         return priv->password_flags & TP_CHANNEL_PASSWORD_FLAG_PROVIDE;
1737 }
1738
1739 static void
1740 provide_password_cb (TpChannel *channel,
1741                                       gboolean correct,
1742                                       const GError *error,
1743                                       gpointer user_data,
1744                                       GObject *weak_object)
1745 {
1746         GSimpleAsyncResult *result = user_data;
1747
1748         if (error != NULL) {
1749                 g_simple_async_result_set_from_error (result, error);
1750         }
1751         else if (!correct) {
1752                 /* The current D-Bus API is a bit weird so re-use the
1753                  * AuthenticationFailed error */
1754                 g_simple_async_result_set_error (result, TP_ERRORS,
1755                                                  TP_ERROR_AUTHENTICATION_FAILED, "Wrong password");
1756         }
1757
1758         g_simple_async_result_complete (result);
1759         g_object_unref (result);
1760 }
1761
1762 void
1763 empathy_tp_chat_provide_password_async (EmpathyTpChat *self,
1764                                                      const gchar *password,
1765                                                      GAsyncReadyCallback callback,
1766                                                      gpointer user_data)
1767 {
1768         EmpathyTpChatPriv *priv = GET_PRIV (self);
1769         GSimpleAsyncResult *result;
1770
1771         result = g_simple_async_result_new (G_OBJECT (self),
1772                                             callback, user_data,
1773                                             empathy_tp_chat_provide_password_finish);
1774
1775         tp_cli_channel_interface_password_call_provide_password
1776                 (priv->channel, -1, password, provide_password_cb, result,
1777                  NULL, G_OBJECT (self));
1778 }
1779
1780 gboolean
1781 empathy_tp_chat_provide_password_finish (EmpathyTpChat *self,
1782                                                       GAsyncResult *result,
1783                                                       GError **error)
1784 {
1785         if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
1786                 error))
1787                 return FALSE;
1788
1789         g_return_val_if_fail (g_simple_async_result_is_valid (result,
1790                                                               G_OBJECT (self), empathy_tp_chat_provide_password_finish), FALSE);
1791
1792         return TRUE;
1793 }
1794
1795 /**
1796  * empathy_tp_chat_can_add_contact:
1797  *
1798  * Returns: %TRUE if empathy_contact_list_add() will work for this channel.
1799  * That is if this chat is a 1-to-1 channel that can be upgraded to
1800  * a MUC using the Conference interface or if the channel is a MUC.
1801  */
1802 gboolean
1803 empathy_tp_chat_can_add_contact (EmpathyTpChat *self)
1804 {
1805         EmpathyTpChatPriv *priv;
1806
1807         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), FALSE);
1808
1809         priv = GET_PRIV (self);
1810
1811         return priv->can_upgrade_to_muc ||
1812                 tp_proxy_has_interface_by_id (priv->channel,
1813                         TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP);;
1814 }
1815
1816 static void
1817 leave_remove_members_cb (TpChannel *proxy,
1818                          const GError *error,
1819                          gpointer user_data,
1820                          GObject *weak_object)
1821 {
1822         EmpathyTpChat *self = user_data;
1823
1824         if (error == NULL)
1825                 return;
1826
1827         DEBUG ("RemoveMembers failed (%s); closing the channel", error->message);
1828         empathy_tp_chat_close (self);
1829 }
1830
1831 void
1832 empathy_tp_chat_leave (EmpathyTpChat *self)
1833 {
1834         EmpathyTpChatPriv *priv = GET_PRIV (self);
1835         TpHandle self_handle;
1836         GArray *array;
1837
1838         if (!tp_proxy_has_interface_by_id (priv->channel,
1839                 TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
1840                 empathy_tp_chat_close (self);
1841                 return;
1842         }
1843
1844         self_handle = tp_channel_group_get_self_handle (priv->channel);
1845         if (self_handle == 0) {
1846                 /* we are not member of the channel */
1847                 empathy_tp_chat_close (self);
1848                 return;
1849         }
1850
1851         array = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
1852         g_array_insert_val (array, 0, self_handle);
1853
1854         tp_cli_channel_interface_group_call_remove_members (priv->channel, -1, array,
1855                 "", leave_remove_members_cb, self, NULL, G_OBJECT (self));
1856
1857         g_array_free (array, TRUE);
1858 }