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