]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-contact-list.c
Initial room list support. It does not works yet.
[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 = _("Local Network");
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
978         if (handle_type == TP_HANDLE_TYPE_LIST) {
979                 TpContactListType list_type;
980
981                 group = empathy_tp_group_new (priv->account, new_chan);
982
983                 list_type = tp_contact_list_get_type (list, group);
984                 if (list_type == TP_CONTACT_LIST_TYPE_UNKNOWN) {
985                         empathy_debug (DEBUG_DOMAIN,
986                                       "Type of contact list channel unknown: %s",
987                                       empathy_tp_group_get_name (group));
988
989                         g_object_unref (new_chan);
990                         g_object_unref (group);
991                         return;
992                 } else {
993                         empathy_debug (DEBUG_DOMAIN,
994                                       "New contact list channel of type: %d",
995                                       list_type);
996                 }
997
998                 g_signal_connect (group, "members-added",
999                                   G_CALLBACK (tp_contact_list_added_cb),
1000                                   list);
1001                 g_signal_connect (group, "members-removed",
1002                                   G_CALLBACK (tp_contact_list_removed_cb),
1003                                   list);
1004
1005                 if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH) {
1006                         GList *pendings, *l;
1007
1008                         if (priv->publish) {
1009                                 g_object_unref (priv->publish);
1010                         }
1011                         priv->publish = group;
1012
1013                         /* Makes no sense to be in remote-pending */
1014                         g_signal_connect (group, "local-pending",
1015                                           G_CALLBACK (tp_contact_list_pending_cb),
1016                                           list);
1017
1018                         pendings = empathy_tp_group_get_local_pending_members_with_info (group);
1019                         if (pendings) {
1020                                 GArray *pending;
1021
1022                                 pending = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
1023                                 for (l = pendings; l; l = l->next) {
1024                                         EmpathyTpGroupInfo *info;
1025
1026                                         info = l->data;
1027
1028                                         g_array_insert_val (pending, 0, info->member);
1029                                         tp_contact_list_pending_cb (group, pending,
1030                                                                     info->actor,
1031                                                                     info->reason,
1032                                                                     info->message,
1033                                                                     list);
1034                                 }
1035                                 g_array_free (pending, TRUE);
1036                                 empathy_tp_group_info_list_free (pendings);
1037                         }
1038                 }
1039                 if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE) {
1040                         GArray *remote_pendings = NULL;
1041
1042                         if (priv->subscribe) {
1043                                 g_object_unref (priv->subscribe);
1044                         }
1045                         priv->subscribe = group;
1046
1047                         /* Makes no sense to be in local-pending */
1048                         g_signal_connect (group, "remote-pending",
1049                                           G_CALLBACK (tp_contact_list_pending_cb),
1050                                           list);
1051                         empathy_tp_group_get_all_members (group,
1052                                                           &members,
1053                                                           NULL,
1054                                                           &remote_pendings);
1055
1056                         tp_contact_list_pending_cb (group, remote_pendings, 0,
1057                                                     TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
1058                                                     NULL,
1059                                                     list);
1060                         g_array_free (remote_pendings, TRUE);
1061                 } else {
1062                         members = empathy_tp_group_get_members (group);
1063                 }
1064
1065                 tp_contact_list_added_cb (group, members, 0,
1066                                           TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
1067                                           NULL, list);
1068                 g_array_free (members, TRUE);
1069         }
1070         else if (handle_type == TP_HANDLE_TYPE_GROUP) {
1071                 const gchar *object_path;
1072
1073                 object_path = dbus_g_proxy_get_path (DBUS_G_PROXY (new_chan));
1074                 if (g_hash_table_lookup (priv->groups, object_path)) {
1075                         g_object_unref (new_chan);
1076                         return;
1077                 }
1078
1079                 group = empathy_tp_group_new (priv->account, new_chan);
1080
1081                 empathy_debug (DEBUG_DOMAIN, "New server-side group channel: %s",
1082                               empathy_tp_group_get_name (group));
1083
1084                 dbus_g_proxy_connect_signal (DBUS_G_PROXY (new_chan), "Closed",
1085                                              G_CALLBACK
1086                                              (tp_contact_list_group_channel_closed_cb),
1087                                              list, NULL);
1088
1089                 g_hash_table_insert (priv->groups, g_strdup (object_path), group);
1090                 g_signal_connect (group, "members-added",
1091                                   G_CALLBACK (tp_contact_list_group_members_added_cb),
1092                                   list);
1093                 g_signal_connect (group, "members-removed",
1094                                   G_CALLBACK (tp_contact_list_group_members_removed_cb),
1095                                   list);
1096
1097                 members = empathy_tp_group_get_members (group);
1098                 tp_contact_list_group_members_added_cb (group, members, 0,
1099                                                         TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
1100                                                         NULL, list);
1101                 g_array_free (members, TRUE);
1102         }
1103
1104         g_object_unref (new_chan);
1105 }
1106
1107 static TpContactListType
1108 tp_contact_list_get_type (EmpathyTpContactList *list,
1109                           EmpathyTpGroup       *group)
1110 {
1111         EmpathyTpContactListPriv *priv;
1112         TpContactListType         list_type;
1113         const gchar              *name;
1114
1115         priv = GET_PRIV (list);
1116
1117         name = empathy_tp_group_get_name (group);
1118         if (strcmp (name, "subscribe") == 0) {
1119                 list_type = TP_CONTACT_LIST_TYPE_SUBSCRIBE;
1120         } else if (strcmp (name, "publish") == 0) {
1121                 list_type = TP_CONTACT_LIST_TYPE_PUBLISH;
1122         } else {
1123                 list_type = TP_CONTACT_LIST_TYPE_UNKNOWN;
1124         }
1125
1126         return list_type;
1127 }
1128
1129 static void
1130 tp_contact_list_added_cb (EmpathyTpGroup       *group,
1131                           GArray               *handles,
1132                           guint                 actor_handle,
1133                           guint                 reason,
1134                           const gchar          *message,
1135                           EmpathyTpContactList *list)
1136 {
1137         EmpathyTpContactListPriv *priv;
1138         GList                    *added_list, *l;
1139         TpContactListType         list_type;
1140
1141         priv = GET_PRIV (list);
1142
1143         list_type = tp_contact_list_get_type (list, group);
1144
1145         added_list = empathy_tp_contact_list_get_from_handles (list, handles);
1146         for (l = added_list; l; l = l->next) {
1147                 EmpathyContact      *contact;
1148                 EmpathySubscription  subscription;
1149
1150                 contact = EMPATHY_CONTACT (l->data);
1151
1152                 empathy_debug (DEBUG_DOMAIN, "Contact '%s' added to list type %d",
1153                               empathy_contact_get_name (contact),
1154                               list_type);
1155
1156                 subscription = empathy_contact_get_subscription (contact);
1157                 if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE) {
1158                         subscription |= EMPATHY_SUBSCRIPTION_FROM;
1159                 }
1160                 else if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH) {
1161                         subscription |= EMPATHY_SUBSCRIPTION_TO;
1162                         tp_contact_list_remove_local_pending (list, contact);
1163                 }
1164
1165                 tp_contact_list_block_contact (list, contact);
1166                 empathy_contact_set_subscription (contact, subscription);
1167                 tp_contact_list_unblock_contact (list, contact);
1168
1169                 if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE) {
1170                         if (!g_list_find (priv->members, contact)) {
1171                                 priv->members = g_list_prepend (priv->members,
1172                                                                 g_object_ref (contact));
1173                                 g_signal_emit_by_name (list, "contact-added", contact);
1174                         }
1175                 }
1176
1177                 g_object_unref (contact);
1178         }
1179
1180         g_list_free (added_list);
1181 }
1182
1183 static void
1184 tp_contact_list_removed_cb (EmpathyTpGroup       *group,
1185                             GArray               *handles,
1186                             guint                 actor_handle,
1187                             guint                 reason,
1188                             const gchar          *message,
1189                             EmpathyTpContactList *list)
1190 {
1191         EmpathyTpContactListPriv *priv;
1192         GList                    *removed_list, *l;
1193         TpContactListType         list_type;
1194
1195         priv = GET_PRIV (list);
1196
1197         list_type = tp_contact_list_get_type (list, group);
1198
1199         removed_list = empathy_tp_contact_list_get_from_handles (list, handles);
1200         for (l = removed_list; l; l = l->next) {
1201                 EmpathyContact      *contact;
1202                 EmpathySubscription  subscription;
1203
1204                 contact = EMPATHY_CONTACT (l->data);
1205
1206                 empathy_debug (DEBUG_DOMAIN, "Contact '%s' removed from list type %d",
1207                               empathy_contact_get_name (contact),
1208                               list_type);
1209
1210                 subscription = empathy_contact_get_subscription (contact);
1211                 if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE) {
1212                         subscription &= !EMPATHY_SUBSCRIPTION_FROM;
1213                 }
1214                 else if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH) {
1215                         subscription &= !EMPATHY_SUBSCRIPTION_TO;
1216                         tp_contact_list_remove_local_pending (list, contact);
1217                 }
1218
1219                 tp_contact_list_block_contact (list, contact);
1220                 empathy_contact_set_subscription (contact, subscription);
1221                 tp_contact_list_unblock_contact (list, contact);
1222
1223                 if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE) {
1224                         GList *l;
1225
1226                         if ((l = g_list_find (priv->members, contact))) {
1227                                 g_signal_emit_by_name (list, "contact-removed", contact);
1228                                 priv->members = g_list_delete_link (priv->members, l);
1229                                 g_object_unref (contact);
1230                         }
1231                 }
1232                 g_object_unref (contact);
1233         }
1234
1235         g_list_free (removed_list);
1236 }
1237
1238 static void
1239 tp_contact_list_pending_cb (EmpathyTpGroup       *group,
1240                             GArray               *handles,
1241                             guint                 actor_handle,
1242                             guint                 reason,
1243                             const gchar          *message,
1244                             EmpathyTpContactList *list)
1245 {
1246         EmpathyTpContactListPriv *priv;
1247         GList                    *pending_list, *l;
1248         TpContactListType         list_type;
1249
1250         priv = GET_PRIV (list);
1251
1252         list_type = tp_contact_list_get_type (list, group);
1253
1254         pending_list = empathy_tp_contact_list_get_from_handles (list, handles);
1255         for (l = pending_list; l; l = l->next) {
1256                 EmpathyContact *contact;
1257
1258                 contact = EMPATHY_CONTACT (l->data);
1259
1260                 empathy_debug (DEBUG_DOMAIN, "Contact '%s' pending in list type %d",
1261                               empathy_contact_get_name (contact),
1262                               list_type);
1263
1264                 if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH) {
1265                         if (!g_list_find (priv->members, contact)) {
1266                                 EmpathyContactListInfo *info;
1267
1268                                 info = empathy_contact_list_info_new (contact, message);
1269                                 priv->local_pending = g_list_prepend (priv->local_pending,
1270                                                                       info);
1271
1272                                 g_signal_emit_by_name (list, "local-pending",
1273                                                        contact, message);
1274                         } else {
1275                                 guint handle;
1276
1277                                 /* That contact wants our presence and he is
1278                                  * in our roster. Accept to publish our presence
1279                                  * without asking the user. */
1280                                 handle = empathy_contact_get_handle (contact);
1281                                 empathy_tp_group_add_member (priv->publish,
1282                                                              handle, "");
1283                         }
1284                 }
1285                 else if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE) {
1286                         if (!g_list_find (priv->members, contact)) {
1287                                 priv->members = g_list_prepend (priv->members,
1288                                                                 g_object_ref (contact));
1289                                 g_signal_emit_by_name (list, "contact-added", contact);
1290                         }
1291                 }
1292
1293                 g_object_unref (contact);
1294         }
1295
1296         g_list_free (pending_list);
1297 }
1298
1299 static void
1300 tp_contact_list_remove_local_pending (EmpathyTpContactList *list,
1301                                       EmpathyContact        *contact)
1302 {
1303         EmpathyTpContactListPriv *priv;
1304         GList                    *l;
1305
1306         priv = GET_PRIV (list);
1307
1308         for (l = priv->local_pending; l; l = l->next) {
1309                 EmpathyContactListInfo *info;
1310
1311                 info = l->data;
1312                 if (empathy_contact_equal (contact, info->contact)) {
1313                         empathy_debug (DEBUG_DOMAIN, "Contact no more local-pending: %s",
1314                                       empathy_contact_get_name (contact));
1315
1316                         priv->local_pending = g_list_delete_link (priv->local_pending, l);
1317                         empathy_contact_list_info_free (info);
1318                         break;
1319                 }
1320         }
1321 }
1322
1323 static void
1324 tp_contact_list_groups_updated_cb (EmpathyContact       *contact,
1325                                    GParamSpec           *param,
1326                                    EmpathyTpContactList *list)
1327 {
1328         EmpathyTpContactListPriv *priv;
1329         TpContactListData         data;
1330         GList                    *groups, *l;
1331
1332         priv = GET_PRIV (list);
1333
1334         /* Make sure all groups are created */
1335         groups = empathy_contact_get_groups (contact);
1336         for (l = groups; l; l = l->next) {
1337                 tp_contact_list_get_group (list, l->data);
1338         }
1339
1340         data.handle = empathy_contact_get_handle (contact);
1341         data.new_groups = groups;
1342
1343         g_hash_table_foreach (priv->groups,
1344                               (GHFunc) tp_contact_list_update_groups_foreach,
1345                               &data);
1346 }
1347
1348 static void
1349 tp_contact_list_name_updated_cb (EmpathyContact        *contact,
1350                                  GParamSpec           *param,
1351                                  EmpathyTpContactList *list)
1352 {
1353         EmpathyTpContactListPriv *priv;
1354         GHashTable               *new_alias;
1355         const gchar              *new_name;
1356         guint                     handle;
1357         GError                   *error = NULL;
1358
1359         priv = GET_PRIV (list);
1360         
1361         if (!priv->aliasing_iface) {
1362                 return;
1363         }
1364
1365         handle = empathy_contact_get_handle (contact);
1366         new_name = empathy_contact_get_name (contact);
1367
1368         empathy_debug (DEBUG_DOMAIN, "renaming handle %d to %s",
1369                       handle, new_name);
1370
1371         new_alias = g_hash_table_new_full (g_direct_hash,
1372                                            g_direct_equal,
1373                                            NULL,
1374                                            g_free);
1375
1376         g_hash_table_insert (new_alias,
1377                              GUINT_TO_POINTER (handle),
1378                              g_strdup (new_name));
1379
1380         if (!tp_conn_iface_aliasing_set_aliases (priv->aliasing_iface,
1381                                                  new_alias,
1382                                                  &error)) {
1383                 empathy_debug (DEBUG_DOMAIN, 
1384                               "Couldn't rename contact: %s",
1385                               error ? error->message : "No error given");
1386                 g_clear_error (&error);
1387         }
1388
1389         g_hash_table_destroy (new_alias);
1390 }
1391
1392 static void
1393 tp_contact_list_update_groups_foreach (gchar             *object_path,
1394                                        EmpathyTpGroup    *group,
1395                                        TpContactListData *data)
1396 {
1397         gboolean     is_member;
1398         gboolean     found = FALSE;
1399         const gchar *group_name;
1400         GList       *l;
1401
1402         is_member = empathy_tp_group_is_member (group, data->handle);
1403         group_name = empathy_tp_group_get_name (group);
1404
1405         for (l = data->new_groups; l; l = l->next) {
1406                 if (strcmp (group_name, l->data) == 0) {
1407                         found = TRUE;
1408                         break;
1409                 }
1410         }
1411
1412         if (is_member && !found) {
1413                 /* We are no longer member of this group */
1414                 empathy_debug (DEBUG_DOMAIN, "Contact %d removed from group '%s'",
1415                               data->handle, group_name);
1416                 empathy_tp_group_remove_member (group, data->handle, "");
1417         }
1418
1419         if (!is_member && found) {
1420                 /* We are now member of this group */
1421                 empathy_debug (DEBUG_DOMAIN, "Contact %d added to group '%s'",
1422                               data->handle, group_name);
1423                 empathy_tp_group_add_member (group, data->handle, "");
1424         }
1425 }
1426
1427 static EmpathyTpGroup *
1428 tp_contact_list_get_group (EmpathyTpContactList *list,
1429                            const gchar          *name)
1430 {
1431         EmpathyTpContactListPriv *priv;
1432         EmpathyTpGroup           *group;
1433         TpChan                   *group_channel;
1434         GArray                   *handles;
1435         guint                     group_handle;
1436         char                     *group_object_path;
1437         const char               *names[2] = {name, NULL};
1438         GError                   *error = NULL;
1439
1440         priv = GET_PRIV (list);
1441
1442         group = g_hash_table_find (priv->groups,
1443                                    (GHRFunc) tp_contact_list_find_group,
1444                                    (gchar*) name);
1445         if (group) {
1446                 return group;
1447         }
1448
1449         empathy_debug (DEBUG_DOMAIN, "creating new group: %s", name);
1450
1451         if (!tp_conn_request_handles (DBUS_G_PROXY (priv->tp_conn),
1452                                       TP_HANDLE_TYPE_GROUP,
1453                                       names,
1454                                       &handles,
1455                                       &error)) {
1456                 empathy_debug (DEBUG_DOMAIN,
1457                               "Couldn't request the creation of a new handle for group: %s",
1458                               error ? error->message : "No error given");
1459                 g_clear_error (&error);
1460                 return NULL;
1461         }
1462         group_handle = g_array_index (handles, guint, 0);
1463         g_array_free (handles, TRUE);
1464
1465         if (!tp_conn_request_channel (DBUS_G_PROXY (priv->tp_conn),
1466                                       TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
1467                                       TP_HANDLE_TYPE_GROUP,
1468                                       group_handle,
1469                                       FALSE,
1470                                       &group_object_path,
1471                                       &error)) {
1472                 empathy_debug (DEBUG_DOMAIN,
1473                               "Couldn't request the creation of a new group channel: %s",
1474                               error ? error->message : "No error given");
1475                 g_clear_error (&error);
1476                 return NULL;
1477         }
1478
1479         group_channel = tp_chan_new (tp_get_bus (),
1480                                      dbus_g_proxy_get_bus_name (DBUS_G_PROXY (priv->tp_conn)),
1481                                      group_object_path,
1482                                      TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
1483                                      TP_HANDLE_TYPE_GROUP,
1484                                      group_handle);
1485
1486         dbus_g_proxy_connect_signal (DBUS_G_PROXY (group_channel),
1487                                      "Closed",
1488                                      G_CALLBACK
1489                                      (tp_contact_list_group_channel_closed_cb),
1490                                      list,
1491                                      NULL);
1492
1493         group = empathy_tp_group_new (priv->account, group_channel);
1494         g_hash_table_insert (priv->groups, group_object_path, group);
1495         g_signal_connect (group, "members-added",
1496                           G_CALLBACK (tp_contact_list_group_members_added_cb),
1497                           list);
1498         g_signal_connect (group, "members-removed",
1499                           G_CALLBACK (tp_contact_list_group_members_removed_cb),
1500                           list);
1501
1502         return group;
1503 }
1504
1505 static gboolean
1506 tp_contact_list_find_group (gchar          *key,
1507                             EmpathyTpGroup *group,
1508                             gchar          *group_name)
1509 {
1510         if (strcmp (group_name, empathy_tp_group_get_name (group)) == 0) {
1511                 return TRUE;
1512         }
1513
1514         return FALSE;
1515 }
1516
1517 static void
1518 tp_contact_list_get_groups_foreach (gchar          *key,
1519                                     EmpathyTpGroup *group,
1520                                     GList          **groups)
1521 {
1522         const gchar *name;
1523
1524         name = empathy_tp_group_get_name (group);
1525         *groups = g_list_append (*groups, g_strdup (name));
1526 }
1527
1528 static void
1529 tp_contact_list_group_channel_closed_cb (TpChan             *channel,
1530                                          EmpathyTpContactList *list)
1531 {
1532         EmpathyTpContactListPriv *priv;
1533
1534         priv = GET_PRIV (list);
1535
1536         g_hash_table_remove (priv->groups,
1537                              dbus_g_proxy_get_path (DBUS_G_PROXY (channel)));
1538 }
1539
1540 static void
1541 tp_contact_list_group_members_added_cb (EmpathyTpGroup       *group,
1542                                         GArray               *members,
1543                                         guint                 actor_handle,
1544                                         guint                 reason,
1545                                         const gchar          *message,
1546                                         EmpathyTpContactList *list)
1547 {
1548         EmpathyTpContactListPriv *priv;
1549         GList                    *added_list, *l;
1550         const gchar              *group_name;
1551
1552         priv = GET_PRIV (list);
1553
1554         group_name = empathy_tp_group_get_name (group);
1555         added_list = empathy_tp_contact_list_get_from_handles (list, members);
1556
1557         for (l = added_list; l; l = l->next) {
1558                 EmpathyContact *contact;
1559
1560                 contact = EMPATHY_CONTACT (l->data);
1561
1562                 tp_contact_list_block_contact (list, contact);
1563                 empathy_contact_add_group (contact, group_name);
1564                 tp_contact_list_unblock_contact (list, contact);
1565
1566                 g_object_unref (contact);
1567         }
1568
1569         g_list_free (added_list);
1570 }
1571
1572 static void
1573 tp_contact_list_group_members_removed_cb (EmpathyTpGroup       *group,
1574                                           GArray               *members,
1575                                           guint                 actor_handle,
1576                                           guint                 reason,
1577                                           const gchar          *message,
1578                                           EmpathyTpContactList *list)
1579 {
1580         EmpathyTpContactListPriv *priv;
1581         GList                    *removed_list, *l;
1582         const gchar              *group_name;
1583
1584         priv = GET_PRIV (list);
1585
1586         group_name = empathy_tp_group_get_name (group);
1587         removed_list = empathy_tp_contact_list_get_from_handles (list, members);
1588
1589         for (l = removed_list; l; l = l->next) {
1590                 EmpathyContact *contact;
1591
1592                 contact = l->data;
1593
1594                 tp_contact_list_block_contact (list, contact);
1595                 empathy_contact_remove_group (contact, group_name);
1596                 tp_contact_list_unblock_contact (list, contact);
1597
1598                 g_object_unref (contact);
1599         }
1600
1601         g_list_free (removed_list);
1602 }
1603
1604 static void
1605 tp_contact_list_get_info (EmpathyTpContactList *list,
1606                           GArray               *handles)
1607 {
1608         EmpathyTpContactListPriv *priv;
1609         GError                   *error = NULL;
1610
1611         priv = GET_PRIV (list);
1612
1613         if (priv->presence_iface) {
1614                 /* FIXME: We should use GetPresence instead */
1615                 if (!tp_conn_iface_presence_request_presence (priv->presence_iface,
1616                                                               handles, &error)) {
1617                         empathy_debug (DEBUG_DOMAIN, 
1618                                       "Could not request presences: %s",
1619                                       error ? error->message : "No error given");
1620                         g_clear_error (&error);
1621                 }
1622         }
1623
1624         if (priv->aliasing_iface) {
1625                 TpContactListAliasesRequestData *data;
1626
1627                 data = g_slice_new (TpContactListAliasesRequestData);
1628                 data->list = list;
1629                 data->handles = g_memdup (handles->data, handles->len * sizeof (guint));
1630
1631                 tp_conn_iface_aliasing_request_aliases_async (priv->aliasing_iface,
1632                                                               handles,
1633                                                               (tp_conn_iface_aliasing_request_aliases_reply)
1634                                                               tp_contact_list_request_aliases_cb,
1635                                                               data);
1636         }
1637
1638         if (priv->avatars_iface) {
1639                 guint i;
1640
1641                 for (i = 0; i < handles->len; i++) {
1642                         guint handle;
1643
1644                         handle = g_array_index (handles, gint, i);
1645                         tp_contact_list_request_avatar (list, handle);
1646                 }
1647         }
1648 }
1649
1650 static void
1651 tp_contact_list_request_avatar (EmpathyTpContactList *list,
1652                                 guint                 handle)
1653 {
1654         EmpathyTpContactListPriv       *priv;
1655         TpContactListAvatarRequestData *data;
1656
1657         priv = GET_PRIV (list);
1658         
1659         /* We queue avatar requests to not send too many dbus async
1660          * calls at once. If we don't we reach the dbus's limit of
1661          * pending calls */
1662         data = g_slice_new (TpContactListAvatarRequestData);
1663         data->list = g_object_ref (list);
1664         data->handle = handle;
1665         avatar_requests_queue = g_list_append (avatar_requests_queue, data);
1666         tp_contact_list_start_avatar_requests ();
1667 }
1668
1669 static void
1670 tp_contact_list_start_avatar_requests (void)
1671 {
1672         empathy_debug (DEBUG_DOMAIN, "Start avatar requests, pending calls: %d",
1673                        n_avatar_requests);
1674
1675         while (n_avatar_requests <  MAX_AVATAR_REQUESTS && avatar_requests_queue) {
1676                 EmpathyTpContactListPriv       *priv;
1677                 TpContactListAvatarRequestData *data;
1678
1679                 data = avatar_requests_queue->data;
1680                 priv = GET_PRIV (data->list);
1681
1682                 n_avatar_requests++;
1683                 avatar_requests_queue = g_list_delete_link (avatar_requests_queue,
1684                                                             avatar_requests_queue);
1685
1686                 empathy_debug (DEBUG_DOMAIN, "Calling RequestAvatar async");
1687                 tp_conn_iface_avatars_request_avatar_async (priv->avatars_iface,
1688                                                             data->handle,
1689                                                             (tp_conn_iface_avatars_request_avatar_reply)
1690                                                             tp_contact_list_request_avatar_cb,
1691                                                             data);
1692         }
1693 }
1694
1695 static void
1696 tp_contact_list_avatar_update_cb (DBusGProxy           *proxy,
1697                                   guint                 handle,
1698                                   gchar                *new_token,
1699                                   EmpathyTpContactList *list)
1700 {
1701         EmpathyTpContactListPriv *priv;
1702
1703         priv = GET_PRIV (list);
1704
1705         if (!g_hash_table_lookup (priv->contacts, GUINT_TO_POINTER (handle))) {
1706                 /* We don't know this contact, skip */
1707                 return;
1708         }
1709
1710         empathy_debug (DEBUG_DOMAIN, "Changing avatar for %d to %s",
1711                       handle, new_token);
1712
1713         tp_contact_list_request_avatar (list, handle);
1714 }
1715
1716 static void
1717 tp_contact_list_request_avatar_cb (DBusGProxy                     *proxy,
1718                                    GArray                         *avatar_data,
1719                                    gchar                          *mime_type,
1720                                    GError                         *error,
1721                                    TpContactListAvatarRequestData *data)
1722 {
1723         EmpathyContact *contact;
1724
1725         contact = empathy_tp_contact_list_get_from_handle (data->list, data->handle);
1726
1727         if (error) {
1728                 empathy_debug (DEBUG_DOMAIN, "Error requesting avatar for %s: %s",
1729                               empathy_contact_get_name (contact),
1730                               error ? error->message : "No error given");
1731         } else {
1732                 EmpathyAvatar *avatar;
1733
1734                 empathy_debug (DEBUG_DOMAIN, "Avatar received for %s (%d)",
1735                                empathy_contact_get_id (contact),
1736                                data->handle);
1737
1738                 avatar = empathy_avatar_new (avatar_data->data,
1739                                             avatar_data->len,
1740                                             mime_type);
1741                 tp_contact_list_block_contact (data->list, contact);
1742                 empathy_contact_set_avatar (contact, avatar);
1743                 tp_contact_list_unblock_contact (data->list, contact);
1744                 empathy_avatar_unref (avatar);
1745         }
1746
1747         n_avatar_requests--;
1748         tp_contact_list_start_avatar_requests ();
1749
1750         g_object_unref (contact);
1751         g_object_unref (data->list);
1752         g_slice_free (TpContactListAvatarRequestData, data);
1753 }
1754
1755 static void
1756 tp_contact_list_aliases_update_cb (DBusGProxy           *proxy,
1757                                    GPtrArray            *renamed_handlers,
1758                                    EmpathyTpContactList *list)
1759 {
1760         EmpathyTpContactListPriv *priv;
1761         guint                     i;
1762
1763         priv = GET_PRIV (list);
1764
1765         for (i = 0; renamed_handlers->len > i; i++) {
1766                 guint          handle;
1767                 const gchar   *alias;
1768                 GValueArray   *renamed_struct;
1769                 EmpathyContact *contact;
1770
1771                 renamed_struct = g_ptr_array_index (renamed_handlers, i);
1772                 handle = g_value_get_uint(g_value_array_get_nth (renamed_struct, 0));
1773                 alias = g_value_get_string(g_value_array_get_nth (renamed_struct, 1));
1774
1775                 if (!g_hash_table_lookup (priv->contacts, GUINT_TO_POINTER (handle))) {
1776                         /* We don't know this contact, skip */
1777                         continue;
1778                 }
1779
1780                 if (G_STR_EMPTY (alias)) {
1781                         alias = NULL;
1782                 }
1783
1784                 contact = empathy_tp_contact_list_get_from_handle (list, handle);
1785                 tp_contact_list_block_contact (list, contact);
1786                 empathy_contact_set_name (contact, alias);
1787                 tp_contact_list_unblock_contact (list, contact);
1788                 g_object_unref (contact);
1789
1790                 empathy_debug (DEBUG_DOMAIN, "contact %d renamed to %s (update cb)",
1791                               handle, alias);
1792         }
1793 }
1794
1795 static void
1796 tp_contact_list_request_aliases_cb (DBusGProxy                       *proxy,
1797                                     gchar                           **contact_names,
1798                                     GError                           *error,
1799                                     TpContactListAliasesRequestData  *data)
1800 {
1801         guint   i = 0;
1802         gchar **name;
1803
1804         if (error) {
1805                 empathy_debug (DEBUG_DOMAIN, "Error requesting aliases: %s",
1806                               error->message);
1807         }
1808
1809         for (name = contact_names; *name && !error; name++) {
1810                 EmpathyContact *contact;
1811
1812                 contact = empathy_tp_contact_list_get_from_handle (data->list,
1813                                                                    data->handles[i]);
1814                 tp_contact_list_block_contact (data->list, contact);
1815                 empathy_contact_set_name (contact, *name);
1816                 tp_contact_list_unblock_contact (data->list, contact);
1817                 g_object_unref (contact);
1818
1819                 empathy_debug (DEBUG_DOMAIN, "contact %d renamed to %s (request cb)",
1820                               data->handles[i], *name);
1821
1822                 i++;
1823         }
1824
1825         g_free (data->handles);
1826         g_slice_free (TpContactListAliasesRequestData, data);
1827 }
1828
1829 static void
1830 tp_contact_list_presence_update_cb (DBusGProxy           *proxy,
1831                                     GHashTable           *handle_table,
1832                                     EmpathyTpContactList *list)
1833 {
1834         g_hash_table_foreach (handle_table,
1835                               (GHFunc) tp_contact_list_parse_presence_foreach,
1836                               list);
1837 }
1838
1839 static void
1840 tp_contact_list_parse_presence_foreach (guint                 handle,
1841                                         GValueArray          *presence_struct,
1842                                         EmpathyTpContactList *list)
1843 {
1844         EmpathyTpContactListPriv *priv;
1845         GHashTable     *presences_table;
1846         EmpathyContact  *contact;
1847         EmpathyPresence *presence = NULL;
1848
1849         priv = GET_PRIV (list);
1850
1851         if (!g_hash_table_lookup (priv->contacts, GUINT_TO_POINTER (handle))) {
1852                 /* We don't know this contact, skip */
1853                 return;
1854         }
1855
1856         contact = empathy_tp_contact_list_get_from_handle (list, handle);
1857         presences_table = g_value_get_boxed (g_value_array_get_nth (presence_struct, 1));
1858
1859         g_hash_table_foreach (presences_table,
1860                               (GHFunc) tp_contact_list_presences_table_foreach,
1861                               &presence);
1862
1863         empathy_debug (DEBUG_DOMAIN, "Presence changed for %s (%d) to %s (%d)",
1864                       empathy_contact_get_name (contact),
1865                       handle,
1866                       presence ? empathy_presence_get_status (presence) : "unset",
1867                       presence ? empathy_presence_get_state (presence) : MC_PRESENCE_UNSET);
1868
1869         tp_contact_list_block_contact (list, contact);
1870         empathy_contact_set_presence (contact, presence);
1871         tp_contact_list_unblock_contact (list, contact);
1872
1873         g_object_unref (contact);
1874 }
1875
1876 static void
1877 tp_contact_list_presences_table_foreach (const gchar     *state_str,
1878                                          GHashTable      *presences_table,
1879                                          EmpathyPresence **presence)
1880 {
1881         McPresence    state;
1882         const GValue *message;
1883
1884         state = empathy_presence_state_from_str (state_str);
1885         if ((state == MC_PRESENCE_UNSET) || (state == MC_PRESENCE_OFFLINE)) {
1886                 return;
1887         }
1888
1889         if (*presence) {
1890                 g_object_unref (*presence);
1891                 *presence = NULL;
1892         }
1893
1894         *presence = empathy_presence_new ();
1895         empathy_presence_set_state (*presence, state);
1896
1897         message = g_hash_table_lookup (presences_table, "message");
1898         if (message != NULL) {
1899                 empathy_presence_set_status (*presence,
1900                                             g_value_get_string (message));
1901         }
1902 }
1903
1904 static void
1905 tp_contact_list_status_changed_cb (MissionControl                  *mc,
1906                                    TelepathyConnectionStatus        status,
1907                                    McPresence                       presence,
1908                                    TelepathyConnectionStatusReason  reason,
1909                                    const gchar                     *unique_name,
1910                                    EmpathyTpContactList            *list)
1911 {
1912         EmpathyTpContactListPriv *priv;
1913         McAccount                *account;
1914
1915         priv = GET_PRIV (list);
1916
1917         account = mc_account_lookup (unique_name);
1918         if (status != TP_CONN_STATUS_DISCONNECTED ||
1919             !empathy_account_equal (account, priv->account) ||
1920             !priv->tp_conn) {
1921                 g_object_unref (account);
1922                 return;
1923         }
1924
1925         /* We are disconnected, do just like if the connection was destroyed */
1926         g_signal_handlers_disconnect_by_func (priv->tp_conn,
1927                                               tp_contact_list_destroy_cb,
1928                                               list);
1929         tp_contact_list_destroy_cb (DBUS_G_PROXY (priv->tp_conn), list);
1930
1931         g_object_unref (account);
1932 }
1933