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