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