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