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