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