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