]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-chat.c
Port EmpathyTpChat to new API and drop usage of EmpathyTpGroup.
[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/channel.h>
27 #include <telepathy-glib/dbus.h>
28 #include <telepathy-glib/util.h>
29
30 #include "empathy-tp-chat.h"
31 #include "empathy-tp-contact-factory.h"
32 #include "empathy-contact-monitor.h"
33 #include "empathy-contact-list.h"
34 #include "empathy-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         EmpathyTpContactFactory *factory;
45         EmpathyContactMonitor *contact_monitor;
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         gboolean               ready;
58 } EmpathyTpChatPriv;
59
60 typedef struct {
61         gchar          *name;
62         guint           id;
63         TpPropertyFlags flags;
64         GValue         *value;
65 } TpChatProperty;
66
67 static void tp_chat_iface_init         (EmpathyContactListIface *iface);
68
69 enum {
70         PROP_0,
71         PROP_CHANNEL,
72         PROP_REMOTE_CONTACT,
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         EmpathyTpChatPriv *priv = GET_PRIV (chat);
101
102         DEBUG ("Channel invalidated: %s", message);
103         g_signal_emit (chat, signals[DESTROY], 0);
104
105         g_object_unref (priv->channel);
106         priv->channel = NULL;
107 }
108
109 static void
110 tp_chat_async_cb (TpChannel *proxy,
111                   const GError *error,
112                   gpointer user_data,
113                   GObject *weak_object)
114 {
115         if (error) {
116                 DEBUG ("Error %s: %s", (gchar*) user_data, error->message);
117         }
118 }
119
120 static void
121 tp_chat_add (EmpathyContactList *list,
122              EmpathyContact     *contact,
123              const gchar        *message)
124 {
125         EmpathyTpChatPriv *priv = GET_PRIV (list);
126         TpHandle           handle;
127         GArray             handles = {(gchar *) &handle, 1};
128
129         g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
130         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
131
132         handle = empathy_contact_get_handle (contact);
133         tp_cli_channel_interface_group_call_add_members (priv->channel, -1,
134                                                          &handles, NULL,
135                                                          NULL, NULL, NULL,
136                                                          NULL);
137 }
138
139 static void
140 tp_chat_remove (EmpathyContactList *list,
141                 EmpathyContact     *contact,
142                 const gchar        *message)
143 {
144         EmpathyTpChatPriv *priv = GET_PRIV (list);
145         TpHandle           handle;
146         GArray             handles = {(gchar *) &handle, 1};
147
148         g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
149         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
150
151         handle = empathy_contact_get_handle (contact);
152         tp_cli_channel_interface_group_call_remove_members (priv->channel, -1,
153                                                             &handles, NULL,
154                                                             NULL, NULL, NULL,
155                                                             NULL);
156 }
157
158 static GList *
159 tp_chat_get_members (EmpathyContactList *list)
160 {
161         EmpathyTpChatPriv *priv = GET_PRIV (list);
162         GList             *members = NULL;
163
164         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL);
165
166         if (priv->members) {
167                 members = g_list_copy (priv->members);
168                 g_list_foreach (members, (GFunc) g_object_ref, NULL);
169         } else {
170                 members = g_list_prepend (members, g_object_ref (priv->user));
171                 members = g_list_prepend (members, g_object_ref (priv->remote_contact));
172         }
173
174         return members;
175 }
176
177 static EmpathyContactMonitor *
178 tp_chat_get_monitor (EmpathyContactList *list)
179 {
180         EmpathyTpChatPriv *priv;
181
182         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL);
183
184         priv = GET_PRIV (list);
185
186         if (priv->contact_monitor == NULL) {
187                 priv->contact_monitor = empathy_contact_monitor_new_for_iface (list);
188         }
189
190         return priv->contact_monitor;
191 }
192
193 static void
194 tp_chat_emit_queued_messages (EmpathyTpChat *chat)
195 {
196         EmpathyTpChatPriv *priv = GET_PRIV (chat);
197         EmpathyMessage    *message;
198
199         /* Check if we can now emit some queued messages */
200         while ((message = g_queue_peek_head (priv->messages_queue)) != NULL) {
201                 if (empathy_message_get_sender (message) == NULL) {
202                         break;
203                 }
204
205                 DEBUG ("Queued message ready");
206                 g_queue_pop_head (priv->messages_queue);
207                 g_queue_push_tail (priv->pending_messages_queue, message);
208                 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
209         }
210 }
211
212 static void
213 tp_chat_got_sender_cb (EmpathyTpContactFactory *factory,
214                        GList                   *contacts,
215                        gpointer                 message,
216                        GObject                 *chat)
217 {
218         empathy_message_set_sender (message, contacts->data);
219         tp_chat_emit_queued_messages (EMPATHY_TP_CHAT (chat));
220 }
221
222 static void
223 tp_chat_build_message (EmpathyTpChat *chat,
224                        guint          id,
225                        guint          type,
226                        guint          timestamp,
227                        guint          from_handle,
228                        const gchar   *message_body)
229 {
230         EmpathyTpChatPriv *priv;
231         EmpathyMessage    *message;
232
233         priv = GET_PRIV (chat);
234
235         message = empathy_message_new (message_body);
236         empathy_message_set_tptype (message, type);
237         empathy_message_set_receiver (message, priv->user);
238         empathy_message_set_timestamp (message, timestamp);
239         empathy_message_set_id (message, id);
240         g_queue_push_tail (priv->messages_queue, message);
241
242         if (from_handle == 0) {
243                 empathy_message_set_sender (message, priv->user);
244                 tp_chat_emit_queued_messages (chat);
245         } else {
246                 empathy_tp_contact_factory_get_from_handles (priv->factory,
247                         1, &from_handle,
248                         tp_chat_got_sender_cb,
249                         message, NULL, G_OBJECT (chat));
250         }
251 }
252
253 static void
254 tp_chat_received_cb (TpChannel   *channel,
255                      guint        message_id,
256                      guint        timestamp,
257                      guint        from_handle,
258                      guint        message_type,
259                      guint        message_flags,
260                      const gchar *message_body,
261                      gpointer     user_data,
262                      GObject     *chat_)
263 {
264         EmpathyTpChat *chat = EMPATHY_TP_CHAT (chat_);
265         EmpathyTpChatPriv *priv = GET_PRIV (chat);
266
267         if (priv->channel == NULL)
268                 return;
269
270         if (priv->listing_pending_messages) {
271                 return;
272         }
273  
274         DEBUG ("Message received: %s", message_body);
275
276         if (message_flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT &&
277             !tp_strdiff (message_body, "")) {
278                 GArray *ids;
279
280                 DEBUG ("Empty message with NonTextContent, ignoring and acking.");
281
282                 ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
283                 g_array_append_val (ids, message_id);
284                 acknowledge_messages (chat, ids);
285                 g_array_free (ids, TRUE);
286
287                 return;
288         }
289
290         tp_chat_build_message (chat,
291                                message_id,
292                                message_type,
293                                timestamp,
294                                from_handle,
295                                message_body);
296 }
297
298 static void
299 tp_chat_sent_cb (TpChannel   *channel,
300                  guint        timestamp,
301                  guint        message_type,
302                  const gchar *message_body,
303                  gpointer     user_data,
304                  GObject     *chat_)
305 {
306         EmpathyTpChat *chat = EMPATHY_TP_CHAT (chat_);
307         EmpathyTpChatPriv *priv = GET_PRIV (chat);
308
309         if (priv->channel == NULL)
310                 return;
311
312         DEBUG ("Message sent: %s", message_body);
313
314         tp_chat_build_message (chat,
315                                0,
316                                message_type,
317                                timestamp,
318                                0,
319                                message_body);
320 }
321
322 static void
323 tp_chat_send_error_cb (TpChannel   *channel,
324                        guint        error_code,
325                        guint        timestamp,
326                        guint        message_type,
327                        const gchar *message_body,
328                        gpointer     user_data,
329                        GObject     *chat)
330 {
331         EmpathyTpChatPriv *priv = GET_PRIV (chat);
332
333         if (priv->channel == NULL)
334                 return;
335
336         DEBUG ("Message sent error: %s (%d)", message_body, error_code);
337
338         tp_chat_build_message (EMPATHY_TP_CHAT (chat),
339                                0,
340                                message_type,
341                                timestamp,
342                                0,
343                                message_body);
344 }
345
346 static void
347 tp_chat_send_cb (TpChannel    *proxy,
348                  const GError *error,
349                  gpointer      user_data,
350                  GObject      *chat)
351 {
352         EmpathyMessage *message = EMPATHY_MESSAGE (user_data);
353
354         if (error) {
355                 DEBUG ("Error: %s", error->message);
356                 g_signal_emit (chat, signals[SEND_ERROR], 0, message,
357                                TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN);
358         }
359 }
360
361 typedef struct {
362         EmpathyTpChat *chat;
363         TpChannelChatState state;
364 } StateChangedData;
365
366 static void
367 tp_chat_state_changed_got_contact_cb (EmpathyTpContactFactory *factory,
368                                       GList                   *contacts,
369                                       gpointer                 user_data,
370                                       GObject                 *chat)
371 {
372         EmpathyContact *contact = contacts->data;
373         TpChannelChatState state;
374
375         state = GPOINTER_TO_UINT (user_data);
376         DEBUG ("Chat state changed for %s (%d): %d",
377                 empathy_contact_get_name (contact),
378                 empathy_contact_get_handle (contact), state);
379
380         g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
381 }
382
383 static void
384 tp_chat_state_changed_cb (TpChannel *channel,
385                           TpHandle   handle,
386                           TpChannelChatState state,
387                           gpointer   user_data,
388                           GObject   *chat)
389 {
390         EmpathyTpChatPriv *priv = GET_PRIV (chat);
391
392         empathy_tp_contact_factory_get_from_handles (priv->factory, 1, &handle,
393                 tp_chat_state_changed_got_contact_cb, GUINT_TO_POINTER (state),
394                 NULL, chat);
395 }
396
397 static void
398 tp_chat_list_pending_messages_cb (TpChannel       *channel,
399                                   const GPtrArray *messages_list,
400                                   const GError    *error,
401                                   gpointer         user_data,
402                                   GObject         *chat_)
403 {
404         EmpathyTpChat     *chat = EMPATHY_TP_CHAT (chat_);
405         EmpathyTpChatPriv *priv = GET_PRIV (chat);
406         guint              i;
407         GArray            *empty_non_text_content_ids = NULL;
408
409         priv->listing_pending_messages = FALSE;
410
411         if (priv->channel == NULL)
412                 return;
413
414         if (error) {
415                 DEBUG ("Error listing pending messages: %s", error->message);
416                 return;
417         }
418
419         for (i = 0; i < messages_list->len; i++) {
420                 GValueArray    *message_struct;
421                 const gchar    *message_body;
422                 guint           message_id;
423                 guint           timestamp;
424                 guint           from_handle;
425                 guint           message_type;
426                 guint           message_flags;
427
428                 message_struct = g_ptr_array_index (messages_list, i);
429
430                 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
431                 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
432                 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
433                 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
434                 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
435                 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
436
437                 DEBUG ("Message pending: %s", message_body);
438
439                 if (message_flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT &&
440                     !tp_strdiff (message_body, "")) {
441                         DEBUG ("Empty message with NonTextContent, ignoring and acking.");
442
443                         if (empty_non_text_content_ids == NULL) {
444                                 empty_non_text_content_ids = g_array_new (FALSE, FALSE, sizeof (guint));
445                         }
446
447                         g_array_append_val (empty_non_text_content_ids, message_id);
448                         continue;
449                 }
450
451                 tp_chat_build_message (chat,
452                                        message_id,
453                                        message_type,
454                                        timestamp,
455                                        from_handle,
456                                        message_body);
457         }
458
459         if (empty_non_text_content_ids != NULL) {
460                 acknowledge_messages (chat, empty_non_text_content_ids);
461                 g_array_free (empty_non_text_content_ids, TRUE);
462         }
463 }
464
465 static void
466 tp_chat_property_flags_changed_cb (TpProxy         *proxy,
467                                    const GPtrArray *properties,
468                                    gpointer         user_data,
469                                    GObject         *chat)
470 {
471         EmpathyTpChatPriv *priv = GET_PRIV (chat);
472         guint              i, j;
473
474         if (priv->channel == NULL)
475                 return;
476
477         if (!priv->had_properties_list || !properties) {
478                 return;
479         }
480
481         for (i = 0; i < properties->len; i++) {
482                 GValueArray    *prop_struct;
483                 TpChatProperty *property;
484                 guint           id;
485                 guint           flags;
486
487                 prop_struct = g_ptr_array_index (properties, i);
488                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
489                 flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 1));
490
491                 for (j = 0; j < priv->properties->len; j++) {
492                         property = g_ptr_array_index (priv->properties, j);
493                         if (property->id == id) {
494                                 property->flags = flags;
495                                 DEBUG ("property %s flags changed: %d",
496                                         property->name, property->flags);
497                                 break;
498                         }
499                 }
500         }
501 }
502
503 static void
504 tp_chat_properties_changed_cb (TpProxy         *proxy,
505                                const GPtrArray *properties,
506                                gpointer         user_data,
507                                GObject         *chat)
508 {
509         EmpathyTpChatPriv *priv = GET_PRIV (chat);
510         guint              i, j;
511
512         if (priv->channel == NULL)
513                 return;
514
515         if (!priv->had_properties_list || !properties) {
516                 return;
517         }
518
519         for (i = 0; i < properties->len; i++) {
520                 GValueArray    *prop_struct;
521                 TpChatProperty *property;
522                 guint           id;
523                 GValue         *src_value;
524
525                 prop_struct = g_ptr_array_index (properties, i);
526                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
527                 src_value = g_value_get_boxed (g_value_array_get_nth (prop_struct, 1));
528
529                 for (j = 0; j < priv->properties->len; j++) {
530                         property = g_ptr_array_index (priv->properties, j);
531                         if (property->id == id) {
532                                 if (property->value) {
533                                         g_value_copy (src_value, property->value);
534                                 } else {
535                                         property->value = tp_g_value_slice_dup (src_value);
536                                 }
537
538                                 DEBUG ("property %s changed", property->name);
539                                 g_signal_emit (chat, signals[PROPERTY_CHANGED], 0,
540                                                property->name, property->value);
541                                 break;
542                         }
543                 }
544         }
545 }
546
547 static void
548 tp_chat_get_properties_cb (TpProxy         *proxy,
549                            const GPtrArray *properties,
550                            const GError    *error,
551                            gpointer         user_data,
552                            GObject         *chat)
553 {
554         if (error) {
555                 DEBUG ("Error getting properties: %s", error->message);
556                 return;
557         }
558
559         tp_chat_properties_changed_cb (proxy, properties, user_data, chat);
560 }
561
562 static void
563 tp_chat_list_properties_cb (TpProxy         *proxy,
564                             const GPtrArray *properties,
565                             const GError    *error,
566                             gpointer         user_data,
567                             GObject         *chat)
568 {
569         EmpathyTpChatPriv *priv = GET_PRIV (chat);
570         GArray            *ids;
571         guint              i;
572
573         if (priv->channel == NULL)
574                 return;
575
576         priv->had_properties_list = TRUE;
577
578         if (error) {
579                 DEBUG ("Error listing properties: %s", error->message);
580                 return;
581         }
582
583         ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), properties->len);
584         priv->properties = g_ptr_array_sized_new (properties->len);
585         for (i = 0; i < properties->len; i++) {
586                 GValueArray    *prop_struct;
587                 TpChatProperty *property;
588
589                 prop_struct = g_ptr_array_index (properties, i);
590                 property = g_slice_new0 (TpChatProperty);
591                 property->id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
592                 property->name = g_value_dup_string (g_value_array_get_nth (prop_struct, 1));
593                 property->flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 3));
594
595                 DEBUG ("Adding property name=%s id=%d flags=%d",
596                         property->name, property->id, property->flags);
597                 g_ptr_array_add (priv->properties, property);
598                 if (property->flags & TP_PROPERTY_FLAG_READ) {
599                         g_array_append_val (ids, property->id);
600                 }
601         }
602
603         tp_cli_properties_interface_call_get_properties (proxy, -1,
604                                                          ids,
605                                                          tp_chat_get_properties_cb,
606                                                          NULL, NULL,
607                                                          chat);
608
609         g_array_free (ids, TRUE);
610 }
611
612 void
613 empathy_tp_chat_set_property (EmpathyTpChat *chat,
614                               const gchar   *name,
615                               const GValue  *value)
616 {
617         EmpathyTpChatPriv *priv = GET_PRIV (chat);
618         TpChatProperty    *property;
619         guint              i;
620
621         for (i = 0; i < priv->properties->len; i++) {
622                 property = g_ptr_array_index (priv->properties, i);
623                 if (!tp_strdiff (property->name, name)) {
624                         GPtrArray   *properties;
625                         GValueArray *prop;
626                         GValue       id = {0, };
627                         GValue       dest_value = {0, };
628
629                         if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) {
630                                 break;
631                         }
632
633                         g_value_init (&id, G_TYPE_UINT);
634                         g_value_init (&dest_value, G_TYPE_VALUE);
635                         g_value_set_uint (&id, property->id);
636                         g_value_set_boxed (&dest_value, value);
637
638                         prop = g_value_array_new (2);
639                         g_value_array_append (prop, &id);
640                         g_value_array_append (prop, &dest_value);
641
642                         properties = g_ptr_array_sized_new (1);
643                         g_ptr_array_add (properties, prop);
644
645                         DEBUG ("Set property %s", name);
646                         tp_cli_properties_interface_call_set_properties (priv->channel, -1,
647                                                                          properties,
648                                                                          (tp_cli_properties_interface_callback_for_set_properties)
649                                                                          tp_chat_async_cb,
650                                                                          "Seting property", NULL,
651                                                                          G_OBJECT (chat));
652
653                         g_ptr_array_free (properties, TRUE);
654                         g_value_array_free (prop);
655
656                         break;
657                 }
658         }
659 }
660
661 static void
662 tp_chat_dispose (GObject *object)
663 {
664         EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
665         EmpathyTpChatPriv *priv = GET_PRIV (self);
666
667         if (priv->dispose_has_run)
668                 return;
669
670         priv->dispose_has_run = TRUE;
671
672         if (priv->channel != NULL) {
673                 g_signal_handlers_disconnect_by_func (priv->channel,
674                         tp_chat_invalidated_cb, self);
675                 g_object_unref (priv->channel);
676         }
677         priv->channel = NULL;
678
679         if (priv->remote_contact != NULL)
680                 g_object_unref (priv->remote_contact);
681         priv->remote_contact = NULL;
682
683         if (priv->factory != NULL)
684                 g_object_unref (priv->factory);
685         priv->factory = NULL;
686
687         if (priv->user != NULL);
688                 g_object_unref (priv->user);
689         priv->user = NULL;
690
691         if (priv->contact_monitor)
692                 g_object_unref (priv->contact_monitor);
693         priv->contact_monitor = NULL;
694
695         g_queue_foreach (priv->messages_queue, (GFunc) g_object_unref, NULL);
696         g_queue_clear (priv->messages_queue);
697
698         g_queue_foreach (priv->pending_messages_queue,
699                 (GFunc) g_object_unref, NULL);
700         g_queue_clear (priv->pending_messages_queue);
701
702         if (G_OBJECT_CLASS (empathy_tp_chat_parent_class)->dispose)
703                 G_OBJECT_CLASS (empathy_tp_chat_parent_class)->dispose (object);
704 }
705
706 static void
707 tp_chat_finalize (GObject *object)
708 {
709         EmpathyTpChatPriv *priv = GET_PRIV (object);
710         guint              i;
711
712         DEBUG ("Finalize: %p", object);
713
714         if (priv->properties) {
715                 for (i = 0; i < priv->properties->len; i++) {
716                         TpChatProperty *property;
717
718                         property = g_ptr_array_index (priv->properties, i);
719                         g_free (property->name);
720                         if (property->value) {
721                                 tp_g_value_slice_free (property->value);
722                         }
723                         g_slice_free (TpChatProperty, property);
724                 }
725                 g_ptr_array_free (priv->properties, TRUE);
726         }
727
728         g_queue_free (priv->messages_queue);
729         g_queue_free (priv->pending_messages_queue);
730
731         G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
732 }
733
734 static void
735 tp_chat_check_if_ready (EmpathyTpChat *chat)
736 {
737         EmpathyTpChatPriv *priv = GET_PRIV (chat);
738
739         if (priv->ready || priv->user == NULL ||
740             (priv->members == NULL && priv->remote_contact == NULL)) {
741                 return;
742         }
743
744         DEBUG ("Ready!");
745
746         priv->listing_pending_messages = TRUE;
747         tp_cli_channel_type_text_call_list_pending_messages (priv->channel, -1,
748                                                              FALSE,
749                                                              tp_chat_list_pending_messages_cb,
750                                                              NULL, NULL,
751                                                              G_OBJECT (chat));
752
753         tp_cli_channel_type_text_connect_to_received (priv->channel,
754                                                       tp_chat_received_cb,
755                                                       NULL, NULL,
756                                                       G_OBJECT (chat), NULL);
757         tp_cli_channel_type_text_connect_to_sent (priv->channel,
758                                                   tp_chat_sent_cb,
759                                                   NULL, NULL,
760                                                   G_OBJECT (chat), NULL);
761         tp_cli_channel_type_text_connect_to_send_error (priv->channel,
762                                                         tp_chat_send_error_cb,
763                                                         NULL, NULL,
764                                                         G_OBJECT (chat), NULL);
765         tp_cli_channel_interface_chat_state_connect_to_chat_state_changed (priv->channel,
766                                                                            tp_chat_state_changed_cb,
767                                                                            NULL, NULL,
768                                                                            G_OBJECT (chat), NULL);
769         tp_cli_channel_interface_chat_state_connect_to_chat_state_changed (priv->channel,
770                                                                            tp_chat_state_changed_cb,
771                                                                            NULL, NULL,
772                                                                            G_OBJECT (chat), NULL);
773         priv->ready = TRUE;
774         g_object_notify (G_OBJECT (chat), "ready");
775 }
776
777 static void
778 tp_chat_update_remote_contact (EmpathyTpChat *chat)
779 {
780         EmpathyTpChatPriv *priv = GET_PRIV (chat);
781         EmpathyContact *contact = NULL;
782         TpHandle self_handle;
783         TpHandleType handle_type;
784         GList *l;
785
786         /* If this is a named chatroom, never pretend it is a private chat */
787         tp_channel_get_handle (priv->channel, &handle_type);
788         if (handle_type == TP_HANDLE_TYPE_ROOM) {
789                 return;
790         }
791
792         /* This is an MSN-like chat where anyone can join the chat at anytime.
793          * If there is only one non-self contact member, we are in a private
794          * chat and we set the "remote-contact" property to that contact. If
795          * there are more, set the "remote-contact" property to NULL and the
796          * UI will display a contact list. */
797         self_handle = tp_channel_group_get_self_handle (priv->channel);
798         for (l = priv->members; l; l = l->next) {
799                 /* Skip self contact if member */
800                 if (empathy_contact_get_handle (l->data) == self_handle) {
801                         continue;
802                 }
803
804                 /* We have more than one remote contact, break */
805                 if (contact != NULL) {
806                         contact = NULL;
807                         break;
808                 }
809
810                 /* If we didn't find yet a remote contact, keep this one */
811                 contact = l->data;
812         }
813
814         if (priv->remote_contact == contact) {
815                 return;
816         }
817
818         DEBUG ("Changing remote contact from %p to %p",
819                 priv->remote_contact, contact);
820
821         if (priv->remote_contact) {
822                 g_object_unref (priv->remote_contact);
823         }
824
825         priv->remote_contact = contact ? g_object_ref (contact) : NULL;
826         g_object_notify (G_OBJECT (chat), "remote-contact");
827 }
828
829 static void
830 tp_chat_got_added_contacts_cb (EmpathyTpContactFactory *factory,
831                                GList                   *contacts,
832                                gpointer                 user_data,
833                                GObject                 *chat)
834 {
835         EmpathyTpChatPriv *priv = GET_PRIV (chat);
836         GList *l;
837         const TpIntSet *members;
838         TpHandle handle;
839         EmpathyContact *contact;
840
841         members = tp_channel_group_get_members (priv->channel);
842         for (l = contacts; l; l = l->next) {
843                 contact = l->data;
844                 handle = empathy_contact_get_handle (contact);
845
846                 /* Make sure the contact is still member */
847                 if (tp_intset_is_member (members, handle)) {
848                         priv->members = g_list_prepend (priv->members,
849                                 g_object_ref (contact));
850                         g_signal_emit_by_name (chat, "members-changed",
851                                                contact, NULL, 0, NULL, FALSE);
852                 }
853         }
854
855         tp_chat_update_remote_contact (EMPATHY_TP_CHAT (chat));
856         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
857 }
858
859 static void
860 tp_chat_group_members_changed_cb (TpChannel     *self,
861                                   gchar         *message,
862                                   GArray        *added,
863                                   GArray        *removed,
864                                   GArray        *local_pending,
865                                   GArray        *remote_pending,
866                                   guint          actor,
867                                   guint          reason,
868                                   EmpathyTpChat *chat)
869 {
870         EmpathyTpChatPriv *priv = GET_PRIV (chat);
871         EmpathyContact *contact;
872         TpHandle handle;
873         guint i;
874         GList *l;
875
876         /* Remove contacts that are not members anymore */
877         for (i = 0; i < removed->len; i++) {
878                 for (l = priv->members; l; l = l->next) {
879                         contact = l->data;
880                         handle = empathy_contact_get_handle (contact);
881                         if (handle == g_array_index (removed, TpHandle, i)) {
882                                 priv->members = g_list_delete_link (priv->members, l);
883                                 g_signal_emit_by_name (chat, "members-changed",
884                                                        contact, actor, reason,
885                                                        message, FALSE);
886                                 g_object_unref (contact);
887                                 break;
888                         }
889                 }
890         }
891
892         /* Request added contacts */
893         if (added->len > 0) {
894                 empathy_tp_contact_factory_get_from_handles (priv->factory,
895                         added->len, (TpHandle*) added->data,
896                         tp_chat_got_added_contacts_cb, NULL, NULL,
897                         G_OBJECT (chat));
898         }
899
900         tp_chat_update_remote_contact (chat);
901 }
902
903 static void
904 tp_chat_got_remote_contact_cb (EmpathyTpContactFactory *factory,
905                                GList *contacts,
906                                gpointer user_data,
907                                GObject *chat)
908 {
909         EmpathyTpChatPriv *priv = GET_PRIV (chat);
910
911         priv->remote_contact = g_object_ref (contacts->data);
912         g_object_notify (chat, "remote-contact");
913
914         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
915 }
916
917 static void
918 tp_chat_got_self_contact_cb (EmpathyTpContactFactory *factory,
919                              GList                   *contacts,
920                              gpointer                 user_data,
921                              GObject                 *chat)
922 {
923         EmpathyTpChatPriv *priv = GET_PRIV (chat);
924
925         priv->user = g_object_ref (contacts->data);
926         empathy_contact_set_is_user (priv->user, TRUE);
927         tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
928 }
929
930 static void
931 tp_chat_get_self_handle_cb (TpConnection *connection,
932                             TpHandle self_handle,
933                             const GError *error,
934                             gpointer user_data,
935                             GObject *chat)
936 {
937         EmpathyTpChatPriv *priv = GET_PRIV (chat);
938
939         if (error) {
940                 DEBUG ("Error: %s", error->message);
941                 tp_cli_channel_call_close (priv->channel, -1,
942                                            NULL, NULL, NULL, NULL);
943                 return;
944         }
945
946         empathy_tp_contact_factory_get_from_handles (priv->factory,
947                 1, &self_handle,
948                 tp_chat_got_self_contact_cb,
949                 NULL, NULL, chat);
950 }
951
952 static GObject *
953 tp_chat_constructor (GType                  type,
954                      guint                  n_props,
955                      GObjectConstructParam *props)
956 {
957         GObject           *chat;
958         EmpathyTpChatPriv *priv;
959         TpConnection      *connection;
960         TpHandle           handle;
961
962         chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props);
963
964         priv = GET_PRIV (chat);
965
966         connection = tp_channel_borrow_connection (priv->channel);
967         priv->factory = empathy_tp_contact_factory_dup_singleton (connection);
968         g_signal_connect (priv->channel, "invalidated",
969                           G_CALLBACK (tp_chat_invalidated_cb),
970                           chat);
971
972         if (tp_proxy_has_interface_by_id (priv->channel,
973                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
974                 const TpIntSet *members;
975                 GArray *handles;
976
977                 /* Get self contact from the group's self handle */
978                 handle = tp_channel_group_get_self_handle (priv->channel);
979                 empathy_tp_contact_factory_get_from_handles (priv->factory,
980                         1, &handle,
981                         tp_chat_got_self_contact_cb,
982                         NULL, NULL, chat);
983
984                 /* Get initial member contacts */
985                 members = tp_channel_group_get_members (priv->channel);
986                 handles = tp_intset_to_array (members);         
987                 empathy_tp_contact_factory_get_from_handles (priv->factory,
988                         handles->len, (TpHandle*) handles->data,
989                         tp_chat_got_added_contacts_cb, NULL, NULL, chat);
990
991                 g_signal_connect_swapped (priv->channel, "group-members-changed",
992                         G_CALLBACK (tp_chat_group_members_changed_cb), chat);
993         } else {
994                 /* Get the self contact from the connection's self handle */
995                 tp_cli_connection_call_get_self_handle (connection, -1,
996                         tp_chat_get_self_handle_cb, NULL, NULL, chat);
997
998                 /* Get the remote contact */
999                 handle = tp_channel_get_handle (priv->channel, NULL);
1000                 empathy_tp_contact_factory_get_from_handles (priv->factory,
1001                         1, &handle, tp_chat_got_remote_contact_cb,
1002                         NULL, NULL, chat);
1003         }
1004
1005         if (tp_proxy_has_interface_by_id (priv->channel,
1006                                           TP_IFACE_QUARK_PROPERTIES_INTERFACE)) {
1007                 tp_cli_properties_interface_call_list_properties (priv->channel, -1,
1008                                                                   tp_chat_list_properties_cb,
1009                                                                   NULL, NULL,
1010                                                                   G_OBJECT (chat));
1011                 tp_cli_properties_interface_connect_to_properties_changed (priv->channel,
1012                                                                            tp_chat_properties_changed_cb,
1013                                                                            NULL, NULL,
1014                                                                            G_OBJECT (chat), NULL);
1015                 tp_cli_properties_interface_connect_to_property_flags_changed (priv->channel,
1016                                                                                tp_chat_property_flags_changed_cb,
1017                                                                                NULL, NULL,
1018                                                                                G_OBJECT (chat), NULL);
1019         }
1020
1021         return chat;
1022 }
1023
1024 static void
1025 tp_chat_get_property (GObject    *object,
1026                       guint       param_id,
1027                       GValue     *value,
1028                       GParamSpec *pspec)
1029 {
1030         EmpathyTpChatPriv *priv = GET_PRIV (object);
1031
1032         switch (param_id) {
1033         case PROP_CHANNEL:
1034                 g_value_set_object (value, priv->channel);
1035                 break;
1036         case PROP_REMOTE_CONTACT:
1037                 g_value_set_object (value, priv->remote_contact);
1038                 break;
1039         case PROP_READY:
1040                 g_value_set_boolean (value, priv->ready);
1041                 break;
1042         default:
1043                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1044                 break;
1045         };
1046 }
1047
1048 static void
1049 tp_chat_set_property (GObject      *object,
1050                       guint         param_id,
1051                       const GValue *value,
1052                       GParamSpec   *pspec)
1053 {
1054         EmpathyTpChatPriv *priv = GET_PRIV (object);
1055
1056         switch (param_id) {
1057         case PROP_CHANNEL:
1058                 priv->channel = g_value_dup_object (value);
1059                 break;
1060         default:
1061                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1062                 break;
1063         };
1064 }
1065
1066 static void
1067 empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
1068 {
1069         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1070
1071         object_class->dispose = tp_chat_dispose;
1072         object_class->finalize = tp_chat_finalize;
1073         object_class->constructor = tp_chat_constructor;
1074         object_class->get_property = tp_chat_get_property;
1075         object_class->set_property = tp_chat_set_property;
1076
1077         g_object_class_install_property (object_class,
1078                                          PROP_CHANNEL,
1079                                          g_param_spec_object ("channel",
1080                                                               "telepathy channel",
1081                                                               "The text channel for the chat",
1082                                                               TP_TYPE_CHANNEL,
1083                                                               G_PARAM_READWRITE |
1084                                                               G_PARAM_CONSTRUCT_ONLY));
1085
1086         g_object_class_install_property (object_class,
1087                                          PROP_REMOTE_CONTACT,
1088                                          g_param_spec_object ("remote-contact",
1089                                                               "The remote contact",
1090                                                               "The remote contact if there is no group iface on the channel",
1091                                                               EMPATHY_TYPE_CONTACT,
1092                                                               G_PARAM_READABLE));
1093
1094         g_object_class_install_property (object_class,
1095                                          PROP_READY,
1096                                          g_param_spec_boolean ("ready",
1097                                                                "Is the object ready",
1098                                                                "This object can't be used until this becomes true",
1099                                                                FALSE,
1100                                                                G_PARAM_READABLE));
1101
1102         /* Signals */
1103         signals[MESSAGE_RECEIVED] =
1104                 g_signal_new ("message-received",
1105                               G_TYPE_FROM_CLASS (klass),
1106                               G_SIGNAL_RUN_LAST,
1107                               0,
1108                               NULL, NULL,
1109                               g_cclosure_marshal_VOID__OBJECT,
1110                               G_TYPE_NONE,
1111                               1, EMPATHY_TYPE_MESSAGE);
1112
1113         signals[SEND_ERROR] =
1114                 g_signal_new ("send-error",
1115                               G_TYPE_FROM_CLASS (klass),
1116                               G_SIGNAL_RUN_LAST,
1117                               0,
1118                               NULL, NULL,
1119                               _empathy_marshal_VOID__OBJECT_UINT,
1120                               G_TYPE_NONE,
1121                               2, EMPATHY_TYPE_MESSAGE, G_TYPE_UINT);
1122
1123         signals[CHAT_STATE_CHANGED] =
1124                 g_signal_new ("chat-state-changed",
1125                               G_TYPE_FROM_CLASS (klass),
1126                               G_SIGNAL_RUN_LAST,
1127                               0,
1128                               NULL, NULL,
1129                               _empathy_marshal_VOID__OBJECT_UINT,
1130                               G_TYPE_NONE,
1131                               2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT);
1132
1133         signals[PROPERTY_CHANGED] =
1134                 g_signal_new ("property-changed",
1135                               G_TYPE_FROM_CLASS (klass),
1136                               G_SIGNAL_RUN_LAST,
1137                               0,
1138                               NULL, NULL,
1139                               _empathy_marshal_VOID__STRING_BOXED,
1140                               G_TYPE_NONE,
1141                               2, G_TYPE_STRING, G_TYPE_VALUE);
1142
1143         signals[DESTROY] =
1144                 g_signal_new ("destroy",
1145                               G_TYPE_FROM_CLASS (klass),
1146                               G_SIGNAL_RUN_LAST,
1147                               0,
1148                               NULL, NULL,
1149                               g_cclosure_marshal_VOID__VOID,
1150                               G_TYPE_NONE,
1151                               0);
1152
1153         g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
1154 }
1155
1156 static void
1157 empathy_tp_chat_init (EmpathyTpChat *chat)
1158 {
1159         EmpathyTpChatPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chat,
1160                 EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv);
1161
1162         chat->priv = priv;
1163         priv->contact_monitor = NULL;
1164         priv->messages_queue = g_queue_new ();
1165         priv->pending_messages_queue = g_queue_new ();
1166 }
1167
1168 static void
1169 tp_chat_iface_init (EmpathyContactListIface *iface)
1170 {
1171         iface->add         = tp_chat_add;
1172         iface->remove      = tp_chat_remove;
1173         iface->get_members = tp_chat_get_members;
1174         iface->get_monitor = tp_chat_get_monitor;
1175 }
1176
1177 EmpathyTpChat *
1178 empathy_tp_chat_new (TpChannel *channel)
1179 {
1180         return g_object_new (EMPATHY_TYPE_TP_CHAT,
1181                              "channel", channel,
1182                              NULL);
1183 }
1184
1185 void
1186 empathy_tp_chat_close (EmpathyTpChat *chat) {
1187         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1188
1189         /* If there are still messages left, it'll come back..
1190            We loose the ordering of sent messages though */
1191         g_signal_handlers_disconnect_by_func (priv->channel,
1192                 tp_chat_invalidated_cb, chat);
1193
1194         tp_cli_channel_call_close (priv->channel, -1, tp_chat_async_cb,
1195                 "closing channel", NULL, NULL);
1196
1197         g_object_unref (priv->channel);
1198         priv->channel = NULL;
1199
1200         g_signal_emit (chat, signals[DESTROY], 0);
1201 }
1202
1203 const gchar *
1204 empathy_tp_chat_get_id (EmpathyTpChat *chat)
1205 {
1206         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1207
1208         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1209
1210         return tp_channel_get_identifier (priv->channel);
1211 }
1212
1213 EmpathyContact *
1214 empathy_tp_chat_get_remote_contact (EmpathyTpChat *chat)
1215 {
1216         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1217
1218         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1219         g_return_val_if_fail (priv->ready, NULL);
1220
1221         return priv->remote_contact;
1222 }
1223
1224 TpChannel *
1225 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
1226 {
1227         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1228
1229         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1230
1231         return priv->channel;
1232 }
1233
1234 gboolean
1235 empathy_tp_chat_is_ready (EmpathyTpChat *chat)
1236 {
1237         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1238
1239         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
1240
1241         return priv->ready;
1242 }
1243
1244 void
1245 empathy_tp_chat_send (EmpathyTpChat *chat,
1246                       EmpathyMessage *message)
1247 {
1248         EmpathyTpChatPriv        *priv = GET_PRIV (chat);
1249         const gchar              *message_body;
1250         TpChannelTextMessageType  message_type;
1251
1252         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1253         g_return_if_fail (EMPATHY_IS_MESSAGE (message));
1254         g_return_if_fail (priv->ready);
1255
1256         message_body = empathy_message_get_body (message);
1257         message_type = empathy_message_get_tptype (message);
1258
1259         DEBUG ("Sending message: %s", message_body);
1260         tp_cli_channel_type_text_call_send (priv->channel, -1,
1261                                             message_type,
1262                                             message_body,
1263                                             tp_chat_send_cb,
1264                                             g_object_ref (message),
1265                                             (GDestroyNotify) g_object_unref,
1266                                             G_OBJECT (chat));
1267 }
1268
1269 void
1270 empathy_tp_chat_set_state (EmpathyTpChat      *chat,
1271                            TpChannelChatState  state)
1272 {
1273         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1274
1275         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1276         g_return_if_fail (priv->ready);
1277
1278         DEBUG ("Set state: %d", state);
1279         tp_cli_channel_interface_chat_state_call_set_chat_state (priv->channel, -1,
1280                                                                  state,
1281                                                                  tp_chat_async_cb,
1282                                                                  "setting chat state",
1283                                                                  NULL,
1284                                                                  G_OBJECT (chat));
1285 }
1286
1287
1288 const GList *
1289 empathy_tp_chat_get_pending_messages (EmpathyTpChat *chat)
1290 {
1291         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1292
1293         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1294         g_return_val_if_fail (priv->ready, NULL);
1295
1296         return priv->pending_messages_queue->head;
1297 }
1298
1299 static void
1300 acknowledge_messages (EmpathyTpChat *chat, GArray *ids) {
1301         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1302
1303         tp_cli_channel_type_text_call_acknowledge_pending_messages (
1304                 priv->channel, -1, ids, tp_chat_async_cb,
1305                 "acknowledging received message", NULL, G_OBJECT (chat));
1306 }
1307
1308 void
1309 empathy_tp_chat_acknowledge_message (EmpathyTpChat *chat,
1310                                      EmpathyMessage *message) {
1311         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1312         GArray *message_ids;
1313         GList *m;
1314         guint id;
1315
1316         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1317         g_return_if_fail (priv->ready);
1318
1319         if (empathy_message_get_sender (message) == priv->user)
1320                 goto out;
1321
1322         message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
1323
1324         id = empathy_message_get_id (message);
1325         g_array_append_val (message_ids, id);
1326         acknowledge_messages (chat, message_ids);
1327         g_array_free (message_ids, TRUE);
1328
1329 out:
1330         m = g_queue_find (priv->pending_messages_queue, message);
1331         g_assert (m != NULL);
1332         g_queue_delete_link (priv->pending_messages_queue, m);
1333         g_object_unref (message);
1334 }
1335
1336 void
1337 empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat,
1338                                       const GList *messages) {
1339         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1340         /* Copy messages as the messges list (probably is) our own */
1341         GList *msgs = g_list_copy ((GList *) messages);
1342         GList *l;
1343         guint length;
1344         GArray *message_ids;
1345
1346         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1347         g_return_if_fail (priv->ready);
1348
1349         length = g_list_length ((GList *)messages);
1350
1351         if (length == 0)
1352                 return;
1353
1354         message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), length);
1355
1356         for (l = msgs; l != NULL; l = g_list_next (l)) {
1357                 GList *m;
1358
1359                 EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
1360
1361                 m = g_queue_find (priv->pending_messages_queue, message);
1362                 g_assert (m != NULL);
1363                 g_queue_delete_link (priv->pending_messages_queue, m);
1364
1365                 if (empathy_message_get_sender (message) != priv->user) {
1366                         guint id = empathy_message_get_id (message);
1367                         g_array_append_val (message_ids, id);
1368                 }
1369                 g_object_unref (message);
1370         }
1371
1372         if (message_ids->len > 0)
1373                 acknowledge_messages (chat, message_ids);
1374
1375         g_array_free (message_ids, TRUE);
1376         g_list_free (msgs);
1377 }
1378