[darcs-to-svn @ Connect accounts in empathy-launcher, not in empathy-contact-list]
[empathy.git] / libempathy / empathy-contact-list.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
4  * Copyright (C) 2007 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  * 
21  * Authors: Xavier Claessens <xclaesse@gmail.com>
22  */
23
24 #include <config.h>
25
26 #include <string.h>
27
28 #include <libtelepathy/tp-conn.h>
29 #include <libtelepathy/tp-chan.h>
30 #include <libtelepathy/tp-helpers.h>
31 #include <libtelepathy/tp-chan-type-contact-list-gen.h>
32 #include <libtelepathy/tp-conn-iface-aliasing-gen.h>
33 #include <libtelepathy/tp-conn-iface-presence-gen.h>
34 #include <libtelepathy/tp-conn-iface-avatars-gen.h>
35
36 #include "empathy-contact-list.h"
37 #include "empathy-session.h"
38 #include "gossip-debug.h"
39 #include "gossip-telepathy-group.h"
40
41 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
42                        EMPATHY_TYPE_CONTACT_LIST, EmpathyContactListPriv))
43
44 #define DEBUG_DOMAIN "ContactList"
45 #define MAX_AVATAR_REQUESTS 10
46
47 struct _EmpathyContactListPriv {
48         TpConn               *tp_conn;
49         McAccount            *account;
50         GossipContact        *own_contact;
51
52         GossipTelepathyGroup *known;
53         GossipTelepathyGroup *publish;
54         GossipTelepathyGroup *subscribe;
55
56         GHashTable           *groups;
57         GHashTable           *contacts;
58
59         DBusGProxy           *aliasing_iface;
60         DBusGProxy           *avatars_iface;
61         DBusGProxy           *presence_iface;
62
63         GList                *avatar_requests_queue;
64 };
65
66 typedef enum {
67         CONTACT_LIST_TYPE_KNOWN,
68         CONTACT_LIST_TYPE_PUBLISH,
69         CONTACT_LIST_TYPE_SUBSCRIBE,
70         CONTACT_LIST_TYPE_UNKNOWN,
71         CONTACT_LIST_TYPE_COUNT
72 } ContactListType;
73
74 typedef struct {
75         guint  handle;
76         GList *new_groups;
77 } ContactListData;
78
79 typedef struct {
80         EmpathyContactList *list;
81         guint                 handle;
82 } ContactListAvatarRequestData;
83
84 typedef struct {
85         EmpathyContactList *list;
86         guint                *handles;
87 } ContactListAliasesRequestData;
88
89 static void                   empathy_contact_list_class_init       (EmpathyContactListClass       *klass);
90 static void                   empathy_contact_list_init             (EmpathyContactList            *list);
91 static void                   contact_list_finalize                 (GObject                       *object);
92 static void                   contact_list_finalize_proxies         (EmpathyContactList            *list);
93 static void                   contact_list_contact_removed_foreach  (guint                          handle,
94                                                                      GossipContact                 *contact,
95                                                                      EmpathyContactList            *list);
96 static void                   contact_list_destroy_cb               (DBusGProxy                    *proxy,
97                                                                      EmpathyContactList            *list);
98 static gboolean               contact_list_find_foreach             (guint                          handle,
99                                                                      GossipContact                 *contact,
100                                                                      gchar                         *id);
101 static void                   contact_list_newchannel_cb            (DBusGProxy                    *proxy,
102                                                                      const gchar                   *object_path,
103                                                                      const gchar                   *channel_type,
104                                                                      TelepathyHandleType            handle_type,
105                                                                      guint                          channel_handle,
106                                                                      gboolean                       suppress_handle,
107                                                                      EmpathyContactList            *list);
108 static ContactListType        contact_list_get_type                 (EmpathyContactList            *list,
109                                                                      TpChan                        *list_chan);
110 static void                   contact_list_contact_added_cb         (GossipTelepathyGroup          *group,
111                                                                      GArray                        *handles,
112                                                                      guint                          actor_handle,
113                                                                      guint                          reason,
114                                                                      const gchar                   *message,
115                                                                      EmpathyContactList            *list);
116 static void                   contact_list_contact_removed_cb       (GossipTelepathyGroup          *group,
117                                                                      GArray                        *handles,
118                                                                      guint                          actor_handle,
119                                                                      guint                          reason,
120                                                                      const gchar                   *message,
121                                                                      EmpathyContactList            *list);
122 static void                   contact_list_local_pending_cb         (GossipTelepathyGroup          *group,
123                                                                      GArray                        *handles,
124                                                                      guint                          actor_handle,
125                                                                      guint                          reason,
126                                                                      const gchar                   *message,
127                                                                      EmpathyContactList            *list);
128 static void                   contact_list_groups_updated_cb        (GossipContact                 *contact,
129                                                                      GParamSpec                    *param,
130                                                                      EmpathyContactList            *list);
131 static void                   contact_list_subscription_updated_cb  (GossipContact                 *contact,
132                                                                      GParamSpec                    *param,
133                                                                      EmpathyContactList            *list);
134 static void                   contact_list_name_updated_cb          (GossipContact                 *contact,
135                                                                      GParamSpec                    *param,
136                                                                      EmpathyContactList            *list);
137 static void                   contact_list_update_groups_foreach    (gchar                         *object_path,
138                                                                      GossipTelepathyGroup          *group,
139                                                                      ContactListData               *data);
140 static GossipTelepathyGroup * contact_list_get_group                (EmpathyContactList            *list,
141                                                                      const gchar                   *name);
142 static gboolean               contact_list_find_group               (gchar                         *key,
143                                                                      GossipTelepathyGroup          *group,
144                                                                      gchar                         *group_name);
145 static void                   contact_list_get_groups_foreach       (gchar                         *key,
146                                                                      GossipTelepathyGroup          *group,
147                                                                      GList                        **groups);
148 static void                   contact_list_group_channel_closed_cb  (TpChan                        *channel,
149                                                                      EmpathyContactList            *list);
150 static void                   contact_list_group_members_added_cb   (GossipTelepathyGroup          *group,
151                                                                      GArray                        *members,
152                                                                      guint                          actor_handle,
153                                                                      guint                          reason,
154                                                                      const gchar                   *message,
155                                                                      EmpathyContactList            *list);
156 static void                   contact_list_group_members_removed_cb (GossipTelepathyGroup          *group,
157                                                                      GArray                        *members,
158                                                                      guint                          actor_handle,
159                                                                      guint                          reason,
160                                                                      const gchar                   *message,
161                                                                      EmpathyContactList            *list);
162 static void                   contact_list_get_contacts_foreach     (guint                          handle,
163                                                                      GossipContact                 *contact,
164                                                                      GList                        **contacts);
165 static void                   contact_list_get_info                 (EmpathyContactList            *list,
166                                                                      GArray                        *handles);
167 static void                   contact_list_request_avatar           (EmpathyContactList            *list,
168                                                                      guint                          handle);
169 static void                   contact_list_start_avatar_requests    (EmpathyContactList            *list);
170 static void                   contact_list_avatar_update_cb         (DBusGProxy                    *proxy,
171                                                                      guint                          handle,
172                                                                      gchar                         *new_token,
173                                                                      EmpathyContactList            *list);
174 static void                   contact_list_request_avatar_cb        (DBusGProxy                    *proxy,
175                                                                      GArray                        *avatar_data,
176                                                                      gchar                         *mime_type,
177                                                                      GError                        *error,
178                                                                      ContactListAvatarRequestData  *data);
179 static void                   contact_list_aliases_update_cb        (DBusGProxy                    *proxy,
180                                                                      GPtrArray                     *handlers,
181                                                                      EmpathyContactList            *list);
182 static void                   contact_list_request_aliases_cb       (DBusGProxy                    *proxy,
183                                                                      gchar                        **contact_names,
184                                                                      GError                        *error,
185                                                                      ContactListAliasesRequestData *data);
186 static void                   contact_list_presence_update_cb       (DBusGProxy                    *proxy,
187                                                                      GHashTable                    *handle_table,
188                                                                      EmpathyContactList            *list);
189 static void                   contact_list_parse_presence_foreach   (guint                          handle,
190                                                                      GValueArray                   *presence_struct,
191                                                                      EmpathyContactList            *list);
192 static void                   contact_list_presences_table_foreach  (const gchar                   *state_str,
193                                                                      GHashTable                    *presences_table,
194                                                                      GossipPresence               **presence);
195 static GossipPresenceState    contact_list_presence_state_from_str  (const gchar                   *str);
196
197 enum {
198         CONTACT_ADDED,
199         CONTACT_REMOVED,
200         DESTROY,
201         LAST_SIGNAL
202 };
203
204 static guint signals[LAST_SIGNAL];
205 static guint n_avatar_requests = 0;
206
207 G_DEFINE_TYPE (EmpathyContactList, empathy_contact_list, G_TYPE_OBJECT);
208
209 static void
210 empathy_contact_list_class_init (EmpathyContactListClass *klass)
211 {
212         GObjectClass *object_class = G_OBJECT_CLASS (klass);
213
214         object_class->finalize = contact_list_finalize;
215
216         signals[CONTACT_ADDED] =
217                 g_signal_new ("contact-added",
218                               G_TYPE_FROM_CLASS (klass),
219                               G_SIGNAL_RUN_LAST,
220                               0,
221                               NULL, NULL,
222                               g_cclosure_marshal_VOID__OBJECT,
223                               G_TYPE_NONE,
224                               1, GOSSIP_TYPE_CONTACT);
225
226         signals[CONTACT_REMOVED] =
227                 g_signal_new ("contact-removed",
228                               G_TYPE_FROM_CLASS (klass),
229                               G_SIGNAL_RUN_LAST,
230                               0,
231                               NULL, NULL,
232                               g_cclosure_marshal_VOID__OBJECT,
233                               G_TYPE_NONE,
234                               1, GOSSIP_TYPE_CONTACT);
235
236         signals[DESTROY] =
237                 g_signal_new ("destroy",
238                               G_TYPE_FROM_CLASS (klass),
239                               G_SIGNAL_RUN_LAST,
240                               0,
241                               NULL, NULL,
242                               g_cclosure_marshal_VOID__VOID,
243                               G_TYPE_NONE,
244                               0);
245
246         g_type_class_add_private (object_class, sizeof (EmpathyContactListPriv));
247 }
248
249 static void
250 empathy_contact_list_init (EmpathyContactList *list)
251 {
252         EmpathyContactListPriv *priv;
253
254         priv = GET_PRIV (list);
255
256         priv->groups = g_hash_table_new_full (g_str_hash,
257                                               g_str_equal,
258                                               (GDestroyNotify) g_free,
259                                               (GDestroyNotify) g_object_unref);
260         priv->contacts = g_hash_table_new_full (g_direct_hash,
261                                                 g_direct_equal,
262                                                 NULL,
263                                                 (GDestroyNotify) g_object_unref);
264 }
265
266
267 static void
268 contact_list_finalize (GObject *object)
269 {
270         EmpathyContactListPriv *priv;
271         EmpathyContactList     *list;
272
273         list = EMPATHY_CONTACT_LIST (object);
274         priv = GET_PRIV (list);
275
276         gossip_debug (DEBUG_DOMAIN, "finalize: %p", object);
277
278         if (priv->tp_conn) {
279                 contact_list_finalize_proxies (list);
280                 g_object_unref (priv->tp_conn);
281         }
282
283         if (priv->known) {
284                 g_object_unref (priv->known);
285         }
286
287         if (priv->subscribe) {
288                 g_object_unref (priv->subscribe);
289         }
290
291         if (priv->publish) {
292                 g_object_unref (priv->publish);
293         }
294
295         g_object_unref (priv->account);
296         g_object_unref (priv->own_contact);
297         g_hash_table_destroy (priv->groups);
298         g_hash_table_destroy (priv->contacts);
299
300         G_OBJECT_CLASS (empathy_contact_list_parent_class)->finalize (object);
301 }
302
303 EmpathyContactList *
304 empathy_contact_list_new (McAccount *account)
305 {
306         EmpathyContactListPriv *priv;
307         EmpathyContactList     *list;
308         MissionControl         *mc;
309         TpConn                 *tp_conn;
310         guint                   handle;
311         GError                 *error = NULL;
312
313         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
314
315         mc = mission_control_new (tp_get_bus ());
316
317         if (mission_control_get_connection_status (mc, account, NULL) != 0) {
318                 /* The account is not connected, nothing to do. */
319                 return NULL;
320         }
321
322         tp_conn = mission_control_get_connection (mc, account, NULL);
323         g_object_unref (mc);
324         g_return_val_if_fail (tp_conn != NULL, NULL);
325
326         list = g_object_new (EMPATHY_TYPE_CONTACT_LIST, NULL);
327         priv = GET_PRIV (list);
328
329         priv->tp_conn = tp_conn;
330         priv->account = g_object_ref (account);
331
332         g_signal_connect (priv->tp_conn, "destroy",
333                           G_CALLBACK (contact_list_destroy_cb),
334                           list);
335
336         priv->aliasing_iface = tp_conn_get_interface (priv->tp_conn,
337                                                       TELEPATHY_CONN_IFACE_ALIASING_QUARK);
338         priv->avatars_iface = tp_conn_get_interface (priv->tp_conn,
339                                                      TELEPATHY_CONN_IFACE_AVATARS_QUARK);
340         priv->presence_iface = tp_conn_get_interface (priv->tp_conn,
341                                                       TELEPATHY_CONN_IFACE_PRESENCE_QUARK);
342
343         if (priv->aliasing_iface) {
344                 dbus_g_proxy_connect_signal (priv->aliasing_iface,
345                                              "AliasesChanged",
346                                              G_CALLBACK (contact_list_aliases_update_cb),
347                                              list, NULL);
348         }
349
350         if (priv->avatars_iface) {
351                 dbus_g_proxy_connect_signal (priv->avatars_iface,
352                                              "AvatarUpdated",
353                                              G_CALLBACK (contact_list_avatar_update_cb),
354                                              list, NULL);
355         }
356
357         if (priv->presence_iface) {
358                 dbus_g_proxy_connect_signal (priv->presence_iface,
359                                              "PresenceUpdate",
360                                              G_CALLBACK (contact_list_presence_update_cb),
361                                              list, NULL);
362         }
363
364         /* Get our own handle and contact */
365         if (!tp_conn_get_self_handle (DBUS_G_PROXY (priv->tp_conn),
366                                       &handle, &error)) {
367                 gossip_debug (DEBUG_DOMAIN, "GetSelfHandle Error: %s",
368                               error ? error->message : "No error given");
369                 g_clear_error (&error);
370         } else {
371                 priv->own_contact = empathy_contact_list_get_from_handle (list, handle);
372         }
373
374         return list;
375 }
376
377 void
378 empathy_contact_list_setup (EmpathyContactList *list)
379 {
380         EmpathyContactListPriv *priv;
381         GPtrArray                *channels;
382         GError                   *error = NULL;
383         guint                     i;
384
385         g_return_if_fail (EMPATHY_IS_CONTACT_LIST (list));
386
387         priv = GET_PRIV (list);
388
389         dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_conn), "NewChannel",
390                                      G_CALLBACK (contact_list_newchannel_cb),
391                                      list, NULL);
392
393         /* Get existing channels */
394         if (!tp_conn_list_channels (DBUS_G_PROXY (priv->tp_conn),
395                                     &channels,
396                                     &error)) {
397                 gossip_debug (DEBUG_DOMAIN,
398                               "Failed to get list of open channels: %s",
399                               error ? error->message : "No error given");
400                 g_clear_error (&error);
401                 return;
402         }
403
404         for (i = 0; channels->len > i; i++) {
405                 GValueArray         *chan_struct;
406                 const gchar         *object_path;
407                 const gchar         *chan_iface;
408                 TelepathyHandleType  handle_type;
409                 guint                handle;
410
411                 chan_struct = g_ptr_array_index (channels, i);
412                 object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0));
413                 chan_iface = g_value_get_string (g_value_array_get_nth (chan_struct, 1));
414                 handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2));
415                 handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3));
416
417                 contact_list_newchannel_cb (DBUS_G_PROXY (priv->tp_conn),
418                                             object_path, chan_iface,
419                                             handle_type, handle,
420                                             FALSE, list);
421
422                 g_value_array_free (chan_struct);
423         }
424
425         g_ptr_array_free (channels, TRUE);
426 }
427
428 McAccount *
429 empathy_contact_list_get_account (EmpathyContactList *list)
430 {
431         EmpathyContactListPriv *priv;
432
433         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
434
435         priv = GET_PRIV (list);
436
437         return priv->account;
438 }
439
440 GossipContact *
441 empathy_contact_list_get_own (EmpathyContactList *list)
442 {
443         EmpathyContactListPriv *priv;
444
445         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
446
447         priv = GET_PRIV (list);
448         
449         return priv->own_contact;
450 }
451
452 GossipContact *
453 empathy_contact_list_find (EmpathyContactList *list,
454                            const gchar        *id)
455 {
456         EmpathyContactListPriv *priv;
457         GossipContact            *contact;
458
459         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
460
461         priv = GET_PRIV (list);
462
463         contact = g_hash_table_find (priv->contacts,
464                                      (GHRFunc) contact_list_find_foreach,
465                                      (gchar*) id);
466
467         return NULL;
468 }
469
470 void
471 empathy_contact_list_add (EmpathyContactList *list,
472                           guint               handle,
473                           const gchar        *message)
474 {
475         EmpathyContactListPriv *priv;
476
477         g_return_if_fail (EMPATHY_IS_CONTACT_LIST (list));
478
479         priv = GET_PRIV (list);
480
481         gossip_telepathy_group_add_member (priv->subscribe, handle, message);
482 }
483
484 void
485 empathy_contact_list_remove (EmpathyContactList *list,
486                              guint               handle)
487 {
488         EmpathyContactListPriv *priv;
489
490         g_return_if_fail (EMPATHY_IS_CONTACT_LIST (list));
491
492         priv = GET_PRIV (list);
493
494         gossip_telepathy_group_remove_member (priv->subscribe, handle, "");
495         gossip_telepathy_group_remove_member (priv->publish, handle, "");
496         gossip_telepathy_group_remove_member (priv->known, handle, "");
497 }
498
499 GossipContact *
500 empathy_contact_list_get_from_id (EmpathyContactList *list,
501                                   const gchar        *id)
502 {
503         EmpathyContactListPriv *priv;
504         GossipContact          *contact;
505         const gchar            *contact_ids[] = {id, NULL};
506         GArray                 *handles;
507         guint                   handle;
508         GError                 *error = NULL;
509
510         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
511         g_return_val_if_fail (id != NULL, NULL);
512         
513         priv = GET_PRIV (list);
514
515         contact = empathy_contact_list_find (list, id);
516         if (contact) {
517                 return contact;
518         }
519
520         /* The id is unknown, requests a new handle */
521         if (!tp_conn_request_handles (DBUS_G_PROXY (priv->tp_conn),
522                                       TP_HANDLE_TYPE_CONTACT,
523                                       contact_ids,
524                                       &handles, &error)) {
525                 gossip_debug (DEBUG_DOMAIN, 
526                               "RequestHandle for %s failed: %s", id,
527                               error ? error->message : "No error given");
528                 g_clear_error (&error);
529                 return 0;
530         }
531
532         handle = g_array_index(handles, guint, 0);
533         g_array_free (handles, TRUE);
534
535         return empathy_contact_list_get_from_handle (list, handle);
536 }
537
538 GossipContact *
539 empathy_contact_list_get_from_handle (EmpathyContactList *list,
540                                       guint               handle)
541 {
542         GossipContact *contact;
543         GArray        *handles;
544         GList         *contacts;
545
546         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
547
548         handles = g_array_new (FALSE, FALSE, sizeof (guint));
549         g_array_append_val (handles, handle);
550
551         contacts = empathy_contact_list_get_from_handles (list, handles);
552         g_array_free (handles, TRUE);
553
554         if (!contacts) {
555                 return NULL;
556         }
557
558         contact = contacts->data;
559         g_list_free (contacts);
560
561         return contact;
562 }
563
564 GList *
565 empathy_contact_list_get_from_handles (EmpathyContactList *list,
566                                        GArray             *handles)
567 {
568         EmpathyContactListPriv  *priv;
569         gchar                  **handles_names;
570         gchar                  **id;
571         GArray                  *new_handles;
572         GList                   *contacts = NULL;
573         guint                    i;
574         GError                  *error = NULL;
575
576         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
577         g_return_val_if_fail (handles != NULL, NULL);
578
579         priv = GET_PRIV (list);
580
581         /* Search all handles we already have */
582         new_handles = g_array_new (FALSE, FALSE, sizeof (guint));
583         for (i = 0; i < handles->len; i++) {
584                 GossipContact *contact;
585                 guint          handle;
586
587                 handle = g_array_index (handles, guint, i);
588                 contact = g_hash_table_lookup (priv->contacts,
589                                                GUINT_TO_POINTER (handle));
590
591                 if (contact) {
592                         contacts = g_list_prepend (contacts,
593                                                    g_object_ref (contact));
594                 } else {
595                         g_array_append_val (new_handles, handle);
596                 }
597         }
598
599         if (new_handles->len == 0) {
600                 return contacts;
601         }
602
603         /* Holds all handles we don't have yet.
604          * FIXME: We should release them at some point. */
605         if (!tp_conn_hold_handles (DBUS_G_PROXY (priv->tp_conn),
606                                    TP_HANDLE_TYPE_CONTACT,
607                                    new_handles, &error)) {
608                 gossip_debug (DEBUG_DOMAIN, 
609                               "HoldHandles Error: %s",
610                               error ? error->message : "No error given");
611                 g_clear_error (&error);
612                 g_array_free (new_handles, TRUE);
613                 return contacts;
614         }
615
616         /* Get the IDs of all new handles */
617         if (!tp_conn_inspect_handles (DBUS_G_PROXY (priv->tp_conn),
618                                       TP_HANDLE_TYPE_CONTACT,
619                                       new_handles,
620                                       &handles_names,
621                                       &error)) {
622                 gossip_debug (DEBUG_DOMAIN, 
623                               "InspectHandle Error: %s",
624                               error ? error->message : "No error given");
625                 g_clear_error (&error);
626                 g_array_free (new_handles, TRUE);
627                 return contacts;
628         }
629
630         /* Create contact objects */
631         for (i = 0, id = handles_names; *id && i < new_handles->len; id++, i++) {
632                 GossipContact *contact;
633                 guint          handle;
634
635                 handle = g_array_index (new_handles, guint, i);
636                 contact = g_object_new (GOSSIP_TYPE_CONTACT,
637                                         "account", priv->account,
638                                         "id", *id,
639                                         "handle", handle,
640                                         NULL);
641
642                 g_signal_connect (contact, "notify::groups",
643                                   G_CALLBACK (contact_list_groups_updated_cb),
644                                   list);
645                 g_signal_connect (contact, "notify::subscription",
646                                   G_CALLBACK (contact_list_subscription_updated_cb),
647                                   list);
648                 g_signal_connect (contact, "notify::name",
649                                   G_CALLBACK (contact_list_name_updated_cb),
650                                   list);
651
652                 gossip_debug (DEBUG_DOMAIN, "new contact created: %s (%d)",
653                               *id, handle);
654
655                 g_hash_table_insert (priv->contacts,
656                                      GUINT_TO_POINTER (handle),
657                                      contact);
658
659                 contacts = g_list_prepend (contacts, g_object_ref (contact));
660         }
661
662         contact_list_get_info (list, new_handles);
663
664         g_array_free (new_handles, TRUE);
665         g_strfreev (handles_names);
666
667         return contacts;
668 }
669
670 void
671 empathy_contact_list_rename_group (EmpathyContactList *list,
672                                    const gchar        *old_group,
673                                    const gchar        *new_group)
674 {
675         EmpathyContactListPriv *priv;
676         GossipTelepathyGroup   *group;
677         GArray                 *members;
678
679         g_return_if_fail (EMPATHY_IS_CONTACT_LIST (list));
680         g_return_if_fail (old_group != NULL);
681         g_return_if_fail (new_group != NULL);
682
683         priv = GET_PRIV (list);
684
685         group = g_hash_table_find (priv->groups,
686                                    (GHRFunc) contact_list_find_group,
687                                    (gchar*) old_group);
688         if (!group) {
689                 /* The group doesn't exists on this account */
690                 return;
691         }
692
693         gossip_debug (DEBUG_DOMAIN, "rename group %s to %s", group, new_group);
694
695         /* Remove all members from the old group */
696         members = gossip_telepathy_group_get_members (group);
697         gossip_telepathy_group_remove_members (group, members, "");
698         contact_list_group_members_removed_cb (group, members, 
699                                                0, 
700                                                TP_CHANNEL_GROUP_CHANGE_REASON_NONE, 
701                                                NULL, list);
702         g_hash_table_remove (priv->groups,
703                              gossip_telepathy_group_get_object_path (group));
704
705         /* Add all members to the new group */
706         group = contact_list_get_group (list, new_group);
707         if (group) {
708                 gossip_telepathy_group_add_members (group, members, "");
709         }
710 }
711
712 GList *
713 empathy_contact_list_get_groups (EmpathyContactList *list)
714 {
715         EmpathyContactListPriv *priv;
716         GList                    *groups = NULL;
717
718         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
719
720         priv = GET_PRIV (list);
721
722         g_hash_table_foreach (priv->groups,
723                               (GHFunc) contact_list_get_groups_foreach,
724                               &groups);
725
726         groups = g_list_sort (groups, (GCompareFunc) strcmp);
727
728         return groups;
729 }
730
731 GList *
732 empathy_contact_list_get_contacts (EmpathyContactList *list)
733 {
734         EmpathyContactListPriv *priv;
735         GList                    *contacts = NULL;
736
737         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
738
739         priv = GET_PRIV (list);
740
741         /* FIXME: we should only return contacts that are in the contact list */
742         g_hash_table_foreach (priv->contacts,
743                               (GHFunc) contact_list_get_contacts_foreach,
744                               &contacts);
745
746         return contacts;
747 }
748
749 static void
750 contact_list_finalize_proxies (EmpathyContactList *list)
751 {
752         EmpathyContactListPriv *priv;
753
754         priv = GET_PRIV (list);
755
756         g_signal_handlers_disconnect_by_func (priv->tp_conn,
757                                               contact_list_destroy_cb,
758                                               list);
759         dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->tp_conn), "NewChannel",
760                                         G_CALLBACK (contact_list_newchannel_cb),
761                                         list);
762
763         if (priv->aliasing_iface) {
764                 dbus_g_proxy_disconnect_signal (priv->aliasing_iface,
765                                                 "AliasesChanged",
766                                                 G_CALLBACK (contact_list_aliases_update_cb),
767                                                 list);
768         }
769
770         if (priv->avatars_iface) {
771                 dbus_g_proxy_disconnect_signal (priv->avatars_iface,
772                                                 "AvatarUpdated",
773                                                 G_CALLBACK (contact_list_avatar_update_cb),
774                                                 list);
775         }
776
777         if (priv->presence_iface) {
778                 dbus_g_proxy_disconnect_signal (priv->presence_iface,
779                                                 "PresenceUpdate",
780                                                 G_CALLBACK (contact_list_presence_update_cb),
781                                                 list);
782         }
783 }
784
785 static void
786 contact_list_destroy_cb (DBusGProxy           *proxy,
787                          EmpathyContactList *list)
788 {
789         EmpathyContactListPriv *priv;
790
791         priv = GET_PRIV (list);
792
793         gossip_debug (DEBUG_DOMAIN, "Connection destroyed. "
794                                     "Account disconnected or CM crashed.");
795
796         /* DBus proxies should NOT be used anymore */
797         g_object_unref (priv->tp_conn);
798         priv->tp_conn = NULL;
799         priv->aliasing_iface = NULL;
800         priv->avatars_iface = NULL;
801         priv->presence_iface = NULL;
802
803         /* Remove all contacts */
804         g_hash_table_foreach (priv->contacts,
805                               (GHFunc) contact_list_contact_removed_foreach,
806                               list);
807         g_hash_table_remove_all (priv->contacts);
808
809         /* Tell the world to not use us anymore */
810         g_signal_emit (list, signals[DESTROY], 0);
811 }
812
813 static void
814 contact_list_contact_removed_foreach (guint                 handle,
815                                       GossipContact        *contact,
816                                       EmpathyContactList *list)
817 {
818         g_signal_handlers_disconnect_by_func (contact,
819                                               contact_list_groups_updated_cb,
820                                               list);
821         g_signal_handlers_disconnect_by_func (contact,
822                                               contact_list_subscription_updated_cb,
823                                               list);
824         g_signal_handlers_disconnect_by_func (contact,
825                                               contact_list_name_updated_cb,
826                                               list);
827
828         g_signal_emit (list, signals[CONTACT_REMOVED], 0, contact);
829 }
830
831 static void
832 contact_list_block_contact (EmpathyContactList *list,
833                             GossipContact        *contact)
834 {
835         g_signal_handlers_block_by_func (contact,
836                                          contact_list_groups_updated_cb,
837                                          list);
838         g_signal_handlers_block_by_func (contact,
839                                          contact_list_subscription_updated_cb,
840                                          list);
841         g_signal_handlers_block_by_func (contact,
842                                          contact_list_name_updated_cb,
843                                          list);
844 }
845
846 static void
847 contact_list_unblock_contact (EmpathyContactList *list,
848                               GossipContact        *contact)
849 {
850         g_signal_handlers_unblock_by_func (contact,
851                                            contact_list_groups_updated_cb,
852                                            list);
853         g_signal_handlers_unblock_by_func (contact,
854                                            contact_list_subscription_updated_cb,
855                                            list);
856         g_signal_handlers_unblock_by_func (contact,
857                                            contact_list_name_updated_cb,
858                                            list);
859 }
860
861 static gboolean
862 contact_list_find_foreach (guint          handle,
863                            GossipContact *contact,
864                            gchar         *id)
865 {
866         if (strcmp (gossip_contact_get_id (contact), id) == 0) {
867                 return TRUE;
868         }
869
870         return FALSE;
871 }
872
873 static void
874 contact_list_newchannel_cb (DBusGProxy          *proxy,
875                             const gchar         *object_path,
876                             const gchar         *channel_type,
877                             TelepathyHandleType  handle_type,
878                             guint                channel_handle,
879                             gboolean             suppress_handle,
880                             EmpathyContactList  *list)
881 {
882         EmpathyContactListPriv *priv;
883         GossipTelepathyGroup   *group;
884         TpChan                 *new_chan;
885         const gchar            *bus_name;
886         GArray                 *members;
887
888         priv = GET_PRIV (list);
889
890         if (strcmp (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) != 0 ||
891             suppress_handle) {
892                 return;
893         }
894
895         bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (priv->tp_conn));
896         new_chan = tp_chan_new (tp_get_bus (),
897                                 bus_name,
898                                 object_path,
899                                 channel_type, handle_type, channel_handle);
900
901         if (handle_type == TP_HANDLE_TYPE_LIST) {
902                 ContactListType list_type;
903
904                 list_type = contact_list_get_type (list, new_chan);
905                 if (list_type == CONTACT_LIST_TYPE_UNKNOWN) {
906                         gossip_debug (DEBUG_DOMAIN, "Unknown contact list channel");
907                         g_object_unref (new_chan);
908                         return;
909                 }
910
911                 gossip_debug (DEBUG_DOMAIN, "New contact list channel of type: %d",
912                               list_type);
913
914                 group = gossip_telepathy_group_new (new_chan, priv->tp_conn);
915
916                 switch (list_type) {
917                 case CONTACT_LIST_TYPE_KNOWN:
918                         if (priv->known) {
919                                 g_object_unref (priv->known);
920                         }
921                         priv->known = group;
922                         break;
923                 case CONTACT_LIST_TYPE_PUBLISH:
924                         if (priv->publish) {
925                                 g_object_unref (priv->publish);
926                         }
927                         priv->publish = group;
928                         break;
929                 case CONTACT_LIST_TYPE_SUBSCRIBE:
930                         if (priv->subscribe) {
931                                 g_object_unref (priv->subscribe);
932                         }
933                         priv->subscribe = group;
934                         break;
935                 default:
936                         g_assert_not_reached ();
937                 }
938
939                 /* Connect and setup the new contact-list group */
940                 if (list_type == CONTACT_LIST_TYPE_KNOWN ||
941                     list_type == CONTACT_LIST_TYPE_SUBSCRIBE) {
942                         g_signal_connect (group, "members-added",
943                                           G_CALLBACK (contact_list_contact_added_cb),
944                                           list);
945                         g_signal_connect (group, "members-removed",
946                                           G_CALLBACK (contact_list_contact_removed_cb),
947                                           list);
948
949                         members = gossip_telepathy_group_get_members (group);
950                         contact_list_contact_added_cb (group, members, 0,
951                                                        TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
952                                                        NULL, list);
953                         g_array_free (members, TRUE);
954                 }
955                 if (list_type == CONTACT_LIST_TYPE_PUBLISH) {
956                         GPtrArray *info;
957                         GArray    *pending; 
958                         guint      i;
959
960                         g_signal_connect (group, "local-pending",
961                                           G_CALLBACK (contact_list_local_pending_cb),
962                                           list);
963
964                         info = gossip_telepathy_group_get_local_pending_members_with_info (group);
965
966                         if (!info) {
967                                 /* This happens with butterfly because
968                                  * GetLocalPendingMembersWithInfo is not 
969                                  * implemented */
970                                 g_object_unref (new_chan);
971                                 return;
972                         }
973
974                         pending = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
975                         for (i = 0; info->len > i; i++) {
976                                 GValueArray *pending_struct;
977                                 guint        member;
978                                 guint        invitor;
979                                 guint        reason;
980                                 const gchar *message;
981
982                                 pending_struct = g_ptr_array_index (info, i);
983                                 member = g_value_get_uint (g_value_array_get_nth (pending_struct, 0));
984                                 invitor = g_value_get_uint (g_value_array_get_nth (pending_struct, 1));
985                                 reason = g_value_get_uint (g_value_array_get_nth (pending_struct, 2));
986                                 message = g_value_get_string (g_value_array_get_nth (pending_struct, 3));
987
988                                 g_array_insert_val (pending, 0, member);
989
990                                 contact_list_local_pending_cb (group, pending,
991                                                                invitor,
992                                                                reason,
993                                                                message, list);
994
995                                 g_value_array_free (pending_struct);
996                         }
997
998                         g_ptr_array_free (info, TRUE);
999                         g_array_free (pending, TRUE);
1000                 }
1001         }
1002         else if (handle_type == TP_HANDLE_TYPE_GROUP) {
1003                 const gchar *object_path;
1004
1005                 object_path = dbus_g_proxy_get_path (DBUS_G_PROXY (new_chan));
1006                 if (g_hash_table_lookup (priv->groups, object_path)) {
1007                         g_object_unref (new_chan);
1008                         return;
1009                 }
1010
1011                 group = gossip_telepathy_group_new (new_chan, priv->tp_conn);
1012
1013                 gossip_debug (DEBUG_DOMAIN, "New server-side group channel: %s",
1014                               gossip_telepathy_group_get_name (group));
1015
1016                 dbus_g_proxy_connect_signal (DBUS_G_PROXY (new_chan), "Closed",
1017                                              G_CALLBACK
1018                                              (contact_list_group_channel_closed_cb),
1019                                              list, NULL);
1020
1021                 g_hash_table_insert (priv->groups, g_strdup (object_path), group);
1022                 g_signal_connect (group, "members-added",
1023                                   G_CALLBACK (contact_list_group_members_added_cb),
1024                                   list);
1025                 g_signal_connect (group, "members-removed",
1026                                   G_CALLBACK (contact_list_group_members_removed_cb),
1027                                   list);
1028
1029                 members = gossip_telepathy_group_get_members (group);
1030                 contact_list_group_members_added_cb (group, members, 0,
1031                                                      TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
1032                                                      NULL, list);
1033                 g_array_free (members, TRUE);
1034         }
1035
1036         g_object_unref (new_chan);
1037 }
1038
1039 static ContactListType
1040 contact_list_get_type (EmpathyContactList *list,
1041                        TpChan             *list_chan)
1042 {
1043         EmpathyContactListPriv  *priv;
1044         GArray                  *handles;
1045         gchar                  **handle_name;
1046         ContactListType          list_type;
1047         GError                  *error = NULL;
1048
1049         priv = GET_PRIV (list);
1050
1051         handles = g_array_new (FALSE, FALSE, sizeof (guint));
1052         g_array_append_val (handles, list_chan->handle);
1053
1054         if (!tp_conn_inspect_handles (DBUS_G_PROXY (priv->tp_conn),
1055                                       TP_HANDLE_TYPE_LIST,
1056                                       handles,
1057                                       &handle_name,
1058                                       &error)) {
1059                 gossip_debug (DEBUG_DOMAIN, 
1060                               "InspectHandle Error: %s",
1061                               error ? error->message : "No error given");
1062                 g_clear_error (&error);
1063                 g_array_free (handles, TRUE);
1064                 return CONTACT_LIST_TYPE_UNKNOWN;
1065         }
1066
1067         if (strcmp (*handle_name, "subscribe") == 0) {
1068                 list_type = CONTACT_LIST_TYPE_SUBSCRIBE;
1069         } else if (strcmp (*handle_name, "publish") == 0) {
1070                 list_type = CONTACT_LIST_TYPE_PUBLISH;
1071         } else if (strcmp (*handle_name, "known") == 0) {
1072                 list_type = CONTACT_LIST_TYPE_KNOWN;
1073         } else {
1074                 list_type = CONTACT_LIST_TYPE_UNKNOWN;
1075         }
1076
1077         g_strfreev (handle_name);
1078         g_array_free (handles, TRUE);
1079
1080         return list_type;
1081 }
1082
1083 static void
1084 contact_list_contact_added_cb (GossipTelepathyGroup *group,
1085                                GArray               *handles,
1086                                guint                 actor_handle,
1087                                guint                 reason,
1088                                const gchar          *message,
1089                                EmpathyContactList   *list)
1090 {
1091         EmpathyContactListPriv *priv;
1092         GList                  *added_list, *l;
1093
1094         priv = GET_PRIV (list);
1095
1096         added_list = empathy_contact_list_get_from_handles (list, handles);
1097
1098         for (l = added_list; l; l = l->next) {
1099                 GossipContact *contact;
1100
1101                 contact = GOSSIP_CONTACT (l->data);
1102                 contact_list_block_contact (list, contact);
1103                 gossip_contact_set_subscription (contact, GOSSIP_SUBSCRIPTION_BOTH);
1104                 contact_list_unblock_contact (list, contact);
1105
1106                 g_signal_emit (list, signals[CONTACT_ADDED], 0, contact);
1107
1108                 g_object_unref (contact);
1109         }
1110
1111         g_list_free (added_list);
1112 }
1113
1114 static void
1115 contact_list_contact_removed_cb (GossipTelepathyGroup *group,
1116                                  GArray               *handles,
1117                                  guint                 actor_handle,
1118                                  guint                 reason,
1119                                  const gchar          *message,
1120                                  EmpathyContactList   *list)
1121 {
1122         EmpathyContactListPriv *priv;
1123         GList                  *removed_list, *l;
1124
1125         priv = GET_PRIV (list);
1126
1127         removed_list = empathy_contact_list_get_from_handles (list, handles);
1128
1129         for (l = removed_list; l; l = l->next) {
1130                 GossipContact *contact;
1131                 guint          handle;
1132
1133                 contact = GOSSIP_CONTACT (l->data);
1134
1135                 handle = gossip_contact_get_handle (contact);
1136                 g_hash_table_remove (priv->contacts, GUINT_TO_POINTER (handle));
1137
1138                 g_signal_emit (list, signals[CONTACT_REMOVED], 0, contact);
1139
1140                 g_object_unref (contact);
1141         }
1142
1143         g_list_free (removed_list);
1144 }
1145
1146 static void
1147 contact_list_local_pending_cb (GossipTelepathyGroup *group,
1148                                GArray               *handles,
1149                                guint                 actor_handle,
1150                                guint                 reason,
1151                                const gchar          *message,
1152                                EmpathyContactList   *list)
1153 {
1154         EmpathyContactListPriv *priv;
1155         GList                  *pending_list, *l;
1156
1157         priv = GET_PRIV (list);
1158
1159         pending_list = empathy_contact_list_get_from_handles (list, handles);
1160
1161         for (l = pending_list; l; l = l->next) {
1162                 GossipContact *contact;
1163
1164                 contact = GOSSIP_CONTACT (l->data);
1165
1166                 /* FIXME: Is that the correct way ? */
1167                 contact_list_block_contact (list, contact);
1168                 gossip_contact_set_subscription (contact, GOSSIP_SUBSCRIPTION_FROM);
1169                 contact_list_unblock_contact (list, contact);
1170                 g_signal_emit (list, signals[CONTACT_ADDED], 0, contact);
1171
1172                 g_object_unref (contact);
1173         }
1174
1175         g_list_free (pending_list);
1176 }
1177
1178 static void
1179 contact_list_groups_updated_cb (GossipContact      *contact,
1180                                 GParamSpec         *param,
1181                                 EmpathyContactList *list)
1182 {
1183         EmpathyContactListPriv *priv;
1184         ContactListData         data;
1185         GList                  *groups, *l;
1186
1187         priv = GET_PRIV (list);
1188
1189         /* Make sure all groups are created */
1190         groups = gossip_contact_get_groups (contact);
1191         for (l = groups; l; l = l->next) {
1192                 contact_list_get_group (list, l->data);
1193         }
1194
1195         data.handle = gossip_contact_get_handle (contact);
1196         data.new_groups = groups;
1197
1198         g_hash_table_foreach (priv->groups,
1199                               (GHFunc) contact_list_update_groups_foreach,
1200                               &data);
1201 }
1202
1203 static void
1204 contact_list_subscription_updated_cb (GossipContact      *contact,
1205                                       GParamSpec         *param,
1206                                       EmpathyContactList *list)
1207 {
1208         EmpathyContactListPriv *priv;
1209         GossipSubscription      subscription;
1210         guint                   handle;
1211
1212         priv = GET_PRIV (list);
1213
1214         subscription = gossip_contact_get_subscription (contact);
1215         handle = gossip_contact_get_handle (contact);
1216
1217         /* FIXME: what to do here, I'm a bit lost... */
1218         if (subscription) {
1219                 gossip_telepathy_group_add_member (priv->publish, handle, "");
1220         } else {
1221                 gossip_telepathy_group_remove_member (priv->publish, handle, "");
1222         }
1223 }
1224
1225 static void
1226 contact_list_name_updated_cb (GossipContact      *contact,
1227                               GParamSpec         *param,
1228                               EmpathyContactList *list)
1229 {
1230         EmpathyContactListPriv *priv;
1231         GHashTable             *new_alias;
1232         const gchar            *new_name;
1233         guint                   handle;
1234         GError                 *error = NULL;
1235
1236         priv = GET_PRIV (list);
1237         
1238         handle = gossip_contact_get_handle (contact);
1239         new_name = gossip_contact_get_name (contact);
1240
1241         gossip_debug (DEBUG_DOMAIN, "renaming handle %d to %s",
1242                       handle, new_name);
1243
1244         new_alias = g_hash_table_new_full (g_direct_hash,
1245                                            g_direct_equal,
1246                                            NULL,
1247                                            g_free);
1248
1249         g_hash_table_insert (new_alias,
1250                              GUINT_TO_POINTER (handle),
1251                              g_strdup (new_name));
1252
1253         if (!tp_conn_iface_aliasing_set_aliases (priv->aliasing_iface,
1254                                                  new_alias,
1255                                                  &error)) {
1256                 gossip_debug (DEBUG_DOMAIN, 
1257                               "Couldn't rename contact: %s",
1258                               error ? error->message : "No error given");
1259                 g_clear_error (&error);
1260         }
1261
1262         g_hash_table_destroy (new_alias);
1263 }
1264
1265 static void
1266 contact_list_update_groups_foreach (gchar                *object_path,
1267                                     GossipTelepathyGroup *group,
1268                                     ContactListData      *data)
1269 {
1270         gboolean     is_member;
1271         gboolean     found = FALSE;
1272         const gchar *group_name;
1273         GList       *l;
1274
1275         is_member = gossip_telepathy_group_is_member (group, data->handle);
1276         group_name = gossip_telepathy_group_get_name (group);
1277
1278         for (l = data->new_groups; l; l = l->next) {
1279                 if (strcmp (group_name, l->data) == 0) {
1280                         found = TRUE;
1281                         break;
1282                 }
1283         }
1284
1285         if (is_member && !found) {
1286                 /* We are no longer member of this group */
1287                 gossip_debug (DEBUG_DOMAIN, "Contact %d removed from group '%s'",
1288                               data->handle, group_name);
1289                 gossip_telepathy_group_remove_member (group, data->handle, "");
1290         }
1291
1292         if (!is_member && found) {
1293                 /* We are now member of this group */
1294                 gossip_debug (DEBUG_DOMAIN, "Contact %d added to group '%s'",
1295                               data->handle, group_name);
1296                 gossip_telepathy_group_add_member (group, data->handle, "");
1297         }
1298 }
1299
1300 static GossipTelepathyGroup *
1301 contact_list_get_group (EmpathyContactList *list,
1302                         const gchar        *name)
1303 {
1304         EmpathyContactListPriv *priv;
1305         GossipTelepathyGroup   *group;
1306         TpChan                 *group_channel;
1307         GArray                 *handles;
1308         guint                   group_handle;
1309         char                   *group_object_path;
1310         const char             *names[2] = {name, NULL};
1311         GError                 *error = NULL;
1312
1313         priv = GET_PRIV (list);
1314
1315         group = g_hash_table_find (priv->groups,
1316                                    (GHRFunc) contact_list_find_group,
1317                                    (gchar*) name);
1318         if (group) {
1319                 return group;
1320         }
1321
1322         gossip_debug (DEBUG_DOMAIN, "creating new group: %s", name);
1323
1324         if (!tp_conn_request_handles (DBUS_G_PROXY (priv->tp_conn),
1325                                       TP_HANDLE_TYPE_GROUP,
1326                                       names,
1327                                       &handles,
1328                                       &error)) {
1329                 gossip_debug (DEBUG_DOMAIN,
1330                               "Couldn't request the creation of a new handle for group: %s",
1331                               error ? error->message : "No error given");
1332                 g_clear_error (&error);
1333                 return NULL;
1334         }
1335         group_handle = g_array_index (handles, guint, 0);
1336         g_array_free (handles, TRUE);
1337
1338         if (!tp_conn_request_channel (DBUS_G_PROXY (priv->tp_conn),
1339                                       TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
1340                                       TP_HANDLE_TYPE_GROUP,
1341                                       group_handle,
1342                                       FALSE,
1343                                       &group_object_path,
1344                                       &error)) {
1345                 gossip_debug (DEBUG_DOMAIN,
1346                               "Couldn't request the creation of a new group channel: %s",
1347                               error ? error->message : "No error given");
1348                 g_clear_error (&error);
1349                 return NULL;
1350         }
1351
1352         group_channel = tp_chan_new (tp_get_bus (),
1353                                      dbus_g_proxy_get_bus_name (DBUS_G_PROXY (priv->tp_conn)),
1354                                      group_object_path,
1355                                      TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
1356                                      TP_HANDLE_TYPE_GROUP,
1357                                      group_handle);
1358
1359         dbus_g_proxy_connect_signal (DBUS_G_PROXY (group_channel),
1360                                      "Closed",
1361                                      G_CALLBACK
1362                                      (contact_list_group_channel_closed_cb),
1363                                      list,
1364                                      NULL);
1365
1366         group = gossip_telepathy_group_new (group_channel, priv->tp_conn);
1367         g_hash_table_insert (priv->groups, group_object_path, group);
1368         g_signal_connect (group, "members-added",
1369                           G_CALLBACK (contact_list_group_members_added_cb),
1370                           list);
1371         g_signal_connect (group, "members-removed",
1372                           G_CALLBACK (contact_list_group_members_removed_cb),
1373                           list);
1374
1375         return group;
1376 }
1377
1378 static gboolean
1379 contact_list_find_group (gchar                 *key,
1380                          GossipTelepathyGroup  *group,
1381                          gchar                 *group_name)
1382 {
1383         if (strcmp (group_name, gossip_telepathy_group_get_name (group)) == 0) {
1384                 return TRUE;
1385         }
1386
1387         return FALSE;
1388 }
1389
1390 static void
1391 contact_list_get_groups_foreach (gchar                 *key,
1392                                  GossipTelepathyGroup  *group,
1393                                  GList                **groups)
1394 {
1395         const gchar *name;
1396
1397         name = gossip_telepathy_group_get_name (group);
1398         *groups = g_list_append (*groups, g_strdup (name));
1399 }
1400
1401 static void
1402 contact_list_group_channel_closed_cb (TpChan             *channel,
1403                                       EmpathyContactList *list)
1404 {
1405         EmpathyContactListPriv *priv;
1406
1407         priv = GET_PRIV (list);
1408
1409         g_hash_table_remove (priv->groups,
1410                              dbus_g_proxy_get_path (DBUS_G_PROXY (channel)));
1411 }
1412
1413 static void
1414 contact_list_group_members_added_cb (GossipTelepathyGroup *group,
1415                                      GArray               *members,
1416                                      guint                 actor_handle,
1417                                      guint                 reason,
1418                                      const gchar          *message,
1419                                      EmpathyContactList   *list)
1420 {
1421         EmpathyContactListPriv *priv;
1422         GList                  *added_list, *l;
1423         const gchar            *group_name;
1424
1425         priv = GET_PRIV (list);
1426
1427         group_name = gossip_telepathy_group_get_name (group);
1428         added_list = empathy_contact_list_get_from_handles (list, members);
1429
1430         for (l = added_list; l; l = l->next) {
1431                 GossipContact *contact;
1432                 GList         *contact_groups;
1433
1434                 contact = GOSSIP_CONTACT (l->data);
1435                 contact_groups = gossip_contact_get_groups (contact);
1436
1437                 if (!g_list_find_custom (contact_groups,
1438                                          group_name,
1439                                          (GCompareFunc) strcmp)) {
1440                         gossip_debug (DEBUG_DOMAIN, "Contact %s added to group '%s'",
1441                                       gossip_contact_get_name (contact),
1442                                       group_name);
1443                         contact_groups = g_list_append (contact_groups,
1444                                                         g_strdup (group_name));
1445                         contact_list_block_contact (list, contact);
1446                         gossip_contact_set_groups (contact, contact_groups);
1447                         contact_list_unblock_contact (list, contact);
1448                 }
1449
1450                 g_object_unref (contact);
1451         }
1452
1453         g_list_free (added_list);
1454 }
1455
1456 static void
1457 contact_list_group_members_removed_cb (GossipTelepathyGroup *group,
1458                                        GArray               *members,
1459                                        guint                 actor_handle,
1460                                        guint                 reason,
1461                                        const gchar          *message,
1462                                        EmpathyContactList   *list)
1463 {
1464         EmpathyContactListPriv *priv;
1465         GList                  *removed_list, *l;
1466         const gchar            *group_name;
1467
1468         priv = GET_PRIV (list);
1469
1470         group_name = gossip_telepathy_group_get_name (group);
1471         removed_list = empathy_contact_list_get_from_handles (list, members);
1472
1473         for (l = removed_list; l; l = l->next) {
1474                 GossipContact *contact;
1475                 GList         *contact_groups;
1476                 GList         *to_remove;
1477
1478                 /* FIXME: Does it leak ? */
1479                 contact = GOSSIP_CONTACT (l->data);
1480                 contact_groups = gossip_contact_get_groups (contact);
1481                 contact_groups = g_list_copy (contact_groups);
1482
1483                 to_remove = g_list_find_custom (contact_groups,
1484                                                 group_name,
1485                                                 (GCompareFunc) strcmp);
1486                 if (to_remove) {
1487                         gossip_debug (DEBUG_DOMAIN, "Contact %d removed from group '%s'",
1488                                       gossip_contact_get_handle (contact),
1489                                       group_name);
1490                         contact_groups = g_list_remove_link (contact_groups,
1491                                                              to_remove);
1492                         contact_list_block_contact (list, contact);
1493                         gossip_contact_set_groups (contact, contact_groups);
1494                         contact_list_unblock_contact (list, contact);
1495                 }
1496
1497                 g_list_free (contact_groups);
1498
1499                 g_object_unref (contact);
1500         }
1501
1502         g_list_free (removed_list);
1503 }
1504
1505 static void
1506 contact_list_get_contacts_foreach (guint           handle,
1507                                    GossipContact  *contact,
1508                                    GList         **contacts)
1509 {
1510         *contacts = g_list_append (*contacts, g_object_ref (contact));
1511 }
1512
1513 static void
1514 contact_list_get_info (EmpathyContactList *list,
1515                        GArray             *handles)
1516 {
1517         EmpathyContactListPriv *priv;
1518         GError                 *error = NULL;
1519
1520         priv = GET_PRIV (list);
1521
1522         if (priv->presence_iface) {
1523                 /* FIXME: We should use GetPresence instead */
1524                 if (!tp_conn_iface_presence_request_presence (priv->presence_iface,
1525                                                               handles, &error)) {
1526                         gossip_debug (DEBUG_DOMAIN, 
1527                                       "Could not request presences: %s",
1528                                       error ? error->message : "No error given");
1529                         g_clear_error (&error);
1530                 }
1531         }
1532
1533         if (priv->aliasing_iface) {
1534                 ContactListAliasesRequestData *data;
1535
1536                 data = g_slice_new (ContactListAliasesRequestData);
1537                 data->list = list;
1538                 data->handles = g_memdup (handles->data, handles->len * sizeof (guint));
1539
1540                 tp_conn_iface_aliasing_request_aliases_async (priv->aliasing_iface,
1541                                                               handles,
1542                                                               (tp_conn_iface_aliasing_request_aliases_reply)
1543                                                               contact_list_request_aliases_cb,
1544                                                               data);
1545         }
1546
1547         if (priv->avatars_iface) {
1548                 guint i;
1549
1550                 for (i = 0; i < handles->len; i++) {
1551                         guint handle;
1552
1553                         handle = g_array_index (handles, gint, i);
1554                         contact_list_request_avatar (list, handle);
1555                 }
1556         }
1557 }
1558
1559 static void
1560 contact_list_request_avatar (EmpathyContactList *list,
1561                              guint               handle)
1562 {
1563         EmpathyContactListPriv *priv;
1564
1565         priv = GET_PRIV (list);
1566         
1567         /* We queue avatar requests to not send too many dbus async
1568          * calls at once. If we don't we reach the dbus's limit of
1569          * pending calls */
1570         priv->avatar_requests_queue = g_list_append (priv->avatar_requests_queue,
1571                                                      GUINT_TO_POINTER (handle));
1572         contact_list_start_avatar_requests (list);
1573 }
1574
1575 static void
1576 contact_list_start_avatar_requests (EmpathyContactList *list)
1577 {
1578         EmpathyContactListPriv       *priv;
1579         ContactListAvatarRequestData *data;
1580
1581         priv = GET_PRIV (list);
1582
1583         while (n_avatar_requests <  MAX_AVATAR_REQUESTS &&
1584                priv->avatar_requests_queue) {
1585                 data = g_slice_new (ContactListAvatarRequestData);
1586                 data->list = list;
1587                 data->handle = GPOINTER_TO_UINT (priv->avatar_requests_queue->data);
1588
1589                 n_avatar_requests++;
1590                 priv->avatar_requests_queue = g_list_remove (priv->avatar_requests_queue,
1591                                                              priv->avatar_requests_queue->data);
1592
1593                 tp_conn_iface_avatars_request_avatar_async (priv->avatars_iface,
1594                                                             data->handle,
1595                                                             (tp_conn_iface_avatars_request_avatar_reply)
1596                                                             contact_list_request_avatar_cb,
1597                                                             data);
1598         }
1599 }
1600
1601 static void
1602 contact_list_avatar_update_cb (DBusGProxy         *proxy,
1603                                guint               handle,
1604                                gchar              *new_token,
1605                                EmpathyContactList *list)
1606 {
1607         gossip_debug (DEBUG_DOMAIN, "Changing avatar for %d to %s",
1608                       handle, new_token);
1609
1610         contact_list_request_avatar (list, handle);
1611 }
1612
1613 static void
1614 contact_list_request_avatar_cb (DBusGProxy                   *proxy,
1615                                 GArray                       *avatar_data,
1616                                 gchar                        *mime_type,
1617                                 GError                       *error,
1618                                 ContactListAvatarRequestData *data)
1619 {
1620         GossipContact *contact;
1621
1622         contact = empathy_contact_list_get_from_handle (data->list, data->handle);
1623
1624         if (error) {
1625                 gossip_debug (DEBUG_DOMAIN, "Error requesting avatar for %s: %s",
1626                               gossip_contact_get_name (contact),
1627                               error ? error->message : "No error given");
1628         } else {
1629                 GossipAvatar *avatar;
1630
1631                 avatar = gossip_avatar_new (avatar_data->data,
1632                                             avatar_data->len,
1633                                             mime_type);
1634                 contact_list_block_contact (data->list, contact);
1635                 gossip_contact_set_avatar (contact, avatar);
1636                 contact_list_unblock_contact (data->list, contact);
1637                 gossip_avatar_unref (avatar);
1638         }
1639
1640         n_avatar_requests--;
1641         contact_list_start_avatar_requests (data->list);
1642
1643         g_slice_free (ContactListAvatarRequestData, data);
1644 }
1645
1646 static void
1647 contact_list_aliases_update_cb (DBusGProxy         *proxy,
1648                                 GPtrArray          *renamed_handlers,
1649                                 EmpathyContactList *list)
1650 {
1651         gint i;
1652
1653         for (i = 0; renamed_handlers->len > i; i++) {
1654                 guint          handle;
1655                 const gchar   *alias;
1656                 GValueArray   *renamed_struct;
1657                 GossipContact *contact;
1658
1659                 renamed_struct = g_ptr_array_index (renamed_handlers, i);
1660                 handle = g_value_get_uint(g_value_array_get_nth (renamed_struct, 0));
1661                 alias = g_value_get_string(g_value_array_get_nth (renamed_struct, 1));
1662
1663                 if (alias && *alias == '\0') {
1664                         alias = NULL;
1665                 }
1666
1667                 contact = empathy_contact_list_get_from_handle (list, handle);
1668                 contact_list_block_contact (list, contact);
1669                 gossip_contact_set_name (contact, alias);
1670                 contact_list_unblock_contact (list, contact);
1671
1672                 gossip_debug (DEBUG_DOMAIN, "contact %d renamed to %s (update cb)",
1673                               handle, alias);
1674         }
1675 }
1676
1677 static void
1678 contact_list_request_aliases_cb (DBusGProxy                     *proxy,
1679                                  gchar                         **contact_names,
1680                                  GError                         *error,
1681                                  ContactListAliasesRequestData  *data)
1682 {
1683         guint   i = 0;
1684         gchar **name;
1685
1686         for (name = contact_names; *name && !error; name++) {
1687                 GossipContact *contact;
1688
1689                 contact = empathy_contact_list_get_from_handle (data->list,
1690                                                                 data->handles[i]);
1691                 contact_list_block_contact (data->list, contact);
1692                 gossip_contact_set_name (contact, *name);
1693                 contact_list_unblock_contact (data->list, contact);
1694
1695                 gossip_debug (DEBUG_DOMAIN, "contact %d renamed to %s (request cb)",
1696                               data->handles[i], *name);
1697
1698                 i++;
1699         }
1700
1701         g_free (data->handles);
1702         g_slice_free (ContactListAliasesRequestData, data);
1703 }
1704
1705 static void
1706 contact_list_presence_update_cb (DBusGProxy         *proxy,
1707                                  GHashTable         *handle_table,
1708                                  EmpathyContactList *list)
1709 {
1710         g_hash_table_foreach (handle_table,
1711                               (GHFunc) contact_list_parse_presence_foreach,
1712                               list);
1713 }
1714
1715 static void
1716 contact_list_parse_presence_foreach (guint               handle,
1717                                      GValueArray        *presence_struct,
1718                                      EmpathyContactList *list)
1719 {
1720         GHashTable     *presences_table;
1721         GossipContact  *contact;
1722         GossipPresence *presence = NULL;
1723
1724         contact = empathy_contact_list_get_from_handle (list, handle);
1725         presences_table = g_value_get_boxed (g_value_array_get_nth (presence_struct, 1));
1726
1727         g_hash_table_foreach (presences_table,
1728                               (GHFunc) contact_list_presences_table_foreach,
1729                               &presence);
1730
1731         gossip_debug (DEBUG_DOMAIN, "Presence changed for %s (%d)",
1732                       gossip_contact_get_name (contact),
1733                       handle);
1734
1735         contact_list_block_contact (list, contact);
1736         gossip_contact_set_presence (contact, presence);
1737         contact_list_unblock_contact (list, contact);
1738 }
1739
1740 static void
1741 contact_list_presences_table_foreach (const gchar     *state_str,
1742                                       GHashTable      *presences_table,
1743                                       GossipPresence **presence)
1744 {
1745         GossipPresenceState  state;
1746         const GValue        *message;
1747
1748         if (*presence) {
1749                 g_object_unref (*presence);
1750                 *presence = NULL;
1751         }
1752
1753         state = contact_list_presence_state_from_str (state_str);
1754         if (state == GOSSIP_PRESENCE_STATE_UNAVAILABLE) {
1755                 return;
1756         }
1757
1758         *presence = gossip_presence_new ();
1759         gossip_presence_set_state (*presence, state);
1760
1761         message = g_hash_table_lookup (presences_table, "message");
1762         if (message != NULL) {
1763                 gossip_presence_set_status (*presence,
1764                                             g_value_get_string (message));
1765         }
1766 }
1767
1768 static GossipPresenceState
1769 contact_list_presence_state_from_str (const gchar *str)
1770 {
1771         if (strcmp (str, "available") == 0) {
1772                 return GOSSIP_PRESENCE_STATE_AVAILABLE;
1773         } else if ((strcmp (str, "dnd") == 0) || (strcmp (str, "busy") == 0)) {
1774                 return GOSSIP_PRESENCE_STATE_BUSY;
1775         } else if ((strcmp (str, "away") == 0) || (strcmp (str, "brb") == 0)) {
1776                 return GOSSIP_PRESENCE_STATE_AWAY;
1777         } else if (strcmp (str, "xa") == 0) {
1778                 return GOSSIP_PRESENCE_STATE_EXT_AWAY;
1779         } else if (strcmp (str, "hidden") == 0) {
1780                 return GOSSIP_PRESENCE_STATE_HIDDEN;
1781         } else if (strcmp (str, "offline") == 0) {
1782                 return GOSSIP_PRESENCE_STATE_UNAVAILABLE;
1783         } else if (strcmp (str, "chat") == 0) {
1784                 /* We don't support chat, so treat it like available. */
1785                 return GOSSIP_PRESENCE_STATE_AVAILABLE;
1786         }
1787
1788         return GOSSIP_PRESENCE_STATE_AVAILABLE;
1789 }
1790