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