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