]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-chat.c
Improve API of tp-chat
[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-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         EmpathyContactFactory *factory;
45         EmpathyContactMonitor *contact_monitor;
46         EmpathyContact        *user;
47         EmpathyContact        *remote_contact;
48         EmpathyTpGroup        *group;
49         McAccount             *account;
50         TpChannel             *channel;
51         gchar                 *id;
52         gboolean               listing_pending_messages;
53         /* Queue of messages not signalled yet */
54         GQueue                *messages_queue;
55         /* Queue of messages signalled but not acked yet */
56         GQueue                *pending_messages_queue;
57         gboolean               had_properties_list;
58         GPtrArray             *properties;
59         gboolean               ready;
60         guint                  members_count;
61         guint                  invalidated_handler;
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         DEBUG ("Channel invalidated: %s", message);
104         g_signal_emit (chat, signals[DESTROY], 0);
105
106         g_object_unref (priv->channel);
107         priv->channel = NULL;
108 }
109
110 static void
111 tp_chat_async_cb (TpChannel *proxy,
112                   const GError *error,
113                   gpointer user_data,
114                   GObject *weak_object)
115 {
116         if (error) {
117                 DEBUG ("Error %s: %s", (gchar*) user_data, error->message);
118         }
119 }
120
121 static void
122 tp_chat_member_added_cb (EmpathyTpGroup *group,
123                          EmpathyContact *contact,
124                          EmpathyContact *actor,
125                          guint           reason,
126                          const gchar    *message,
127                          EmpathyTpChat  *chat)
128 {
129         EmpathyTpChatPriv *priv = GET_PRIV (chat);
130         guint              handle_type = 0;
131
132         if (priv->channel == NULL)
133                 return;
134
135         priv->members_count++;
136         g_signal_emit_by_name (chat, "members-changed",
137                                contact, actor, reason, message,
138                                TRUE);
139
140         g_object_get (priv->channel, "handle-type", &handle_type, NULL);
141         if (handle_type == TP_HANDLE_TYPE_ROOM) {
142                 return;
143         }
144
145         if (priv->members_count > 2 && priv->remote_contact) {
146                 /* We now have more than 2 members, this is not a p2p chat
147                  * anymore. Remove the remote-contact as it makes no sense, the
148                  * EmpathyContactList interface must be used now. */
149                 g_object_unref (priv->remote_contact);
150                 priv->remote_contact = NULL;
151                 g_object_notify (G_OBJECT (chat), "remote-contact");
152         }
153         if (priv->members_count <= 2 && !priv->remote_contact &&
154             !empathy_contact_is_user (contact)) {
155                 /* This is a p2p chat, if it's not ourself that means this is
156                  * the remote contact with who we are chatting. This is to
157                  * avoid forcing the usage of the EmpathyContactList interface
158                  * for p2p chats. */
159                 priv->remote_contact = g_object_ref (contact);
160                 g_object_notify (G_OBJECT (chat), "remote-contact");
161         }
162 }
163
164 static void
165 tp_chat_member_removed_cb (EmpathyTpGroup *group,
166                            EmpathyContact *contact,
167                            EmpathyContact *actor,
168                            guint           reason,
169                            const gchar    *message,
170                            EmpathyTpChat  *chat)
171 {
172         EmpathyTpChatPriv *priv = GET_PRIV (chat);
173         guint              handle_type = 0;
174
175         if (priv->channel == NULL)
176                 return;
177
178         priv->members_count--;
179         g_signal_emit_by_name (chat, "members-changed",
180                                contact, actor, reason, message,
181                                FALSE);
182
183         g_object_get (priv->channel, "handle-type", &handle_type, NULL);
184         if (handle_type == TP_HANDLE_TYPE_ROOM) {
185                 return;
186         }
187
188         if (priv->members_count <= 2 && !priv->remote_contact) {
189                 GList *members, *l;
190
191                 /* We are not a MUC anymore, get the remote contact back */
192                 members = empathy_tp_group_get_members (group);
193                 for (l = members; l; l = l->next) {
194                         if (!empathy_contact_is_user (l->data)) {
195                                 priv->remote_contact = g_object_ref (l->data);
196                                 g_object_notify (G_OBJECT (chat), "remote-contact");
197                                 break;
198                         }
199                 }
200                 g_list_foreach (members, (GFunc) g_object_unref, NULL);
201                 g_list_free (members);
202         }
203 }
204
205 static void
206 tp_chat_local_pending_cb  (EmpathyTpGroup *group,
207                            EmpathyContact *contact,
208                            EmpathyContact *actor,
209                            guint           reason,
210                            const gchar    *message,
211                            EmpathyTpChat  *chat)
212 {
213         EmpathyTpChatPriv *priv = GET_PRIV (chat);
214
215         if (priv->channel == NULL)
216                 return;
217
218         g_signal_emit_by_name (chat, "pendings-changed",
219                                contact, actor, reason, message,
220                                TRUE);
221 }
222
223 static void
224 tp_chat_add (EmpathyContactList *list,
225              EmpathyContact     *contact,
226              const gchar        *message)
227 {
228         EmpathyTpChatPriv *priv = GET_PRIV (list);
229
230         g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
231         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
232
233         if (priv->group) {
234                 empathy_tp_group_add_member (priv->group, contact, message);
235         }
236 }
237
238 static void
239 tp_chat_remove (EmpathyContactList *list,
240                 EmpathyContact     *contact,
241                 const gchar        *message)
242 {
243         EmpathyTpChatPriv *priv = GET_PRIV (list);
244
245         g_return_if_fail (EMPATHY_IS_TP_CHAT (list));
246         g_return_if_fail (EMPATHY_IS_CONTACT (contact));
247
248         if (priv->group) {
249                 empathy_tp_group_remove_member (priv->group, contact, message);
250         }
251 }
252
253 static GList *
254 tp_chat_get_members (EmpathyContactList *list)
255 {
256         EmpathyTpChatPriv *priv = GET_PRIV (list);
257         GList             *members = NULL;
258
259         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL);
260
261         if (priv->group) {
262                 members = empathy_tp_group_get_members (priv->group);
263         } else {
264                 members = g_list_prepend (members, g_object_ref (priv->user));
265                 members = g_list_prepend (members, g_object_ref (priv->remote_contact));
266         }
267
268         return members;
269 }
270
271 static EmpathyContactMonitor *
272 tp_chat_get_monitor (EmpathyContactList *list)
273 {
274         EmpathyTpChatPriv *priv;
275
276         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL);
277
278         priv = GET_PRIV (list);
279
280         if (priv->contact_monitor == NULL) {
281                 priv->contact_monitor = empathy_contact_monitor_new_for_iface (list);
282         }
283
284         return priv->contact_monitor;
285 }
286
287 static EmpathyMessage *
288 tp_chat_build_message (EmpathyTpChat *chat,
289                        guint          id,
290                        guint          type,
291                        guint          timestamp,
292                        guint          from_handle,
293                        const gchar   *message_body)
294 {
295         EmpathyTpChatPriv *priv;
296         EmpathyMessage    *message;
297         EmpathyContact    *sender;
298
299         priv = GET_PRIV (chat);
300
301         if (from_handle == 0) {
302                 sender = g_object_ref (priv->user);
303         } else {
304                 sender = empathy_contact_factory_get_from_handle (priv->factory,
305                                                                   priv->account,
306                                                                   from_handle);
307         }
308
309         message = empathy_message_new (message_body);
310         empathy_message_set_tptype (message, type);
311         empathy_message_set_sender (message, sender);
312         empathy_message_set_receiver (message, priv->user);
313         empathy_message_set_timestamp (message, timestamp);
314         empathy_message_set_id (message, id);
315
316         g_object_unref (sender);
317
318         return message;
319 }
320
321 static void
322 tp_chat_sender_ready_notify_cb (EmpathyContact *contact,
323                                 GParamSpec     *param_spec,
324                                 EmpathyTpChat  *chat)
325 {
326         EmpathyTpChatPriv   *priv = GET_PRIV (chat);
327         EmpathyMessage      *message;
328         EmpathyContactReady  ready;
329         EmpathyContact      *sender = NULL;
330         gboolean             removed = FALSE;
331
332         /* Emit all messages queued until we find a message with not
333          * ready sender (in case of a MUC we could have more than one sender).
334          * When leaving this loop, sender is the first not ready contact queued
335          * and removed tells if at least one message got removed
336          * from the queue. */
337         while ((message = g_queue_peek_head (priv->messages_queue)) != NULL) {
338                 sender = empathy_message_get_sender (message);
339                 ready = empathy_contact_get_ready (sender);
340
341                 if ((ready & EMPATHY_CONTACT_READY_NAME) == 0 ||
342                     (ready & EMPATHY_CONTACT_READY_ID) == 0) {
343                         break;
344                 }
345
346                 DEBUG ("Queued message ready");
347                 message = g_queue_pop_head (priv->messages_queue);
348                 g_queue_push_tail (priv->pending_messages_queue, message);
349                 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
350                 removed = TRUE;
351         }
352
353         if (removed) {
354                 /* We removed at least one message from the queue, disconnect
355                  * the ready signal from the previous contact */
356                 g_signal_handlers_disconnect_by_func (contact,
357                                                       tp_chat_sender_ready_notify_cb,
358                                                       chat);
359
360                 if (g_queue_get_length (priv->messages_queue) > 0) {
361                         /* We still have queued message, connect the ready
362                          * signal on the new first message sender. */
363                         g_signal_connect (sender, "notify::ready",
364                                           G_CALLBACK (tp_chat_sender_ready_notify_cb),
365                                           chat);
366                 }
367         }
368 }
369
370 static void
371 tp_chat_emit_or_queue_message (EmpathyTpChat  *chat,
372                                EmpathyMessage *message)
373 {
374         EmpathyTpChatPriv   *priv = GET_PRIV (chat);
375         EmpathyContact      *sender;
376         EmpathyContactReady  ready;
377
378         if (g_queue_get_length (priv->messages_queue) > 0) {
379                 DEBUG ("Message queue not empty");
380                 g_queue_push_tail (priv->messages_queue, g_object_ref (message));
381                 return;
382         }
383
384
385         sender = empathy_message_get_sender (message);
386         ready = empathy_contact_get_ready (sender);
387         if ((ready & EMPATHY_CONTACT_READY_NAME) &&
388             (ready & EMPATHY_CONTACT_READY_ID)) {
389                 DEBUG ("Message queue empty and sender ready");
390                 g_queue_push_tail (priv->pending_messages_queue, g_object_ref (message));
391                 g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
392                 return;
393         }
394
395         DEBUG ("Sender not ready");
396         g_queue_push_tail (priv->messages_queue, g_object_ref (message));
397         g_signal_connect (sender, "notify::ready",
398                           G_CALLBACK (tp_chat_sender_ready_notify_cb),
399                           chat);
400 }
401
402 static void
403 tp_chat_received_cb (TpChannel   *channel,
404                      guint        message_id,
405                      guint        timestamp,
406                      guint        from_handle,
407                      guint        message_type,
408                      guint        message_flags,
409                      const gchar *message_body,
410                      gpointer     user_data,
411                      GObject     *chat)
412 {
413         EmpathyTpChatPriv *priv = GET_PRIV (chat);
414         EmpathyMessage    *message;
415
416         if (priv->channel == NULL)
417                 return;
418
419         if (priv->listing_pending_messages) {
420                 return;
421         }
422  
423         DEBUG ("Message received: %s", message_body);
424
425         message = tp_chat_build_message (EMPATHY_TP_CHAT (chat),
426                                          message_id,
427                                          message_type,
428                                          timestamp,
429                                          from_handle,
430                                          message_body);
431
432         tp_chat_emit_or_queue_message (EMPATHY_TP_CHAT (chat), message);
433         g_object_unref (message);
434 }
435
436 static void
437 tp_chat_sent_cb (TpChannel   *channel,
438                  guint        timestamp,
439                  guint        message_type,
440                  const gchar *message_body,
441                  gpointer     user_data,
442                  GObject     *chat)
443 {
444         EmpathyTpChatPriv *priv = GET_PRIV (chat);
445         EmpathyMessage *message;
446
447         if (priv->channel == NULL)
448                 return;
449
450         DEBUG ("Message sent: %s", message_body);
451
452         message = tp_chat_build_message (EMPATHY_TP_CHAT (chat),
453                                          0,
454                                          message_type,
455                                          timestamp,
456                                          0,
457                                          message_body);
458
459         tp_chat_emit_or_queue_message (EMPATHY_TP_CHAT (chat), message);
460         g_object_unref (message);
461 }
462
463 static void
464 tp_chat_send_error_cb (TpChannel   *channel,
465                        guint        error_code,
466                        guint        timestamp,
467                        guint        message_type,
468                        const gchar *message_body,
469                        gpointer     user_data,
470                        GObject     *chat)
471 {
472         EmpathyMessage *message;
473         EmpathyTpChatPriv *priv = GET_PRIV (chat);
474
475         if (priv->channel == NULL)
476                 return;
477
478         DEBUG ("Message sent error: %s (%d)", message_body, error_code);
479
480         message = tp_chat_build_message (EMPATHY_TP_CHAT (chat),
481                                          0,
482                                          message_type,
483                                          timestamp,
484                                          0,
485                                          message_body);
486
487         g_signal_emit (chat, signals[SEND_ERROR], 0, message, error_code);
488         g_object_unref (message);
489 }
490
491 static void
492 tp_chat_send_cb (TpChannel    *proxy,
493                  const GError *error,
494                  gpointer      user_data,
495                  GObject      *chat)
496 {
497         EmpathyMessage *message = EMPATHY_MESSAGE (user_data);
498
499         if (error) {
500                 DEBUG ("Error: %s", error->message);
501                 g_signal_emit (chat, signals[SEND_ERROR], 0, message,
502                                TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN);
503         }
504 }
505
506 static void
507 tp_chat_state_changed_cb (TpChannel *channel,
508                           guint      handle,
509                           guint      state,
510                           gpointer   user_data,
511                           GObject   *chat)
512 {
513         EmpathyTpChatPriv *priv = GET_PRIV (chat);
514         EmpathyContact    *contact;
515
516         if (priv->channel == NULL)
517                 return;
518
519         contact = empathy_contact_factory_get_from_handle (priv->factory,
520                                                            priv->account,
521                                                            handle);
522
523         DEBUG ("Chat state changed for %s (%d): %d",
524                 empathy_contact_get_name (contact), handle, state);
525
526         g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
527         g_object_unref (contact);
528 }
529
530 static void
531 tp_chat_list_pending_messages_cb (TpChannel       *channel,
532                                   const GPtrArray *messages_list,
533                                   const GError    *error,
534                                   gpointer         user_data,
535                                   GObject         *chat)
536 {
537         EmpathyTpChatPriv *priv = GET_PRIV (chat);
538         guint              i;
539
540         priv->listing_pending_messages = FALSE;
541
542         if (priv->channel == NULL)
543                 return;
544
545         if (error) {
546                 DEBUG ("Error listing pending messages: %s", error->message);
547                 return;
548         }
549
550         for (i = 0; i < messages_list->len; i++) {
551                 EmpathyMessage *message;
552                 GValueArray    *message_struct;
553                 const gchar    *message_body;
554                 guint           message_id;
555                 guint           timestamp;
556                 guint           from_handle;
557                 guint           message_type;
558                 guint           message_flags;
559
560                 message_struct = g_ptr_array_index (messages_list, i);
561
562                 message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
563                 timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
564                 from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
565                 message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
566                 message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
567                 message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
568
569                 DEBUG ("Message pending: %s", message_body);
570
571                 message = tp_chat_build_message (EMPATHY_TP_CHAT (chat),
572                                                  message_id,
573                                                  message_type,
574                                                  timestamp,
575                                                  from_handle,
576                                                  message_body);
577
578                 tp_chat_emit_or_queue_message (EMPATHY_TP_CHAT (chat), message);
579                 g_object_unref (message);
580         }
581 }
582
583 static void
584 tp_chat_property_flags_changed_cb (TpProxy         *proxy,
585                                    const GPtrArray *properties,
586                                    gpointer         user_data,
587                                    GObject         *chat)
588 {
589         EmpathyTpChatPriv *priv = GET_PRIV (chat);
590         guint              i, j;
591
592         if (priv->channel == NULL)
593                 return;
594
595         if (!priv->had_properties_list || !properties) {
596                 return;
597         }
598
599         for (i = 0; i < properties->len; i++) {
600                 GValueArray    *prop_struct;
601                 TpChatProperty *property;
602                 guint           id;
603                 guint           flags;
604
605                 prop_struct = g_ptr_array_index (properties, i);
606                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
607                 flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 1));
608
609                 for (j = 0; j < priv->properties->len; j++) {
610                         property = g_ptr_array_index (priv->properties, j);
611                         if (property->id == id) {
612                                 property->flags = flags;
613                                 DEBUG ("property %s flags changed: %d",
614                                         property->name, property->flags);
615                                 break;
616                         }
617                 }
618         }
619 }
620
621 static void
622 tp_chat_properties_changed_cb (TpProxy         *proxy,
623                                const GPtrArray *properties,
624                                gpointer         user_data,
625                                GObject         *chat)
626 {
627         EmpathyTpChatPriv *priv = GET_PRIV (chat);
628         guint              i, j;
629
630         if (priv->channel == NULL)
631                 return;
632
633         if (!priv->had_properties_list || !properties) {
634                 return;
635         }
636
637         for (i = 0; i < properties->len; i++) {
638                 GValueArray    *prop_struct;
639                 TpChatProperty *property;
640                 guint           id;
641                 GValue         *src_value;
642
643                 prop_struct = g_ptr_array_index (properties, i);
644                 id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
645                 src_value = g_value_get_boxed (g_value_array_get_nth (prop_struct, 1));
646
647                 for (j = 0; j < priv->properties->len; j++) {
648                         property = g_ptr_array_index (priv->properties, j);
649                         if (property->id == id) {
650                                 if (property->value) {
651                                         g_value_copy (src_value, property->value);
652                                 } else {
653                                         property->value = tp_g_value_slice_dup (src_value);
654                                 }
655
656                                 DEBUG ("property %s changed", property->name);
657                                 g_signal_emit (chat, signals[PROPERTY_CHANGED], 0,
658                                                property->name, property->value);
659                                 break;
660                         }
661                 }
662         }
663 }
664
665 static void
666 tp_chat_get_properties_cb (TpProxy         *proxy,
667                            const GPtrArray *properties,
668                            const GError    *error,
669                            gpointer         user_data,
670                            GObject         *chat)
671 {
672         if (error) {
673                 DEBUG ("Error getting properties: %s", error->message);
674                 return;
675         }
676
677         tp_chat_properties_changed_cb (proxy, properties, user_data, chat);
678 }
679
680 static void
681 tp_chat_list_properties_cb (TpProxy         *proxy,
682                             const GPtrArray *properties,
683                             const GError    *error,
684                             gpointer         user_data,
685                             GObject         *chat)
686 {
687         EmpathyTpChatPriv *priv = GET_PRIV (chat);
688         GArray            *ids;
689         guint              i;
690
691         if (priv->channel == NULL)
692                 return;
693
694         priv->had_properties_list = TRUE;
695
696         if (error) {
697                 DEBUG ("Error listing properties: %s", error->message);
698                 return;
699         }
700
701         ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), properties->len);
702         priv->properties = g_ptr_array_sized_new (properties->len);
703         for (i = 0; i < properties->len; i++) {
704                 GValueArray    *prop_struct;
705                 TpChatProperty *property;
706
707                 prop_struct = g_ptr_array_index (properties, i);
708                 property = g_slice_new0 (TpChatProperty);
709                 property->id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0));
710                 property->name = g_value_dup_string (g_value_array_get_nth (prop_struct, 1));
711                 property->flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 3));
712
713                 DEBUG ("Adding property name=%s id=%d flags=%d",
714                         property->name, property->id, property->flags);
715                 g_ptr_array_add (priv->properties, property);
716                 if (property->flags & TP_PROPERTY_FLAG_READ) {
717                         g_array_append_val (ids, property->id);
718                 }
719         }
720
721         tp_cli_properties_interface_call_get_properties (proxy, -1,
722                                                          ids,
723                                                          tp_chat_get_properties_cb,
724                                                          NULL, NULL,
725                                                          chat);
726
727         g_array_free (ids, TRUE);
728 }
729
730 void
731 empathy_tp_chat_set_property (EmpathyTpChat *chat,
732                               const gchar   *name,
733                               const GValue  *value)
734 {
735         EmpathyTpChatPriv *priv = GET_PRIV (chat);
736         TpChatProperty    *property;
737         guint              i;
738
739         g_return_if_fail (priv->ready);
740
741         for (i = 0; i < priv->properties->len; i++) {
742                 property = g_ptr_array_index (priv->properties, i);
743                 if (!tp_strdiff (property->name, name)) {
744                         GPtrArray   *properties;
745                         GValueArray *prop;
746                         GValue       id = {0, };
747                         GValue       dest_value = {0, };
748
749                         if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) {
750                                 break;
751                         }
752
753                         g_value_init (&id, G_TYPE_UINT);
754                         g_value_init (&dest_value, G_TYPE_VALUE);
755                         g_value_set_uint (&id, property->id);
756                         g_value_set_boxed (&dest_value, value);
757
758                         prop = g_value_array_new (2);
759                         g_value_array_append (prop, &id);
760                         g_value_array_append (prop, &dest_value);
761
762                         properties = g_ptr_array_sized_new (1);
763                         g_ptr_array_add (properties, prop);
764
765                         DEBUG ("Set property %s", name);
766                         tp_cli_properties_interface_call_set_properties (priv->channel, -1,
767                                                                          properties,
768                                                                          (tp_cli_properties_interface_callback_for_set_properties)
769                                                                          tp_chat_async_cb,
770                                                                          "Seting property", NULL,
771                                                                          G_OBJECT (chat));
772
773                         g_ptr_array_free (properties, TRUE);
774                         g_value_array_free (prop);
775
776                         break;
777                 }
778         }
779 }
780
781 static void
782 tp_chat_channel_ready_cb (EmpathyTpChat *chat)
783 {
784         EmpathyTpChatPriv *priv = GET_PRIV (chat);
785         TpConnection      *connection;
786         guint              handle, handle_type;
787
788         if (priv->channel == NULL)
789                 return;
790
791         DEBUG ("Channel ready");
792
793         g_object_get (priv->channel,
794                       "connection", &connection,
795                       "handle", &handle,
796                       "handle_type", &handle_type,
797                       NULL);
798
799         if (handle_type != TP_HANDLE_TYPE_NONE && handle != 0) {
800                 GArray *handles;
801                 gchar **names;
802
803                 handles = g_array_new (FALSE, FALSE, sizeof (guint));
804                 g_array_append_val (handles, handle);
805                 tp_cli_connection_run_inspect_handles (connection, -1,
806                                                        handle_type, handles,
807                                                        &names, NULL, NULL);
808                 priv->id = *names;
809                 g_array_free (handles, TRUE);
810                 g_free (names);
811         }
812
813         if (handle_type == TP_HANDLE_TYPE_CONTACT && handle != 0) {
814                 priv->remote_contact = empathy_contact_factory_get_from_handle (priv->factory,
815                                                                                 priv->account,
816                                                                                 handle);
817                 g_object_notify (G_OBJECT (chat), "remote-contact");
818         }
819
820         if (tp_proxy_has_interface_by_id (priv->channel,
821                                           TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
822                 priv->group = empathy_tp_group_new (priv->channel);
823
824                 g_signal_connect (priv->group, "member-added",
825                                   G_CALLBACK (tp_chat_member_added_cb),
826                                   chat);
827                 g_signal_connect (priv->group, "member-removed",
828                                   G_CALLBACK (tp_chat_member_removed_cb),
829                                   chat);
830                 g_signal_connect (priv->group, "local-pending",
831                                   G_CALLBACK (tp_chat_local_pending_cb),
832                                   chat);
833                 empathy_run_until_ready (priv->group);
834         } else {
835                 priv->members_count = 2;
836         }
837         
838         if (tp_proxy_has_interface_by_id (priv->channel,
839                                           TP_IFACE_QUARK_PROPERTIES_INTERFACE)) {
840                 tp_cli_properties_interface_call_list_properties (priv->channel, -1,
841                                                                   tp_chat_list_properties_cb,
842                                                                   NULL, NULL,
843                                                                   G_OBJECT (chat));
844                 tp_cli_properties_interface_connect_to_properties_changed (priv->channel,
845                                                                            tp_chat_properties_changed_cb,
846                                                                            NULL, NULL,
847                                                                            G_OBJECT (chat), NULL);
848                 tp_cli_properties_interface_connect_to_property_flags_changed (priv->channel,
849                                                                                tp_chat_property_flags_changed_cb,
850                                                                                NULL, NULL,
851                                                                                G_OBJECT (chat), NULL);
852         }
853
854         priv->listing_pending_messages = TRUE;
855         tp_cli_channel_type_text_call_list_pending_messages (priv->channel, -1,
856                                                              FALSE,
857                                                              tp_chat_list_pending_messages_cb,
858                                                              NULL, NULL,
859                                                              G_OBJECT (chat));
860
861         tp_cli_channel_type_text_connect_to_received (priv->channel,
862                                                       tp_chat_received_cb,
863                                                       NULL, NULL,
864                                                       G_OBJECT (chat), NULL);
865         tp_cli_channel_type_text_connect_to_sent (priv->channel,
866                                                   tp_chat_sent_cb,
867                                                   NULL, NULL,
868                                                   G_OBJECT (chat), NULL);
869         tp_cli_channel_type_text_connect_to_send_error (priv->channel,
870                                                         tp_chat_send_error_cb,
871                                                         NULL, NULL,
872                                                         G_OBJECT (chat), NULL);
873         tp_cli_channel_interface_chat_state_connect_to_chat_state_changed (priv->channel,
874                                                                            tp_chat_state_changed_cb,
875                                                                            NULL, NULL,
876                                                                            G_OBJECT (chat), NULL);
877         tp_cli_channel_interface_chat_state_connect_to_chat_state_changed (priv->channel,
878                                                                            tp_chat_state_changed_cb,
879                                                                            NULL, NULL,
880                                                                            G_OBJECT (chat), NULL);
881
882         priv->ready = TRUE;
883         g_object_notify (G_OBJECT (chat), "ready");
884 }
885
886 static void
887 tp_chat_dispose (GObject *object)
888 {
889   EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
890   EmpathyTpChatPriv *priv = GET_PRIV (self);
891
892   if (priv->dispose_has_run)
893     return;
894
895   priv->dispose_has_run = TRUE;
896
897   if (priv->channel != NULL)
898     empathy_tp_chat_close (self);
899
900   if (priv->remote_contact != NULL)
901     g_object_unref (priv->remote_contact);
902   priv->remote_contact = NULL;
903
904   if (priv->group != NULL)
905     g_object_unref (priv->group);
906   priv->group = NULL;
907
908   g_object_unref (priv->factory);
909   priv->factory = NULL;
910
911   g_object_unref (priv->user);
912   priv->user = NULL;
913
914   g_object_unref (priv->account);
915   priv->account = NULL;
916
917   g_list_foreach (priv->messages_queue->head, (GFunc) g_object_unref, NULL);
918   g_queue_free (priv->messages_queue);
919
920   g_list_foreach (priv->pending_messages_queue->head,
921     (GFunc) g_object_unref, NULL);
922   g_queue_free (priv->pending_messages_queue);
923
924   if (G_OBJECT_CLASS (empathy_tp_chat_parent_class)->dispose)
925     G_OBJECT_CLASS (empathy_tp_chat_parent_class)->dispose (object);
926 }
927
928 static void
929 tp_chat_finalize (GObject *object)
930 {
931         EmpathyTpChatPriv *priv = GET_PRIV (object);
932         guint              i;
933
934         DEBUG ("Finalize: %p", object);
935
936
937         if (priv->properties) {
938                 for (i = 0; i < priv->properties->len; i++) {
939                         TpChatProperty *property;
940
941                         property = g_ptr_array_index (priv->properties, i);
942                         g_free (property->name);
943                         if (property->value) {
944                                 tp_g_value_slice_free (property->value);
945                         }
946                         g_slice_free (TpChatProperty, property);
947                 }
948                 g_ptr_array_free (priv->properties, TRUE);
949         }
950
951         if (priv->remote_contact) {
952                 g_object_unref (priv->remote_contact);
953         }
954         if (priv->group) {
955                 g_object_unref (priv->group);
956         }
957
958         if (priv->contact_monitor) {
959                 g_object_unref (priv->contact_monitor);
960         }
961
962         g_object_unref (priv->factory);
963         g_object_unref (priv->user);
964         g_object_unref (priv->account);
965         g_free (priv->id);
966
967         if (g_queue_get_length (priv->messages_queue) > 0) {
968                 EmpathyMessage *message;
969                 EmpathyContact *contact;
970
971                 message = g_queue_peek_head (priv->messages_queue);
972                 contact = empathy_message_get_sender (message);
973                 g_signal_handlers_disconnect_by_func (contact,
974                                                       tp_chat_sender_ready_notify_cb,
975                                                       object);
976         }
977
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_object_ref (g_value_get_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 {
1177   EmpathyTpChatPriv *priv = GET_PRIV (chat);
1178
1179   /* If there are still messages left, it'll come back..
1180      We loose the ordering of sent messages though */
1181   g_signal_handlers_disconnect_by_func (priv->channel,
1182      tp_chat_invalidated_cb, chat);
1183   tp_cli_channel_call_close (priv->channel, -1, tp_chat_async_cb,
1184      "closing channel", NULL, NULL);
1185   g_object_unref (priv->channel);
1186
1187   g_signal_emit (chat, signals[DESTROY], 0);
1188   priv->channel = NULL;
1189 }
1190
1191 const gchar *
1192 empathy_tp_chat_get_id (EmpathyTpChat *chat)
1193 {
1194         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1195
1196         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1197         g_return_val_if_fail (priv->ready, NULL);
1198
1199         return priv->id;
1200 }
1201
1202 EmpathyContact *
1203 empathy_tp_chat_get_remote_contact (EmpathyTpChat *chat)
1204 {
1205         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1206
1207         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1208
1209         return priv->remote_contact;
1210 }
1211
1212 McAccount *
1213 empathy_tp_chat_get_account (EmpathyTpChat *chat)
1214 {
1215         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1216
1217         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
1218
1219         return priv->account;
1220 }
1221
1222 TpChannel *
1223 empathy_tp_chat_get_channel (EmpathyTpChat *chat)
1224 {
1225         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1226
1227         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL);
1228
1229         return priv->channel;
1230 }
1231
1232 gboolean
1233 empathy_tp_chat_is_ready (EmpathyTpChat *chat)
1234 {
1235         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1236
1237         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE);
1238
1239         return priv->ready;
1240 }
1241
1242 guint
1243 empathy_tp_chat_get_members_count (EmpathyTpChat *chat)
1244 {
1245         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1246
1247         g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), 0);
1248
1249         return priv->members_count;
1250 }
1251
1252 void
1253 empathy_tp_chat_send (EmpathyTpChat *chat,
1254                       EmpathyMessage *message)
1255 {
1256         EmpathyTpChatPriv        *priv = GET_PRIV (chat);
1257         const gchar              *message_body;
1258         TpChannelTextMessageType  message_type;
1259
1260         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1261         g_return_if_fail (EMPATHY_IS_MESSAGE (message));
1262         g_return_if_fail (priv->ready);
1263
1264         message_body = empathy_message_get_body (message);
1265         message_type = empathy_message_get_tptype (message);
1266
1267         DEBUG ("Sending message: %s", message_body);
1268         tp_cli_channel_type_text_call_send (priv->channel, -1,
1269                                             message_type,
1270                                             message_body,
1271                                             tp_chat_send_cb,
1272                                             g_object_ref (message),
1273                                             (GDestroyNotify) g_object_unref,
1274                                             G_OBJECT (chat));
1275 }
1276
1277 void
1278 empathy_tp_chat_set_state (EmpathyTpChat      *chat,
1279                            TpChannelChatState  state)
1280 {
1281         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1282
1283         g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
1284         g_return_if_fail (priv->ready);
1285
1286         DEBUG ("Set state: %d", state);
1287         tp_cli_channel_interface_chat_state_call_set_chat_state (priv->channel, -1,
1288                                                                  state,
1289                                                                  tp_chat_async_cb,
1290                                                                  "setting chat state",
1291                                                                  NULL,
1292                                                                  G_OBJECT (chat));
1293 }
1294
1295
1296 const GList *
1297 empathy_tp_chat_get_pending_messages (EmpathyTpChat *chat)
1298 {
1299         EmpathyTpChatPriv *priv = GET_PRIV (chat);
1300
1301         return priv->pending_messages_queue->head;
1302 }
1303
1304 static void
1305 acknowledge_messages (EmpathyTpChat *chat, GArray *ids)
1306 {
1307   EmpathyTpChatPriv *priv = GET_PRIV (chat);
1308
1309   tp_cli_channel_type_text_call_acknowledge_pending_messages (priv->channel,
1310     -1, ids, tp_chat_async_cb, "acknowledging received message",
1311     NULL, G_OBJECT (chat));
1312 }
1313
1314 void
1315 empathy_tp_chat_acknowledge_message (EmpathyTpChat *chat,
1316   EmpathyMessage *message)
1317 {
1318   EmpathyTpChatPriv *priv = GET_PRIV (chat);
1319   GArray *message_ids;
1320   GList *m;
1321   guint id;
1322
1323   if (empathy_message_get_sender (message) == priv->user)
1324     goto out;
1325
1326   message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
1327
1328   id = empathy_message_get_id (message);
1329   g_array_append_val (message_ids, id);
1330   acknowledge_messages (chat, message_ids);
1331   g_array_free (message_ids, TRUE);
1332
1333 out:
1334   m = g_queue_find (priv->pending_messages_queue, message);
1335   g_assert (m != NULL);
1336   g_queue_delete_link (priv->pending_messages_queue, m);
1337   g_object_unref (message);
1338 }
1339
1340 void
1341 empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat,
1342   const GList *messages)
1343 {
1344   EmpathyTpChatPriv *priv = GET_PRIV (chat);
1345   /* Copy messages as the messges list (probably is) our own */
1346   GList *msgs = g_list_copy ((GList *) messages);
1347   GList *l;
1348
1349   GArray *message_ids;
1350   message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
1351
1352   for (l = msgs; l != NULL; l = g_list_next (l))
1353     {
1354       GList *m;
1355       EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
1356
1357       m = g_queue_find (priv->pending_messages_queue, message);
1358       g_assert (m != NULL);
1359       g_queue_delete_link (priv->pending_messages_queue, m);
1360
1361       if (empathy_message_get_sender (message) != priv->user)
1362         {
1363           guint id = empathy_message_get_id (message);
1364           g_array_append_val (message_ids, id);
1365         }
1366     }
1367
1368   if (message_ids->len > 0)
1369     acknowledge_messages (chat, message_ids);
1370
1371   g_array_free (message_ids, TRUE);
1372   g_list_free (msgs);
1373 }