]> git.0d.be Git - empathy.git/blob - libempathy/empathy-individual-manager.c
Merge remote-tracking branch 'glassrose/add-All-service-selection-in-debug-window'
[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   gboolean contacts_loaded;
54 } EmpathyIndividualManagerPriv;
55
56 enum
57 {
58   FAVOURITES_CHANGED,
59   GROUPS_CHANGED,
60   MEMBERS_CHANGED,
61   CONTACTS_LOADED,
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_unref (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           g_cclosure_marshal_generic,
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           g_cclosure_marshal_generic,
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           g_cclosure_marshal_generic,
336           G_TYPE_NONE,
337           4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_UINT);
338
339   signals[CONTACTS_LOADED] =
340       g_signal_new ("contacts-loaded",
341           G_TYPE_FROM_CLASS (klass),
342           G_SIGNAL_RUN_LAST,
343           0,
344           NULL, NULL,
345           g_cclosure_marshal_generic,
346           G_TYPE_NONE,
347           0);
348
349   g_type_class_add_private (object_class,
350       sizeof (EmpathyIndividualManagerPriv));
351 }
352
353 static void
354 aggregator_is_quiescent_notify_cb (FolksIndividualAggregator *aggregator,
355     GParamSpec *spec,
356     EmpathyIndividualManager *self)
357 {
358   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
359   gboolean is_quiescent;
360
361   if (priv->contacts_loaded)
362     return;
363
364   g_object_get (aggregator, "is-quiescent", &is_quiescent, NULL);
365
366   if (!is_quiescent)
367     return;
368
369   priv->contacts_loaded = TRUE;
370
371   g_signal_emit (self, signals[CONTACTS_LOADED], 0);
372 }
373
374 static void
375 empathy_individual_manager_init (EmpathyIndividualManager *self)
376 {
377   EmpathyIndividualManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
378       EMPATHY_TYPE_INDIVIDUAL_MANAGER, EmpathyIndividualManagerPriv);
379
380   self->priv = priv;
381   priv->individuals = g_hash_table_new_full (g_str_hash, g_str_equal,
382       g_free, g_object_unref);
383
384   priv->aggregator = folks_individual_aggregator_new ();
385   g_signal_connect (priv->aggregator, "individuals-changed-detailed",
386       G_CALLBACK (aggregator_individuals_changed_cb), self);
387   g_signal_connect (priv->aggregator, "notify::is-quiescent",
388       G_CALLBACK (aggregator_is_quiescent_notify_cb), self);
389   folks_individual_aggregator_prepare (priv->aggregator, NULL, NULL);
390 }
391
392 EmpathyIndividualManager *
393 empathy_individual_manager_dup_singleton (void)
394 {
395   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MANAGER, NULL);
396 }
397
398 GList *
399 empathy_individual_manager_get_members (EmpathyIndividualManager *self)
400 {
401   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
402
403   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
404
405   return g_hash_table_get_values (priv->individuals);
406 }
407
408 FolksIndividual *
409 empathy_individual_manager_lookup_member (EmpathyIndividualManager *self,
410     const gchar *id)
411 {
412   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
413
414   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
415
416   return g_hash_table_lookup (priv->individuals, id);
417 }
418
419 static void
420 aggregator_add_persona_from_details_cb (GObject *source,
421     GAsyncResult *result,
422     gpointer user_data)
423 {
424   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
425   EmpathyContact *contact = EMPATHY_CONTACT (user_data);
426   FolksPersona *persona;
427   GError *error = NULL;
428
429   persona = folks_individual_aggregator_add_persona_from_details_finish (
430       aggregator, result, &error);
431   if (error != NULL)
432     {
433       g_warning ("failed to add individual from contact: %s", error->message);
434       g_clear_error (&error);
435     }
436
437   /* The persona can be NULL even if there wasn't an error, if the persona was
438    * already in the contact list */
439   if (persona != NULL)
440     {
441       /* Set the contact's persona */
442       empathy_contact_set_persona (contact, persona);
443       g_object_unref (persona);
444     }
445
446   g_object_unref (contact);
447 }
448
449 void
450 empathy_individual_manager_add_from_contact (EmpathyIndividualManager *self,
451     EmpathyContact *contact)
452 {
453   EmpathyIndividualManagerPriv *priv;
454   FolksBackendStore *backend_store;
455   FolksBackend *backend;
456   FolksPersonaStore *persona_store;
457   GHashTable* details;
458   GeeMap *persona_stores;
459   TpAccount *account;
460   const gchar *store_id;
461
462   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
463   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
464
465   priv = GET_PRIV (self);
466
467   /* We need to ref the contact since otherwise its linked TpHandle will be
468    * destroyed. */
469   g_object_ref (contact);
470
471   DEBUG ("adding individual from contact %s (%s)",
472       empathy_contact_get_id (contact), empathy_contact_get_alias (contact));
473
474   account = empathy_contact_get_account (contact);
475   store_id = tp_proxy_get_object_path (TP_PROXY (account));
476
477   /* Get the persona store to use */
478   backend_store = folks_backend_store_dup ();
479   backend =
480       folks_backend_store_dup_backend_by_name (backend_store, "telepathy");
481
482   if (backend == NULL)
483     {
484       g_warning ("Failed to add individual from contact: couldn't get "
485           "'telepathy' backend");
486       goto finish;
487     }
488
489   persona_stores = folks_backend_get_persona_stores (backend);
490   persona_store = gee_map_get (persona_stores, store_id);
491
492   if (persona_store == NULL)
493     {
494       g_warning ("Failed to add individual from contact: couldn't get persona "
495           "store '%s'", store_id);
496       goto finish;
497     }
498
499   details = tp_asv_new (
500       "contact", G_TYPE_STRING, empathy_contact_get_id (contact),
501       NULL);
502
503   folks_individual_aggregator_add_persona_from_details (
504       priv->aggregator, NULL, persona_store, details,
505       aggregator_add_persona_from_details_cb, contact);
506
507   g_hash_table_unref (details);
508   g_object_unref (persona_store);
509
510 finish:
511   tp_clear_object (&backend);
512   tp_clear_object (&backend_store);
513 }
514
515 static void
516 aggregator_remove_individual_cb (GObject *source,
517     GAsyncResult *result,
518     gpointer user_data)
519 {
520   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
521   GError *error = NULL;
522
523   folks_individual_aggregator_remove_individual_finish (
524       aggregator, result, &error);
525   if (error != NULL)
526     {
527       g_warning ("failed to remove individual: %s", error->message);
528       g_clear_error (&error);
529     }
530 }
531
532 /**
533  * Removes the inner contact from the server (and thus the Individual). Not
534  * meant for de-shelling inner personas from an Individual.
535  */
536 void
537 empathy_individual_manager_remove (EmpathyIndividualManager *self,
538     FolksIndividual *individual,
539     const gchar *message)
540 {
541   EmpathyIndividualManagerPriv *priv;
542
543   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
544   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
545
546   priv = GET_PRIV (self);
547
548   DEBUG ("removing individual %s (%s)",
549       folks_individual_get_id (individual),
550       folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
551
552   folks_individual_aggregator_remove_individual (priv->aggregator, individual,
553       aggregator_remove_individual_cb, self);
554 }
555
556 /* FIXME: The parameter @self is not required and the method can be placed in
557  * utilities. I left it as it is to stay coherent with empathy-2.34 */
558 /**
559  * empathy_individual_manager_supports_blocking
560  * @self: the #EmpathyIndividualManager
561  * @individual: an individual to check
562  *
563  * Indicates whether any personas of an @individual can be blocked.
564  *
565  * Returns: %TRUE if any persona supports contact blocking
566  */
567 gboolean
568 empathy_individual_manager_supports_blocking (EmpathyIndividualManager *self,
569     FolksIndividual *individual)
570 {
571   GeeSet *personas;
572   GeeIterator *iter;
573   gboolean retval = FALSE;
574
575   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), FALSE);
576
577   personas = folks_individual_get_personas (individual);
578   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
579   while (!retval && gee_iterator_next (iter))
580     {
581       TpfPersona *persona = gee_iterator_get (iter);
582       TpConnection *conn;
583
584       if (TPF_IS_PERSONA (persona))
585         {
586           TpContact *tp_contact;
587
588           tp_contact = tpf_persona_get_contact (persona);
589           if (tp_contact != NULL)
590             {
591               conn = tp_contact_get_connection (tp_contact);
592
593               if (tp_proxy_has_interface_by_id (conn,
594                     TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING))
595                 retval = TRUE;
596             }
597         }
598       g_clear_object (&persona);
599     }
600   g_clear_object (&iter);
601
602   return retval;
603 }
604
605 void
606 empathy_individual_manager_set_blocked (EmpathyIndividualManager *self,
607     FolksIndividual *individual,
608     gboolean blocked,
609     gboolean abusive)
610 {
611   GeeSet *personas;
612   GeeIterator *iter;
613
614   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
615
616   personas = folks_individual_get_personas (individual);
617   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
618   while (gee_iterator_next (iter))
619     {
620       TpfPersona *persona = gee_iterator_get (iter);
621
622       if (TPF_IS_PERSONA (persona))
623         {
624           TpContact *tp_contact;
625           TpConnection *conn;
626
627           tp_contact = tpf_persona_get_contact (persona);
628           if (tp_contact == NULL)
629             continue;
630
631           conn = tp_contact_get_connection (tp_contact);
632
633           if (!tp_proxy_has_interface_by_id (conn,
634                 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING))
635             continue;
636
637           if (blocked)
638             tp_contact_block_async (tp_contact, abusive, NULL, NULL);
639           else
640             tp_contact_unblock_async (tp_contact, NULL, NULL);
641         }
642       g_clear_object (&persona);
643     }
644   g_clear_object (&iter);
645 }
646
647 static void
648 groups_change_group_cb (GObject *source,
649     GAsyncResult *result,
650     gpointer user_data)
651 {
652   FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
653   GError *error = NULL;
654
655   folks_group_details_change_group_finish (group_details, result, &error);
656   if (error != NULL)
657     {
658       g_warning ("failed to change group: %s", error->message);
659       g_clear_error (&error);
660     }
661 }
662
663 static void
664 remove_group_cb (const gchar *id,
665     FolksIndividual *individual,
666     const gchar *group)
667 {
668   folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual), group,
669       FALSE, groups_change_group_cb, NULL);
670 }
671
672 void
673 empathy_individual_manager_remove_group (EmpathyIndividualManager *manager,
674     const gchar *group)
675 {
676   EmpathyIndividualManagerPriv *priv;
677
678   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager));
679   g_return_if_fail (group != NULL);
680
681   priv = GET_PRIV (manager);
682
683   DEBUG ("removing group %s", group);
684
685   /* Remove every individual from the group */
686   g_hash_table_foreach (priv->individuals, (GHFunc) remove_group_cb,
687       (gpointer) group);
688 }
689
690 static void
691 link_personas_cb (FolksIndividualAggregator *aggregator,
692     GAsyncResult *async_result,
693     gpointer user_data)
694 {
695   GError *error = NULL;
696
697   folks_individual_aggregator_link_personas_finish (aggregator, async_result,
698       &error);
699
700   if (error != NULL)
701     {
702       g_warning ("Failed to link personas: %s", error->message);
703       g_clear_error (&error);
704     }
705 }
706
707 void
708 empathy_individual_manager_link_personas (EmpathyIndividualManager *self,
709     GeeSet *personas)
710 {
711   EmpathyIndividualManagerPriv *priv;
712
713   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
714   g_return_if_fail (personas != NULL);
715
716   priv = GET_PRIV (self);
717
718   DEBUG ("Linking %u personas",
719       gee_collection_get_size (GEE_COLLECTION (personas)));
720
721   folks_individual_aggregator_link_personas (priv->aggregator, personas,
722       (GAsyncReadyCallback) link_personas_cb, NULL);
723 }
724
725 static void
726 unlink_individual_cb (FolksIndividualAggregator *aggregator,
727     GAsyncResult *async_result,
728     gpointer user_data)
729 {
730   GError *error = NULL;
731
732   folks_individual_aggregator_unlink_individual_finish (aggregator,
733       async_result, &error);
734
735   if (error != NULL)
736     {
737       g_warning ("Failed to unlink individual: %s", error->message);
738       g_clear_error (&error);
739     }
740 }
741
742 void
743 empathy_individual_manager_unlink_individual (EmpathyIndividualManager *self,
744     FolksIndividual *individual)
745 {
746   EmpathyIndividualManagerPriv *priv;
747
748   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
749   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
750
751   priv = GET_PRIV (self);
752
753   DEBUG ("Unlinking individual '%s'", folks_individual_get_id (individual));
754
755   folks_individual_aggregator_unlink_individual (priv->aggregator, individual,
756       (GAsyncReadyCallback) unlink_individual_cb, NULL);
757 }
758
759 gboolean
760 empathy_individual_manager_get_contacts_loaded (EmpathyIndividualManager *self)
761 {
762   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
763
764   return priv->contacts_loaded;
765 }
766
767 static gboolean
768 individual_has_contact (FolksIndividual *individual,
769     TpContact *contact)
770 {
771   GeeSet *personas;
772   GeeIterator *iter;
773   gboolean found = FALSE;
774
775   personas = folks_individual_get_personas (individual);
776   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
777
778   while (!found && gee_iterator_next (iter))
779     {
780       TpfPersona *persona = gee_iterator_get (iter);
781
782       if (TPF_IS_PERSONA (persona))
783         {
784           TpContact *c = tpf_persona_get_contact (persona);
785
786           if (c == contact)
787             found = TRUE;
788         }
789
790       g_clear_object (&persona);
791     }
792
793   g_clear_object (&iter);
794
795   return found;
796 }
797
798 /* Try finding a FolksIndividual containing @contact as one of his persona */
799 FolksIndividual *
800 empathy_individual_manager_lookup_by_contact (EmpathyIndividualManager *self,
801     TpContact *contact)
802 {
803   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
804   GHashTableIter iter;
805   gpointer value;
806
807   g_hash_table_iter_init (&iter, priv->individuals);
808   while (g_hash_table_iter_next (&iter, NULL, &value))
809     {
810       FolksIndividual *individual = value;
811
812       if (individual_has_contact (individual, contact))
813         return individual;
814     }
815
816   return NULL;
817 }