]> git.0d.be Git - empathy.git/blob - libempathy/empathy-individual-manager.c
Add confirmation dialog to Remove
[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-marshal.h"
39 #include "empathy-utils.h"
40
41 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
42 #include "empathy-debug.h"
43
44 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualManager)
45
46 /* This class only stores and refs Individuals who contain an EmpathyContact.
47  *
48  * This class merely forwards along signals from the aggregator and individuals
49  * and wraps aggregator functions for other client code. */
50 typedef struct
51 {
52   FolksIndividualAggregator *aggregator;
53   GHashTable *individuals; /* Individual.id -> Individual */
54 } EmpathyIndividualManagerPriv;
55
56 enum
57 {
58   FAVOURITES_CHANGED,
59   GROUPS_CHANGED,
60   MEMBERS_CHANGED,
61   LAST_SIGNAL
62 };
63
64 static guint signals[LAST_SIGNAL] = { 0 };
65
66 G_DEFINE_TYPE (EmpathyIndividualManager, empathy_individual_manager,
67     G_TYPE_OBJECT);
68
69 static EmpathyIndividualManager *manager_singleton = NULL;
70
71 static void
72 individual_group_changed_cb (FolksIndividual *individual,
73     gchar *group,
74     gboolean is_member,
75     EmpathyIndividualManager *self)
76 {
77   g_signal_emit (self, signals[GROUPS_CHANGED], 0, individual, group,
78       is_member);
79 }
80
81 static void
82 individual_notify_is_favourite_cb (FolksIndividual *individual,
83     GParamSpec *pspec,
84     EmpathyIndividualManager *self)
85 {
86   gboolean is_favourite = folks_favouritable_get_is_favourite (
87       FOLKS_FAVOURITABLE (individual));
88   g_signal_emit (self, signals[FAVOURITES_CHANGED], 0, individual,
89       is_favourite);
90 }
91
92 static void
93 add_individual (EmpathyIndividualManager *self, FolksIndividual *individual)
94 {
95   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
96
97   g_hash_table_insert (priv->individuals,
98       (gpointer) folks_individual_get_id (individual),
99       g_object_ref (individual));
100
101   g_signal_connect (individual, "group-changed",
102       G_CALLBACK (individual_group_changed_cb), self);
103   g_signal_connect (individual, "notify::is-favourite",
104       G_CALLBACK (individual_notify_is_favourite_cb), self);
105 }
106
107 static void
108 remove_individual (EmpathyIndividualManager *self, FolksIndividual *individual)
109 {
110   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
111
112   g_signal_handlers_disconnect_by_func (individual,
113       individual_group_changed_cb, self);
114   g_signal_handlers_disconnect_by_func (individual,
115       individual_notify_is_favourite_cb, self);
116
117   g_hash_table_remove (priv->individuals, folks_individual_get_id (individual));
118 }
119
120 /* This is emitted for *all* individuals in the individual aggregator (not
121  * just the ones we keep a reference to), to allow for the case where a new
122  * individual doesn't contain an EmpathyContact, but later has a persona added
123  * which does. */
124 static void
125 individual_notify_personas_cb (FolksIndividual *individual,
126     GParamSpec *pspec,
127     EmpathyIndividualManager *self)
128 {
129   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
130
131   const gchar *id = folks_individual_get_id (individual);
132   gboolean has_contact = empathy_folks_individual_contains_contact (individual);
133   gboolean had_contact = (g_hash_table_lookup (priv->individuals,
134       id) != NULL) ? TRUE : FALSE;
135
136   if (had_contact == TRUE && has_contact == FALSE)
137     {
138       GList *removed = NULL;
139
140       /* The Individual has lost its EmpathyContact */
141       removed = g_list_prepend (removed, individual);
142       g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, NULL, removed,
143           TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
144       g_list_free (removed);
145
146       remove_individual (self, individual);
147     }
148   else if (had_contact == FALSE && has_contact == TRUE)
149     {
150       GList *added = NULL;
151
152       /* The Individual has gained its first EmpathyContact */
153       add_individual (self, individual);
154
155       added = g_list_prepend (added, individual);
156       g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, added, NULL,
157           TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
158       g_list_free (added);
159     }
160 }
161
162 static void
163 aggregator_individuals_changed_cb (FolksIndividualAggregator *aggregator,
164     GList *added,
165     GList *removed,
166     const char *message,
167     FolksPersona *actor,
168     guint reason,
169     EmpathyIndividualManager *self)
170 {
171   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
172   GList *l, *added_filtered = NULL;
173
174   /* Handle the removals first, as one of the added Individuals might have the
175    * same ID as one of the removed Individuals (due to linking). */
176   for (l = removed; l; l = l->next)
177     {
178       FolksIndividual *ind = FOLKS_INDIVIDUAL (l->data);
179
180       g_signal_handlers_disconnect_by_func (ind,
181           individual_notify_personas_cb, self);
182
183       if (g_hash_table_lookup (priv->individuals,
184           folks_individual_get_id (ind)) != NULL)
185         remove_individual (self, ind);
186     }
187
188   /* Filter the individuals for ones which contain EmpathyContacts */
189   for (l = added; l; l = l->next)
190     {
191       FolksIndividual *ind = FOLKS_INDIVIDUAL (l->data);
192
193       g_signal_connect (ind, "notify::personas",
194           G_CALLBACK (individual_notify_personas_cb), self);
195
196       if (empathy_folks_individual_contains_contact (ind) == TRUE)
197         {
198           add_individual (self, ind);
199           added_filtered = g_list_prepend (added_filtered, ind);
200         }
201     }
202
203   /* Bail if we have no individuals left */
204   if (added_filtered == NULL && removed == NULL)
205     return;
206
207   added_filtered = g_list_reverse (added_filtered);
208
209   g_signal_emit (self, signals[MEMBERS_CHANGED], 0, message,
210       added_filtered, removed,
211       tp_channel_group_change_reason_from_folks_groups_change_reason (reason),
212       TRUE);
213
214   g_list_free (added_filtered);
215 }
216
217 static void
218 individual_manager_dispose (GObject *object)
219 {
220   EmpathyIndividualManagerPriv *priv = GET_PRIV (object);
221
222   g_hash_table_destroy (priv->individuals);
223   tp_clear_object (&priv->aggregator);
224
225   G_OBJECT_CLASS (empathy_individual_manager_parent_class)->dispose (object);
226 }
227
228 static GObject *
229 individual_manager_constructor (GType type,
230     guint n_props,
231     GObjectConstructParam *props)
232 {
233   GObject *retval;
234
235   if (manager_singleton)
236     {
237       retval = g_object_ref (manager_singleton);
238     }
239   else
240     {
241       retval =
242           G_OBJECT_CLASS (empathy_individual_manager_parent_class)->
243           constructor (type, n_props, props);
244
245       manager_singleton = EMPATHY_INDIVIDUAL_MANAGER (retval);
246       g_object_add_weak_pointer (retval, (gpointer) & manager_singleton);
247     }
248
249   return retval;
250 }
251
252 /**
253  * empathy_individual_manager_initialized:
254  *
255  * Reports whether or not the singleton has already been created.
256  *
257  * There can be instances where you want to access the #EmpathyIndividualManager
258  * only if it has been set up for this process.
259  *
260  * Returns: %TRUE if the #EmpathyIndividualManager singleton has previously
261  * been initialized.
262  */
263 gboolean
264 empathy_individual_manager_initialized (void)
265 {
266   return (manager_singleton != NULL);
267 }
268
269 static void
270 empathy_individual_manager_class_init (EmpathyIndividualManagerClass *klass)
271 {
272   GObjectClass *object_class = G_OBJECT_CLASS (klass);
273
274   object_class->dispose = individual_manager_dispose;
275   object_class->constructor = individual_manager_constructor;
276
277   signals[GROUPS_CHANGED] =
278       g_signal_new ("groups-changed",
279           G_TYPE_FROM_CLASS (klass),
280           G_SIGNAL_RUN_LAST,
281           0,
282           NULL, NULL,
283           _empathy_marshal_VOID__OBJECT_STRING_BOOLEAN,
284           G_TYPE_NONE, 3, FOLKS_TYPE_INDIVIDUAL, G_TYPE_STRING, G_TYPE_BOOLEAN);
285
286   signals[FAVOURITES_CHANGED] =
287       g_signal_new ("favourites-changed",
288           G_TYPE_FROM_CLASS (klass),
289           G_SIGNAL_RUN_LAST,
290           0,
291           NULL, NULL,
292           _empathy_marshal_VOID__OBJECT_BOOLEAN,
293           G_TYPE_NONE, 2, FOLKS_TYPE_INDIVIDUAL, G_TYPE_BOOLEAN);
294
295   signals[MEMBERS_CHANGED] =
296       g_signal_new ("members-changed",
297           G_TYPE_FROM_CLASS (klass),
298           G_SIGNAL_RUN_LAST,
299           0,
300           NULL, NULL,
301           _empathy_marshal_VOID__STRING_OBJECT_OBJECT_UINT,
302           G_TYPE_NONE,
303           4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_UINT);
304
305   g_type_class_add_private (object_class,
306       sizeof (EmpathyIndividualManagerPriv));
307 }
308
309 static void
310 empathy_individual_manager_init (EmpathyIndividualManager *self)
311 {
312   EmpathyIndividualManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
313       EMPATHY_TYPE_INDIVIDUAL_MANAGER, EmpathyIndividualManagerPriv);
314
315   self->priv = priv;
316   priv->individuals = g_hash_table_new_full (g_str_hash, g_str_equal,
317       NULL, g_object_unref);
318
319   priv->aggregator = folks_individual_aggregator_new ();
320   g_signal_connect (priv->aggregator, "individuals-changed",
321       G_CALLBACK (aggregator_individuals_changed_cb), self);
322   folks_individual_aggregator_prepare (priv->aggregator, NULL, NULL);
323 }
324
325 EmpathyIndividualManager *
326 empathy_individual_manager_dup_singleton (void)
327 {
328   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MANAGER, NULL);
329 }
330
331 GList *
332 empathy_individual_manager_get_members (EmpathyIndividualManager *self)
333 {
334   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
335
336   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
337
338   return g_hash_table_get_values (priv->individuals);
339 }
340
341 FolksIndividual *
342 empathy_individual_manager_lookup_member (EmpathyIndividualManager *self,
343     const gchar *id)
344 {
345   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
346
347   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
348
349   return g_hash_table_lookup (priv->individuals, id);
350 }
351
352 static void
353 aggregator_add_persona_from_details_cb (GObject *source,
354     GAsyncResult *result,
355     gpointer user_data)
356 {
357   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
358   EmpathyContact *contact = EMPATHY_CONTACT (user_data);
359   FolksPersona *persona;
360   GError *error = NULL;
361
362   persona = folks_individual_aggregator_add_persona_from_details_finish (
363       aggregator, result, &error);
364   if (error != NULL)
365     {
366       g_warning ("failed to add individual from contact: %s", error->message);
367       g_clear_error (&error);
368     }
369
370   /* The persona can be NULL even if there wasn't an error, if the persona was
371    * already in the contact list */
372   if (persona != NULL)
373     {
374       /* Set the contact's persona */
375       empathy_contact_set_persona (contact, persona);
376       g_object_unref (persona);
377     }
378
379   g_object_unref (contact);
380 }
381
382 void
383 empathy_individual_manager_add_from_contact (EmpathyIndividualManager *self,
384     EmpathyContact *contact)
385 {
386   EmpathyIndividualManagerPriv *priv;
387   FolksBackendStore *backend_store;
388   FolksBackend *backend;
389   FolksPersonaStore *persona_store;
390   GHashTable* details, *persona_stores;
391   TpAccount *account;
392   const gchar *store_id;
393
394   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
395   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
396
397   priv = GET_PRIV (self);
398
399   /* We need to ref the contact since otherwise its linked TpHandle will be
400    * destroyed. */
401   g_object_ref (contact);
402
403   DEBUG ("adding individual from contact %s (%s)",
404       empathy_contact_get_id (contact), empathy_contact_get_alias (contact));
405
406   account = empathy_contact_get_account (contact);
407   store_id = tp_proxy_get_object_path (TP_PROXY (account));
408
409   /* Get the persona store to use */
410   backend_store = folks_backend_store_dup ();
411   backend =
412       folks_backend_store_dup_backend_by_name (backend_store, "telepathy");
413
414   if (backend == NULL)
415     {
416       g_warning ("Failed to add individual from contact: couldn't get "
417           "'telepathy' backend");
418       goto finish;
419     }
420
421   persona_stores = folks_backend_get_persona_stores (backend);
422   persona_store = g_hash_table_lookup (persona_stores, store_id);
423
424   if (persona_store == NULL)
425     {
426       g_warning ("Failed to add individual from contact: couldn't get persona "
427           "store '%s'", store_id);
428       goto finish;
429     }
430
431   details = tp_asv_new (
432       "contact", G_TYPE_STRING, empathy_contact_get_id (contact),
433       NULL);
434
435   folks_individual_aggregator_add_persona_from_details (
436       priv->aggregator, NULL, persona_store, details,
437       aggregator_add_persona_from_details_cb, contact);
438
439   g_hash_table_destroy (details);
440
441 finish:
442   tp_clear_object (&backend);
443   tp_clear_object (&backend_store);
444 }
445
446 static void
447 aggregator_remove_individual_cb (GObject *source,
448     GAsyncResult *result,
449     gpointer user_data)
450 {
451   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
452   GError *error = NULL;
453
454   folks_individual_aggregator_remove_individual_finish (
455       aggregator, result, &error);
456   if (error != NULL)
457     {
458       g_warning ("failed to remove individual: %s", error->message);
459       g_clear_error (&error);
460     }
461 }
462
463 /**
464  * Removes the inner contact from the server (and thus the Individual). Not
465  * meant for de-shelling inner personas from an Individual.
466  */
467 void
468 empathy_individual_manager_remove (EmpathyIndividualManager *self,
469     FolksIndividual *individual,
470     const gchar *message)
471 {
472   EmpathyIndividualManagerPriv *priv;
473
474   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
475   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
476
477   priv = GET_PRIV (self);
478
479   DEBUG ("removing individual %s (%s)",
480       folks_individual_get_id (individual),
481       folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
482
483   folks_individual_aggregator_remove_individual (priv->aggregator, individual,
484       aggregator_remove_individual_cb, self);
485 }
486
487 /**
488  * empathy_individual_manager_supports_blocking
489  * @self: the #EmpathyIndividualManager
490  * @individual: an individual to check
491  *
492  * Indicates whether any personas of an @individual can be blocked.
493  *
494  * Returns: %TRUE if any persona supports contact blocking
495  */
496 gboolean
497 empathy_individual_manager_supports_blocking (EmpathyIndividualManager *self,
498     FolksIndividual *individual)
499 {
500   EmpathyIndividualManagerPriv *priv;
501   GList *personas, *l;
502
503   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), FALSE);
504
505   priv = GET_PRIV (self);
506
507   personas = folks_individual_get_personas (individual);
508
509   for (l = personas; l != NULL; l = l->next)
510     {
511       TpfPersona *persona = l->data;
512       TpConnection *conn;
513
514       if (!TPF_IS_PERSONA (persona))
515         continue;
516
517       conn = tp_contact_get_connection (tpf_persona_get_contact (persona));
518
519       if (empathy_individual_manager_get_flags_for_connection (self, conn) &
520           EMPATHY_INDIVIDUAL_MANAGER_CAN_BLOCK)
521         return TRUE;
522     }
523
524   return FALSE;
525 }
526
527 void
528 empathy_individual_manager_set_blocked (EmpathyIndividualManager *self,
529     FolksIndividual *individual,
530     gboolean blocked)
531 {
532   EmpathyIndividualManagerPriv *priv;
533   GList *personas, *l;
534
535   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
536
537   priv = GET_PRIV (self);
538
539   personas = folks_individual_get_personas (individual);
540
541   for (l = personas; l != NULL; l = l->next)
542     {
543       TpfPersona *persona = l->data;
544       EmpathyContact *contact;
545
546       if (!TPF_IS_PERSONA (persona))
547         continue;
548
549       contact = empathy_contact_dup_from_tp_contact (
550           tpf_persona_get_contact (persona));
551       empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
552       empathy_contact_list_set_blocked (
553           EMPATHY_CONTACT_LIST (priv->contact_manager),
554           contact, blocked);
555
556       g_object_unref (contact);
557     }
558 }
559
560 static void
561 groups_change_group_cb (GObject *source,
562     GAsyncResult *result,
563     gpointer user_data)
564 {
565   FolksGroupable *groupable = FOLKS_GROUPABLE (source);
566   GError *error = NULL;
567
568   folks_groupable_change_group_finish (groupable, result, &error);
569   if (error != NULL)
570     {
571       g_warning ("failed to change group: %s", error->message);
572       g_clear_error (&error);
573     }
574 }
575
576 static void
577 remove_group_cb (const gchar *id,
578     FolksIndividual *individual,
579     const gchar *group)
580 {
581   folks_groupable_change_group (FOLKS_GROUPABLE (individual), group, FALSE,
582       groups_change_group_cb, NULL);
583 }
584
585 void
586 empathy_individual_manager_remove_group (EmpathyIndividualManager *manager,
587     const gchar *group)
588 {
589   EmpathyIndividualManagerPriv *priv;
590
591   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager));
592   g_return_if_fail (group != NULL);
593
594   priv = GET_PRIV (manager);
595
596   DEBUG ("removing group %s", group);
597
598   /* Remove every individual from the group */
599   g_hash_table_foreach (priv->individuals, (GHFunc) remove_group_cb,
600       (gpointer) group);
601 }
602
603 EmpathyIndividualManagerFlags
604 empathy_individual_manager_get_flags_for_connection (
605     EmpathyIndividualManager *self,
606     TpConnection *connection)
607 {
608   EmpathyIndividualManagerPriv *priv;
609   EmpathyContactListFlags list_flags;
610   EmpathyIndividualManagerFlags flags;
611
612   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self),
613       EMPATHY_INDIVIDUAL_MANAGER_NO_FLAGS);
614
615   priv = GET_PRIV (self);
616
617   list_flags = empathy_contact_manager_get_flags_for_connection (
618     priv->contact_manager, connection);
619
620   flags = EMPATHY_INDIVIDUAL_MANAGER_NO_FLAGS;
621   if (list_flags & EMPATHY_CONTACT_LIST_CAN_ADD)
622     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_ADD;
623   if (list_flags & EMPATHY_CONTACT_LIST_CAN_REMOVE)
624     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE;
625   if (list_flags & EMPATHY_CONTACT_LIST_CAN_ALIAS)
626     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_ALIAS;
627   if (list_flags & EMPATHY_CONTACT_LIST_CAN_GROUP)
628     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_GROUP;
629   if (list_flags & EMPATHY_CONTACT_LIST_CAN_BLOCK)
630     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_BLOCK;
631   if (list_flags & EMPATHY_CONTACT_LIST_CAN_REPORT_ABUSIVE)
632     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_REPORT_ABUSIVE;
633
634   return flags;
635 }
636
637 static void
638 link_personas_cb (FolksIndividualAggregator *aggregator,
639     GAsyncResult *async_result,
640     gpointer user_data)
641 {
642   GError *error = NULL;
643
644   folks_individual_aggregator_link_personas_finish (aggregator, async_result,
645       &error);
646
647   if (error != NULL)
648     {
649       g_warning ("Failed to link personas: %s", error->message);
650       g_clear_error (&error);
651     }
652 }
653
654 void
655 empathy_individual_manager_link_personas (EmpathyIndividualManager *self,
656     GList *personas)
657 {
658   EmpathyIndividualManagerPriv *priv;
659
660   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
661   g_return_if_fail (personas != NULL);
662
663   priv = GET_PRIV (self);
664
665   DEBUG ("Linking %u personas", g_list_length (personas));
666
667   folks_individual_aggregator_link_personas (priv->aggregator, personas,
668       (GAsyncReadyCallback) link_personas_cb, NULL);
669 }
670
671 static void
672 unlink_individual_cb (FolksIndividualAggregator *aggregator,
673     GAsyncResult *async_result,
674     gpointer user_data)
675 {
676   GError *error = NULL;
677
678   folks_individual_aggregator_unlink_individual_finish (aggregator,
679       async_result, &error);
680
681   if (error != NULL)
682     {
683       g_warning ("Failed to unlink individual: %s", error->message);
684       g_clear_error (&error);
685     }
686 }
687
688 void
689 empathy_individual_manager_unlink_individual (EmpathyIndividualManager *self,
690     FolksIndividual *individual)
691 {
692   EmpathyIndividualManagerPriv *priv;
693
694   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
695   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
696
697   priv = GET_PRIV (self);
698
699   DEBUG ("Unlinking individual '%s'", folks_individual_get_id (individual));
700
701   folks_individual_aggregator_unlink_individual (priv->aggregator, individual,
702       (GAsyncReadyCallback) unlink_individual_cb, NULL);
703 }