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