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