]> git.0d.be Git - empathy.git/blob - libempathy/empathy-individual-manager.c
disable clutter deprecation warnings for now
[empathy.git] / libempathy / empathy-individual-manager.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007-2010 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  *          Travis Reitter <travis.reitter@collabora.co.uk>
21  */
22
23 #include <config.h>
24
25 #include <string.h>
26
27 #include <telepathy-glib/account-manager.h>
28 #include <telepathy-glib/enums.h>
29 #include <telepathy-glib/proxy-subclass.h>
30 #include <telepathy-glib/util.h>
31
32 #include <folks/folks.h>
33 #include <folks/folks-telepathy.h>
34
35 #include <extensions/extensions.h>
36
37 #include "empathy-individual-manager.h"
38 #include "empathy-utils.h"
39
40 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
41 #include "empathy-debug.h"
42
43 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualManager)
44
45 /* This class only stores and refs Individuals who contain an EmpathyContact.
46  *
47  * This class merely forwards along signals from the aggregator and individuals
48  * and wraps aggregator functions for other client code. */
49 typedef struct
50 {
51   FolksIndividualAggregator *aggregator;
52   GHashTable *individuals; /* Individual.id -> Individual */
53 } EmpathyIndividualManagerPriv;
54
55 enum
56 {
57   FAVOURITES_CHANGED,
58   GROUPS_CHANGED,
59   MEMBERS_CHANGED,
60   LAST_SIGNAL
61 };
62
63 static guint signals[LAST_SIGNAL] = { 0 };
64
65 G_DEFINE_TYPE (EmpathyIndividualManager, empathy_individual_manager,
66     G_TYPE_OBJECT);
67
68 static EmpathyIndividualManager *manager_singleton = NULL;
69
70 static void
71 individual_group_changed_cb (FolksIndividual *individual,
72     gchar *group,
73     gboolean is_member,
74     EmpathyIndividualManager *self)
75 {
76   g_signal_emit (self, signals[GROUPS_CHANGED], 0, individual, group,
77       is_member);
78 }
79
80 static void
81 individual_notify_is_favourite_cb (FolksIndividual *individual,
82     GParamSpec *pspec,
83     EmpathyIndividualManager *self)
84 {
85   gboolean is_favourite = folks_favourite_details_get_is_favourite (
86       FOLKS_FAVOURITE_DETAILS (individual));
87   g_signal_emit (self, signals[FAVOURITES_CHANGED], 0, individual,
88       is_favourite);
89 }
90
91 static void
92 add_individual (EmpathyIndividualManager *self, FolksIndividual *individual)
93 {
94   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
95
96   g_hash_table_insert (priv->individuals,
97       g_strdup (folks_individual_get_id (individual)),
98       g_object_ref (individual));
99
100   g_signal_connect (individual, "group-changed",
101       G_CALLBACK (individual_group_changed_cb), self);
102   g_signal_connect (individual, "notify::is-favourite",
103       G_CALLBACK (individual_notify_is_favourite_cb), self);
104 }
105
106 static void
107 remove_individual (EmpathyIndividualManager *self, FolksIndividual *individual)
108 {
109   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
110
111   g_signal_handlers_disconnect_by_func (individual,
112       individual_group_changed_cb, self);
113   g_signal_handlers_disconnect_by_func (individual,
114       individual_notify_is_favourite_cb, self);
115
116   g_hash_table_remove (priv->individuals, folks_individual_get_id (individual));
117 }
118
119 /* This is emitted for *all* individuals in the individual aggregator (not
120  * just the ones we keep a reference to), to allow for the case where a new
121  * individual doesn't contain an EmpathyContact, but later has a persona added
122  * which does. */
123 static void
124 individual_notify_personas_cb (FolksIndividual *individual,
125     GParamSpec *pspec,
126     EmpathyIndividualManager *self)
127 {
128   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
129
130   const gchar *id = folks_individual_get_id (individual);
131   gboolean has_contact = empathy_folks_individual_contains_contact (individual);
132   gboolean had_contact = (g_hash_table_lookup (priv->individuals,
133       id) != NULL) ? TRUE : FALSE;
134
135   if (had_contact == TRUE && has_contact == FALSE)
136     {
137       GList *removed = NULL;
138
139       /* The Individual has lost its EmpathyContact */
140       removed = g_list_prepend (removed, individual);
141       g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, NULL, removed,
142           TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
143       g_list_free (removed);
144
145       remove_individual (self, individual);
146     }
147   else if (had_contact == FALSE && has_contact == TRUE)
148     {
149       GList *added = NULL;
150
151       /* The Individual has gained its first EmpathyContact */
152       add_individual (self, individual);
153
154       added = g_list_prepend (added, individual);
155       g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, added, NULL,
156           TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
157       g_list_free (added);
158     }
159 }
160
161 static void
162 aggregator_individuals_changed_cb (FolksIndividualAggregator *aggregator,
163     GeeMultiMap *changes,
164     EmpathyIndividualManager *self)
165 {
166   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
167   GeeIterator *iter;
168   GeeSet *removed;
169   GeeCollection *added;
170   GList *added_set = NULL, *added_filtered = NULL, *removed_list = NULL;
171
172   /* We're not interested in the relationships between the added and removed
173    * individuals, so just extract collections of them. Note that the added
174    * collection may contain duplicates, while the removed set won't. */
175   removed = gee_multi_map_get_keys (changes);
176   added = gee_multi_map_get_values (changes);
177
178   /* Handle the removals first, as one of the added Individuals might have the
179    * same ID as one of the removed Individuals (due to linking). */
180   iter = gee_iterable_iterator (GEE_ITERABLE (removed));
181   while (gee_iterator_next (iter))
182     {
183       FolksIndividual *ind = gee_iterator_get (iter);
184
185       if (ind == NULL)
186         continue;
187
188       g_signal_handlers_disconnect_by_func (ind,
189           individual_notify_personas_cb, self);
190
191       if (g_hash_table_lookup (priv->individuals,
192           folks_individual_get_id (ind)) != NULL)
193         {
194           remove_individual (self, ind);
195           removed_list = g_list_prepend (removed_list, ind);
196         }
197
198       g_clear_object (&ind);
199     }
200   g_clear_object (&iter);
201
202   /* Filter the individuals for ones which contain EmpathyContacts */
203   iter = gee_iterable_iterator (GEE_ITERABLE (added));
204   while (gee_iterator_next (iter))
205     {
206       FolksIndividual *ind = gee_iterator_get (iter);
207
208       /* Make sure we handle each added individual only once. */
209       if (ind == NULL || g_list_find (added_set, ind) != NULL)
210         continue;
211       added_set = g_list_prepend (added_set, ind);
212
213       g_signal_connect (ind, "notify::personas",
214           G_CALLBACK (individual_notify_personas_cb), self);
215
216       if (empathy_folks_individual_contains_contact (ind) == TRUE)
217         {
218           add_individual (self, ind);
219           added_filtered = g_list_prepend (added_filtered, ind);
220         }
221
222       g_clear_object (&ind);
223     }
224   g_clear_object (&iter);
225
226   g_list_free (added_set);
227
228   g_object_unref (added);
229   g_object_unref (removed);
230
231   /* Bail if we have no individuals left */
232   if (added_filtered == NULL && removed == NULL)
233     return;
234
235   added_filtered = g_list_reverse (added_filtered);
236
237   g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL,
238       added_filtered, removed_list,
239       TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
240       TRUE);
241
242   g_list_free (added_filtered);
243   g_list_free (removed_list);
244 }
245
246 static void
247 individual_manager_dispose (GObject *object)
248 {
249   EmpathyIndividualManagerPriv *priv = GET_PRIV (object);
250
251   g_hash_table_unref (priv->individuals);
252
253   g_signal_handlers_disconnect_by_func (priv->aggregator,
254       aggregator_individuals_changed_cb, object);
255   tp_clear_object (&priv->aggregator);
256
257   G_OBJECT_CLASS (empathy_individual_manager_parent_class)->dispose (object);
258 }
259
260 static GObject *
261 individual_manager_constructor (GType type,
262     guint n_props,
263     GObjectConstructParam *props)
264 {
265   GObject *retval;
266
267   if (manager_singleton)
268     {
269       retval = g_object_ref (manager_singleton);
270     }
271   else
272     {
273       retval =
274           G_OBJECT_CLASS (empathy_individual_manager_parent_class)->
275           constructor (type, n_props, props);
276
277       manager_singleton = EMPATHY_INDIVIDUAL_MANAGER (retval);
278       g_object_add_weak_pointer (retval, (gpointer) & manager_singleton);
279     }
280
281   return retval;
282 }
283
284 /**
285  * empathy_individual_manager_initialized:
286  *
287  * Reports whether or not the singleton has already been created.
288  *
289  * There can be instances where you want to access the #EmpathyIndividualManager
290  * only if it has been set up for this process.
291  *
292  * Returns: %TRUE if the #EmpathyIndividualManager singleton has previously
293  * been initialized.
294  */
295 gboolean
296 empathy_individual_manager_initialized (void)
297 {
298   return (manager_singleton != NULL);
299 }
300
301 static void
302 empathy_individual_manager_class_init (EmpathyIndividualManagerClass *klass)
303 {
304   GObjectClass *object_class = G_OBJECT_CLASS (klass);
305
306   object_class->dispose = individual_manager_dispose;
307   object_class->constructor = individual_manager_constructor;
308
309   signals[GROUPS_CHANGED] =
310       g_signal_new ("groups-changed",
311           G_TYPE_FROM_CLASS (klass),
312           G_SIGNAL_RUN_LAST,
313           0,
314           NULL, NULL,
315           g_cclosure_marshal_generic,
316           G_TYPE_NONE, 3, FOLKS_TYPE_INDIVIDUAL, G_TYPE_STRING, G_TYPE_BOOLEAN);
317
318   signals[FAVOURITES_CHANGED] =
319       g_signal_new ("favourites-changed",
320           G_TYPE_FROM_CLASS (klass),
321           G_SIGNAL_RUN_LAST,
322           0,
323           NULL, NULL,
324           g_cclosure_marshal_generic,
325           G_TYPE_NONE, 2, FOLKS_TYPE_INDIVIDUAL, G_TYPE_BOOLEAN);
326
327   signals[MEMBERS_CHANGED] =
328       g_signal_new ("members-changed",
329           G_TYPE_FROM_CLASS (klass),
330           G_SIGNAL_RUN_LAST,
331           0,
332           NULL, NULL,
333           g_cclosure_marshal_generic,
334           G_TYPE_NONE,
335           4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_UINT);
336
337   g_type_class_add_private (object_class,
338       sizeof (EmpathyIndividualManagerPriv));
339 }
340
341 static void
342 empathy_individual_manager_init (EmpathyIndividualManager *self)
343 {
344   EmpathyIndividualManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
345       EMPATHY_TYPE_INDIVIDUAL_MANAGER, EmpathyIndividualManagerPriv);
346
347   self->priv = priv;
348   priv->individuals = g_hash_table_new_full (g_str_hash, g_str_equal,
349       g_free, g_object_unref);
350
351   priv->aggregator = folks_individual_aggregator_new ();
352   g_signal_connect (priv->aggregator, "individuals-changed-detailed",
353       G_CALLBACK (aggregator_individuals_changed_cb), self);
354   folks_individual_aggregator_prepare (priv->aggregator, NULL, NULL);
355 }
356
357 EmpathyIndividualManager *
358 empathy_individual_manager_dup_singleton (void)
359 {
360   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MANAGER, NULL);
361 }
362
363 GList *
364 empathy_individual_manager_get_members (EmpathyIndividualManager *self)
365 {
366   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
367
368   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
369
370   return g_hash_table_get_values (priv->individuals);
371 }
372
373 FolksIndividual *
374 empathy_individual_manager_lookup_member (EmpathyIndividualManager *self,
375     const gchar *id)
376 {
377   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
378
379   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
380
381   return g_hash_table_lookup (priv->individuals, id);
382 }
383
384 static void
385 aggregator_add_persona_from_details_cb (GObject *source,
386     GAsyncResult *result,
387     gpointer user_data)
388 {
389   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
390   EmpathyContact *contact = EMPATHY_CONTACT (user_data);
391   FolksPersona *persona;
392   GError *error = NULL;
393
394   persona = folks_individual_aggregator_add_persona_from_details_finish (
395       aggregator, result, &error);
396   if (error != NULL)
397     {
398       g_warning ("failed to add individual from contact: %s", error->message);
399       g_clear_error (&error);
400     }
401
402   /* The persona can be NULL even if there wasn't an error, if the persona was
403    * already in the contact list */
404   if (persona != NULL)
405     {
406       /* Set the contact's persona */
407       empathy_contact_set_persona (contact, persona);
408       g_object_unref (persona);
409     }
410
411   g_object_unref (contact);
412 }
413
414 void
415 empathy_individual_manager_add_from_contact (EmpathyIndividualManager *self,
416     EmpathyContact *contact)
417 {
418   EmpathyIndividualManagerPriv *priv;
419   FolksBackendStore *backend_store;
420   FolksBackend *backend;
421   FolksPersonaStore *persona_store;
422   GHashTable* details;
423   GeeMap *persona_stores;
424   TpAccount *account;
425   const gchar *store_id;
426
427   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
428   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
429
430   priv = GET_PRIV (self);
431
432   /* We need to ref the contact since otherwise its linked TpHandle will be
433    * destroyed. */
434   g_object_ref (contact);
435
436   DEBUG ("adding individual from contact %s (%s)",
437       empathy_contact_get_id (contact), empathy_contact_get_alias (contact));
438
439   account = empathy_contact_get_account (contact);
440   store_id = tp_proxy_get_object_path (TP_PROXY (account));
441
442   /* Get the persona store to use */
443   backend_store = folks_backend_store_dup ();
444   backend =
445       folks_backend_store_dup_backend_by_name (backend_store, "telepathy");
446
447   if (backend == NULL)
448     {
449       g_warning ("Failed to add individual from contact: couldn't get "
450           "'telepathy' backend");
451       goto finish;
452     }
453
454   persona_stores = folks_backend_get_persona_stores (backend);
455   persona_store = gee_map_get (persona_stores, store_id);
456
457   if (persona_store == NULL)
458     {
459       g_warning ("Failed to add individual from contact: couldn't get persona "
460           "store '%s'", store_id);
461       goto finish;
462     }
463
464   details = tp_asv_new (
465       "contact", G_TYPE_STRING, empathy_contact_get_id (contact),
466       NULL);
467
468   folks_individual_aggregator_add_persona_from_details (
469       priv->aggregator, NULL, persona_store, details,
470       aggregator_add_persona_from_details_cb, contact);
471
472   g_hash_table_unref (details);
473   g_object_unref (persona_store);
474
475 finish:
476   tp_clear_object (&backend);
477   tp_clear_object (&backend_store);
478 }
479
480 static void
481 aggregator_remove_individual_cb (GObject *source,
482     GAsyncResult *result,
483     gpointer user_data)
484 {
485   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
486   GError *error = NULL;
487
488   folks_individual_aggregator_remove_individual_finish (
489       aggregator, result, &error);
490   if (error != NULL)
491     {
492       g_warning ("failed to remove individual: %s", error->message);
493       g_clear_error (&error);
494     }
495 }
496
497 /**
498  * Removes the inner contact from the server (and thus the Individual). Not
499  * meant for de-shelling inner personas from an Individual.
500  */
501 void
502 empathy_individual_manager_remove (EmpathyIndividualManager *self,
503     FolksIndividual *individual,
504     const gchar *message)
505 {
506   EmpathyIndividualManagerPriv *priv;
507
508   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
509   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
510
511   priv = GET_PRIV (self);
512
513   DEBUG ("removing individual %s (%s)",
514       folks_individual_get_id (individual),
515       folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
516
517   folks_individual_aggregator_remove_individual (priv->aggregator, individual,
518       aggregator_remove_individual_cb, self);
519 }
520
521 /* FIXME: The parameter @self is not required and the method can be placed in
522  * utilities. I left it as it is to stay coherent with empathy-2.34 */
523 /**
524  * empathy_individual_manager_supports_blocking
525  * @self: the #EmpathyIndividualManager
526  * @individual: an individual to check
527  *
528  * Indicates whether any personas of an @individual can be blocked.
529  *
530  * Returns: %TRUE if any persona supports contact blocking
531  */
532 gboolean
533 empathy_individual_manager_supports_blocking (EmpathyIndividualManager *self,
534     FolksIndividual *individual)
535 {
536   GeeSet *personas;
537   GeeIterator *iter;
538   gboolean retval = FALSE;
539
540   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), FALSE);
541
542   personas = folks_individual_get_personas (individual);
543   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
544   while (!retval && gee_iterator_next (iter))
545     {
546       TpfPersona *persona = gee_iterator_get (iter);
547       TpConnection *conn;
548
549       if (TPF_IS_PERSONA (persona))
550         {
551           TpContact *tp_contact;
552
553           tp_contact = tpf_persona_get_contact (persona);
554           if (tp_contact != NULL)
555             {
556               conn = tp_contact_get_connection (tp_contact);
557
558               if (tp_proxy_has_interface_by_id (conn,
559                     TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING))
560                 retval = TRUE;
561             }
562         }
563       g_clear_object (&persona);
564     }
565   g_clear_object (&iter);
566
567   return retval;
568 }
569
570 void
571 empathy_individual_manager_set_blocked (EmpathyIndividualManager *self,
572     FolksIndividual *individual,
573     gboolean blocked,
574     gboolean abusive)
575 {
576   GeeSet *personas;
577   GeeIterator *iter;
578
579   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
580
581   personas = folks_individual_get_personas (individual);
582   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
583   while (gee_iterator_next (iter))
584     {
585       TpfPersona *persona = gee_iterator_get (iter);
586
587       if (TPF_IS_PERSONA (persona))
588         {
589           TpContact *tp_contact;
590           TpConnection *conn;
591
592           tp_contact = tpf_persona_get_contact (persona);
593           if (tp_contact == NULL)
594             continue;
595
596           conn = tp_contact_get_connection (tp_contact);
597
598           if (!tp_proxy_has_interface_by_id (conn,
599                 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING))
600             continue;
601
602           if (blocked)
603             tp_contact_block_async (tp_contact, abusive, NULL, NULL);
604           else
605             tp_contact_unblock_async (tp_contact, NULL, NULL);
606         }
607       g_clear_object (&persona);
608     }
609   g_clear_object (&iter);
610 }
611
612 static void
613 groups_change_group_cb (GObject *source,
614     GAsyncResult *result,
615     gpointer user_data)
616 {
617   FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
618   GError *error = NULL;
619
620   folks_group_details_change_group_finish (group_details, result, &error);
621   if (error != NULL)
622     {
623       g_warning ("failed to change group: %s", error->message);
624       g_clear_error (&error);
625     }
626 }
627
628 static void
629 remove_group_cb (const gchar *id,
630     FolksIndividual *individual,
631     const gchar *group)
632 {
633   folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual), group,
634       FALSE, groups_change_group_cb, NULL);
635 }
636
637 void
638 empathy_individual_manager_remove_group (EmpathyIndividualManager *manager,
639     const gchar *group)
640 {
641   EmpathyIndividualManagerPriv *priv;
642
643   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager));
644   g_return_if_fail (group != NULL);
645
646   priv = GET_PRIV (manager);
647
648   DEBUG ("removing group %s", group);
649
650   /* Remove every individual from the group */
651   g_hash_table_foreach (priv->individuals, (GHFunc) remove_group_cb,
652       (gpointer) group);
653 }
654
655 static void
656 link_personas_cb (FolksIndividualAggregator *aggregator,
657     GAsyncResult *async_result,
658     gpointer user_data)
659 {
660   GError *error = NULL;
661
662   folks_individual_aggregator_link_personas_finish (aggregator, async_result,
663       &error);
664
665   if (error != NULL)
666     {
667       g_warning ("Failed to link personas: %s", error->message);
668       g_clear_error (&error);
669     }
670 }
671
672 void
673 empathy_individual_manager_link_personas (EmpathyIndividualManager *self,
674     GeeSet *personas)
675 {
676   EmpathyIndividualManagerPriv *priv;
677
678   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
679   g_return_if_fail (personas != NULL);
680
681   priv = GET_PRIV (self);
682
683   DEBUG ("Linking %u personas",
684       gee_collection_get_size (GEE_COLLECTION (personas)));
685
686   folks_individual_aggregator_link_personas (priv->aggregator, personas,
687       (GAsyncReadyCallback) link_personas_cb, NULL);
688 }
689
690 static void
691 unlink_individual_cb (FolksIndividualAggregator *aggregator,
692     GAsyncResult *async_result,
693     gpointer user_data)
694 {
695   GError *error = NULL;
696
697   folks_individual_aggregator_unlink_individual_finish (aggregator,
698       async_result, &error);
699
700   if (error != NULL)
701     {
702       g_warning ("Failed to unlink individual: %s", error->message);
703       g_clear_error (&error);
704     }
705 }
706
707 void
708 empathy_individual_manager_unlink_individual (EmpathyIndividualManager *self,
709     FolksIndividual *individual)
710 {
711   EmpathyIndividualManagerPriv *priv;
712
713   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
714   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
715
716   priv = GET_PRIV (self);
717
718   DEBUG ("Unlinking individual '%s'", folks_individual_get_id (individual));
719
720   folks_individual_aggregator_unlink_individual (priv->aggregator, individual,
721       (GAsyncReadyCallback) unlink_individual_cb, NULL);
722 }