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