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