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