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