]> git.0d.be Git - empathy.git/blob - libempathy/empathy-individual-manager.c
Updated Greek translation
[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/telepathy-glib.h>
28 #include <telepathy-glib/account-manager.h>
29 #include <telepathy-glib/enums.h>
30 #include <telepathy-glib/proxy-subclass.h>
31 #include <telepathy-glib/util.h>
32
33 #include <folks/folks.h>
34 #include <folks/folks-telepathy.h>
35
36 #include <extensions/extensions.h>
37
38 #include "empathy-individual-manager.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 /* We just expose the $TOP_INDIVIDUALS_LEN more popular individuals as that's
47  * what the view actually care about. We just want to notify it when this list
48  * changes, not when the position of every single individual is updated. */
49 #define TOP_INDIVIDUALS_LEN 5
50
51 /* The constant INDIVIDUALS_COUNT_COMPRESS_FACTOR represents the number of
52  * interactions needed to be considered as 1 interaction */
53 #define INTERACTION_COUNT_COMPRESS_FACTOR 50
54
55 /* The constant DAY_IN_SECONDS represents the seconds in a day */
56 #define DAY_IN_SECONDS 86400
57
58 /* This class only stores and refs Individuals who contain an EmpathyContact.
59  *
60  * This class merely forwards along signals from the aggregator and individuals
61  * and wraps aggregator functions for other client code. */
62 typedef struct
63 {
64   FolksIndividualAggregator *aggregator;
65   GHashTable *individuals; /* Individual.id -> Individual */
66   gboolean contacts_loaded;
67
68   /* reffed FolksIndividual sorted by popularity (most popular first) */
69   GSequence *individuals_pop;
70   /* The TOP_INDIVIDUALS_LEN first FolksIndividual (borrowed) from
71    * individuals_pop */
72   GList *top_individuals;
73   guint global_interaction_counter;
74 } EmpathyIndividualManagerPriv;
75
76 enum
77 {
78   PROP_TOP_INDIVIDUALS = 1,
79   N_PROPS
80 };
81
82 enum
83 {
84   FAVOURITES_CHANGED,
85   GROUPS_CHANGED,
86   MEMBERS_CHANGED,
87   CONTACTS_LOADED,
88   LAST_SIGNAL
89 };
90
91 static guint signals[LAST_SIGNAL] = { 0 };
92
93 G_DEFINE_TYPE (EmpathyIndividualManager, empathy_individual_manager,
94     G_TYPE_OBJECT);
95
96 static EmpathyIndividualManager *manager_singleton = NULL;
97
98 static void
99 individual_manager_get_property (GObject *object,
100     guint property_id,
101     GValue *value,
102     GParamSpec *pspec)
103 {
104   EmpathyIndividualManager *self = EMPATHY_INDIVIDUAL_MANAGER (object);
105   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
106
107   switch (property_id)
108     {
109       case PROP_TOP_INDIVIDUALS:
110         g_value_set_pointer (value, priv->top_individuals);
111       default:
112         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
113         break;
114     }
115 }
116
117 static void
118 individual_group_changed_cb (FolksIndividual *individual,
119     gchar *group,
120     gboolean is_member,
121     EmpathyIndividualManager *self)
122 {
123   g_signal_emit (self, signals[GROUPS_CHANGED], 0, individual, group,
124       is_member);
125 }
126
127 static void
128 individual_notify_is_favourite_cb (FolksIndividual *individual,
129     GParamSpec *pspec,
130     EmpathyIndividualManager *self)
131 {
132   gboolean is_favourite = folks_favourite_details_get_is_favourite (
133       FOLKS_FAVOURITE_DETAILS (individual));
134   g_signal_emit (self, signals[FAVOURITES_CHANGED], 0, individual,
135       is_favourite);
136 }
137
138
139 /* Contacts that have been interacted with within the last 30 days and have
140  * have an interaction count > INTERACTION_COUNT_COMPRESS_FACTOR have a
141  * popularity value of the count/INTERACTION_COUNT_COMPRESS_FACTOR */
142 static guint
143 compute_popularity (FolksIndividual *individual)
144 {
145   FolksInteractionDetails *details = FOLKS_INTERACTION_DETAILS (individual);
146   GDateTime *last;
147   guint  current_timestamp, count;
148   float timediff;
149
150   last = folks_interaction_details_get_last_im_interaction_datetime (details);
151   if (last == NULL)
152     return 0;
153
154   /* Convert g_get_real_time () fro microseconds to seconds */
155   current_timestamp = g_get_real_time () / 1000000;
156   timediff = current_timestamp - g_date_time_to_unix (last);
157
158   if (timediff / DAY_IN_SECONDS > 30)
159     return 0;
160
161   count = folks_interaction_details_get_im_interaction_count (details);
162   count = count / INTERACTION_COUNT_COMPRESS_FACTOR;
163   if (count == 0)
164     return 0;
165
166   return count;
167 }
168
169 static void
170 check_top_individuals (EmpathyIndividualManager *self)
171 {
172   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
173   GSequenceIter *iter;
174   GList *l, *new_list = NULL;
175   gboolean modified = FALSE;
176   guint i;
177
178   iter = g_sequence_get_begin_iter (priv->individuals_pop);
179   l = priv->top_individuals;
180
181   /* Check if the TOP_INDIVIDUALS_LEN first individuals in individuals_pop are
182    * still the same as the ones in top_individuals */
183   for (i = 0; i < TOP_INDIVIDUALS_LEN && !g_sequence_iter_is_end (iter); i++)
184     {
185       FolksIndividual *individual = g_sequence_get (iter);
186       guint pop;
187
188       /* Don't include individual having 0 as pop */
189       pop = compute_popularity (individual);
190       if (pop <= 0)
191         break;
192
193       if (!modified)
194         {
195           if (l == NULL)
196             {
197               /* Old list is shorter than the new one */
198               modified = TRUE;
199             }
200           else
201             {
202               modified = (individual != l->data);
203
204               l = g_list_next (l);
205             }
206         }
207
208       new_list = g_list_prepend (new_list, individual);
209
210       iter = g_sequence_iter_next (iter);
211     }
212
213   g_list_free (priv->top_individuals);
214   priv->top_individuals = g_list_reverse (new_list);
215
216   if (modified)
217     {
218       DEBUG ("Top individuals changed:");
219
220       for (l = priv->top_individuals; l != NULL; l = g_list_next (l))
221         {
222           FolksIndividual *individual = l->data;
223
224           DEBUG ("  %s (%u)",
225               folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
226               compute_popularity (individual));
227         }
228
229       g_object_notify (G_OBJECT (self), "top-individuals");
230     }
231 }
232
233 static gint
234 compare_individual_by_pop (gconstpointer a,
235     gconstpointer b,
236     gpointer user_data)
237 {
238   guint pop_a, pop_b;
239
240   pop_a = compute_popularity (FOLKS_INDIVIDUAL (a));
241   pop_b = compute_popularity (FOLKS_INDIVIDUAL (b));
242
243   return pop_b - pop_a;
244 }
245
246 static void
247 individual_notify_im_interaction_count (FolksIndividual *individual,
248     GParamSpec *pspec,
249     EmpathyIndividualManager *self)
250 {
251   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
252
253   /* We don't use g_sequence_sort_changed() because we'll first have to find
254    * the iter of @individual using g_sequence_lookup() but the lookup function
255    * won't work as it assumes that the sequence is sorted which is no longer
256    * the case at this point as @individual's popularity just changed. */
257   g_sequence_sort (priv->individuals_pop, compare_individual_by_pop, NULL);
258
259   /* Only check for top individuals after 10 interaction events happen */
260   if (priv->global_interaction_counter % 10 == 0)
261     check_top_individuals (self);
262   priv->global_interaction_counter++;
263 }
264
265 static void
266 add_individual (EmpathyIndividualManager *self, FolksIndividual *individual)
267 {
268   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
269
270   g_hash_table_insert (priv->individuals,
271       g_strdup (folks_individual_get_id (individual)),
272       g_object_ref (individual));
273
274   g_sequence_insert_sorted (priv->individuals_pop, g_object_ref (individual),
275       compare_individual_by_pop, NULL);
276   check_top_individuals (self);
277
278   g_signal_connect (individual, "group-changed",
279       G_CALLBACK (individual_group_changed_cb), self);
280   g_signal_connect (individual, "notify::is-favourite",
281       G_CALLBACK (individual_notify_is_favourite_cb), self);
282   g_signal_connect (individual, "notify::im-interaction-count",
283       G_CALLBACK (individual_notify_im_interaction_count), self);
284 }
285
286 static void
287 remove_individual (EmpathyIndividualManager *self, FolksIndividual *individual)
288 {
289   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
290   GSequenceIter *iter;
291
292   iter = g_sequence_lookup (priv->individuals_pop, individual,
293       compare_individual_by_pop, NULL);
294   if (iter != NULL)
295     {
296       g_sequence_remove (iter);
297       check_top_individuals (self);
298     }
299
300   g_signal_handlers_disconnect_by_func (individual,
301       individual_group_changed_cb, self);
302   g_signal_handlers_disconnect_by_func (individual,
303       individual_notify_is_favourite_cb, self);
304   g_signal_handlers_disconnect_by_func (individual,
305       individual_notify_im_interaction_count, self);
306
307   g_hash_table_remove (priv->individuals, folks_individual_get_id (individual));
308 }
309
310 /* This is emitted for *all* individuals in the individual aggregator (not
311  * just the ones we keep a reference to), to allow for the case where a new
312  * individual doesn't contain an EmpathyContact, but later has a persona added
313  * which does. */
314 static void
315 individual_notify_personas_cb (FolksIndividual *individual,
316     GParamSpec *pspec,
317     EmpathyIndividualManager *self)
318 {
319   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
320
321   const gchar *id = folks_individual_get_id (individual);
322   gboolean has_contact = empathy_folks_individual_contains_contact (individual);
323   gboolean had_contact = (g_hash_table_lookup (priv->individuals,
324       id) != NULL) ? TRUE : FALSE;
325
326   if (had_contact == TRUE && has_contact == FALSE)
327     {
328       GList *removed = NULL;
329
330       /* The Individual has lost its EmpathyContact */
331       removed = g_list_prepend (removed, individual);
332       g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, NULL, removed,
333           TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
334       g_list_free (removed);
335
336       remove_individual (self, individual);
337     }
338   else if (had_contact == FALSE && has_contact == TRUE)
339     {
340       GList *added = NULL;
341
342       /* The Individual has gained its first EmpathyContact */
343       add_individual (self, individual);
344
345       added = g_list_prepend (added, individual);
346       g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, added, NULL,
347           TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
348       g_list_free (added);
349     }
350 }
351
352 static void
353 aggregator_individuals_changed_cb (FolksIndividualAggregator *aggregator,
354     GeeMultiMap *changes,
355     EmpathyIndividualManager *self)
356 {
357   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
358   GeeIterator *iter;
359   GeeSet *removed;
360   GeeCollection *added;
361   GList *added_set = NULL, *added_filtered = NULL, *removed_list = NULL;
362
363   /* We're not interested in the relationships between the added and removed
364    * individuals, so just extract collections of them. Note that the added
365    * collection may contain duplicates, while the removed set won't. */
366   removed = gee_multi_map_get_keys (changes);
367   added = gee_multi_map_get_values (changes);
368
369   /* Handle the removals first, as one of the added Individuals might have the
370    * same ID as one of the removed Individuals (due to linking). */
371   iter = gee_iterable_iterator (GEE_ITERABLE (removed));
372   while (gee_iterator_next (iter))
373     {
374       FolksIndividual *ind = gee_iterator_get (iter);
375
376       if (ind == NULL)
377         continue;
378
379       g_signal_handlers_disconnect_by_func (ind,
380           individual_notify_personas_cb, self);
381
382       if (g_hash_table_lookup (priv->individuals,
383           folks_individual_get_id (ind)) != NULL)
384         {
385           remove_individual (self, ind);
386           removed_list = g_list_prepend (removed_list, ind);
387         }
388
389       g_clear_object (&ind);
390     }
391   g_clear_object (&iter);
392
393   /* Filter the individuals for ones which contain EmpathyContacts */
394   iter = gee_iterable_iterator (GEE_ITERABLE (added));
395   while (gee_iterator_next (iter))
396     {
397       FolksIndividual *ind = gee_iterator_get (iter);
398
399       /* Make sure we handle each added individual only once. */
400       if (ind == NULL || g_list_find (added_set, ind) != NULL)
401         continue;
402       added_set = g_list_prepend (added_set, ind);
403
404       g_signal_connect (ind, "notify::personas",
405           G_CALLBACK (individual_notify_personas_cb), self);
406
407       if (empathy_folks_individual_contains_contact (ind) == TRUE)
408         {
409           add_individual (self, ind);
410           added_filtered = g_list_prepend (added_filtered, ind);
411         }
412
413       g_clear_object (&ind);
414     }
415   g_clear_object (&iter);
416
417   g_list_free (added_set);
418
419   g_object_unref (added);
420   g_object_unref (removed);
421
422   /* Bail if we have no individuals left */
423   if (added_filtered == NULL && removed == NULL)
424     return;
425
426   added_filtered = g_list_reverse (added_filtered);
427
428   g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL,
429       added_filtered, removed_list,
430       TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
431       TRUE);
432
433   g_list_free (added_filtered);
434   g_list_free (removed_list);
435 }
436
437 static void
438 individual_manager_dispose (GObject *object)
439 {
440   EmpathyIndividualManagerPriv *priv = GET_PRIV (object);
441
442   g_hash_table_unref (priv->individuals);
443   tp_clear_object (&priv->aggregator);
444
445   G_OBJECT_CLASS (empathy_individual_manager_parent_class)->dispose (object);
446 }
447
448 static void
449 individual_manager_finalize (GObject *object)
450 {
451   EmpathyIndividualManagerPriv *priv = GET_PRIV (object);
452
453   g_sequence_free (priv->individuals_pop);
454
455   G_OBJECT_CLASS (empathy_individual_manager_parent_class)->finalize (object);
456 }
457
458 static GObject *
459 individual_manager_constructor (GType type,
460     guint n_props,
461     GObjectConstructParam *props)
462 {
463   GObject *retval;
464
465   if (manager_singleton)
466     {
467       retval = g_object_ref (manager_singleton);
468     }
469   else
470     {
471       retval =
472           G_OBJECT_CLASS (empathy_individual_manager_parent_class)->
473           constructor (type, n_props, props);
474
475       manager_singleton = EMPATHY_INDIVIDUAL_MANAGER (retval);
476       g_object_add_weak_pointer (retval, (gpointer) & manager_singleton);
477     }
478
479   return retval;
480 }
481
482 /**
483  * empathy_individual_manager_initialized:
484  *
485  * Reports whether or not the singleton has already been created.
486  *
487  * There can be instances where you want to access the #EmpathyIndividualManager
488  * only if it has been set up for this process.
489  *
490  * Returns: %TRUE if the #EmpathyIndividualManager singleton has previously
491  * been initialized.
492  */
493 gboolean
494 empathy_individual_manager_initialized (void)
495 {
496   return (manager_singleton != NULL);
497 }
498
499 static void
500 empathy_individual_manager_class_init (EmpathyIndividualManagerClass *klass)
501 {
502   GObjectClass *object_class = G_OBJECT_CLASS (klass);
503   GParamSpec *spec;
504
505   object_class->get_property = individual_manager_get_property;
506   object_class->dispose = individual_manager_dispose;
507   object_class->finalize = individual_manager_finalize;
508   object_class->constructor = individual_manager_constructor;
509
510   spec = g_param_spec_pointer ("top-individuals", "top individuals",
511       "Top Individuals",
512       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
513   g_object_class_install_property (object_class, PROP_TOP_INDIVIDUALS, spec);
514
515   signals[GROUPS_CHANGED] =
516       g_signal_new ("groups-changed",
517           G_TYPE_FROM_CLASS (klass),
518           G_SIGNAL_RUN_LAST,
519           0,
520           NULL, NULL,
521           g_cclosure_marshal_generic,
522           G_TYPE_NONE, 3, FOLKS_TYPE_INDIVIDUAL, G_TYPE_STRING, G_TYPE_BOOLEAN);
523
524   signals[FAVOURITES_CHANGED] =
525       g_signal_new ("favourites-changed",
526           G_TYPE_FROM_CLASS (klass),
527           G_SIGNAL_RUN_LAST,
528           0,
529           NULL, NULL,
530           g_cclosure_marshal_generic,
531           G_TYPE_NONE, 2, FOLKS_TYPE_INDIVIDUAL, G_TYPE_BOOLEAN);
532
533   signals[MEMBERS_CHANGED] =
534       g_signal_new ("members-changed",
535           G_TYPE_FROM_CLASS (klass),
536           G_SIGNAL_RUN_LAST,
537           0,
538           NULL, NULL,
539           g_cclosure_marshal_generic,
540           G_TYPE_NONE,
541           4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_UINT);
542
543   signals[CONTACTS_LOADED] =
544       g_signal_new ("contacts-loaded",
545           G_TYPE_FROM_CLASS (klass),
546           G_SIGNAL_RUN_LAST,
547           0,
548           NULL, NULL,
549           g_cclosure_marshal_generic,
550           G_TYPE_NONE,
551           0);
552
553   g_type_class_add_private (object_class,
554       sizeof (EmpathyIndividualManagerPriv));
555 }
556
557 static void
558 aggregator_is_quiescent_notify_cb (FolksIndividualAggregator *aggregator,
559     GParamSpec *spec,
560     EmpathyIndividualManager *self)
561 {
562   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
563   gboolean is_quiescent;
564
565   if (priv->contacts_loaded)
566     return;
567
568   g_object_get (aggregator, "is-quiescent", &is_quiescent, NULL);
569
570   if (!is_quiescent)
571     return;
572
573   priv->contacts_loaded = TRUE;
574
575   g_signal_emit (self, signals[CONTACTS_LOADED], 0);
576 }
577
578 static void
579 empathy_individual_manager_init (EmpathyIndividualManager *self)
580 {
581   EmpathyIndividualManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
582       EMPATHY_TYPE_INDIVIDUAL_MANAGER, EmpathyIndividualManagerPriv);
583
584   self->priv = priv;
585   priv->individuals = g_hash_table_new_full (g_str_hash, g_str_equal,
586       g_free, g_object_unref);
587
588   priv->individuals_pop = g_sequence_new (g_object_unref);
589
590   priv->aggregator = folks_individual_aggregator_new ();
591   tp_g_signal_connect_object (priv->aggregator, "individuals-changed-detailed",
592       G_CALLBACK (aggregator_individuals_changed_cb), self, 0);
593   tp_g_signal_connect_object (priv->aggregator, "notify::is-quiescent",
594       G_CALLBACK (aggregator_is_quiescent_notify_cb), self, 0);
595   folks_individual_aggregator_prepare (priv->aggregator, NULL, NULL);
596 }
597
598 EmpathyIndividualManager *
599 empathy_individual_manager_dup_singleton (void)
600 {
601   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MANAGER, NULL);
602 }
603
604 GList *
605 empathy_individual_manager_get_members (EmpathyIndividualManager *self)
606 {
607   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
608
609   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
610
611   return g_hash_table_get_values (priv->individuals);
612 }
613
614 FolksIndividual *
615 empathy_individual_manager_lookup_member (EmpathyIndividualManager *self,
616     const gchar *id)
617 {
618   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
619
620   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
621
622   return g_hash_table_lookup (priv->individuals, id);
623 }
624
625 static void
626 aggregator_add_persona_from_details_cb (GObject *source,
627     GAsyncResult *result,
628     gpointer user_data)
629 {
630   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
631   EmpathyContact *contact = EMPATHY_CONTACT (user_data);
632   FolksPersona *persona;
633   GError *error = NULL;
634
635   persona = folks_individual_aggregator_add_persona_from_details_finish (
636       aggregator, result, &error);
637   if (error != NULL)
638     {
639       g_warning ("failed to add individual from contact: %s", error->message);
640       g_clear_error (&error);
641     }
642
643   /* The persona can be NULL even if there wasn't an error, if the persona was
644    * already in the contact list */
645   if (persona != NULL)
646     {
647       /* Set the contact's persona */
648       empathy_contact_set_persona (contact, persona);
649       g_object_unref (persona);
650     }
651
652   g_object_unref (contact);
653 }
654
655 void
656 empathy_individual_manager_add_from_contact (EmpathyIndividualManager *self,
657     EmpathyContact *contact)
658 {
659   EmpathyIndividualManagerPriv *priv;
660   FolksBackendStore *backend_store;
661   FolksBackend *backend;
662   FolksPersonaStore *persona_store;
663   GHashTable* details;
664   GeeMap *persona_stores;
665   TpAccount *account;
666   const gchar *store_id;
667
668   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
669   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
670
671   priv = GET_PRIV (self);
672
673   /* We need to ref the contact since otherwise its linked TpHandle will be
674    * destroyed. */
675   g_object_ref (contact);
676
677   DEBUG ("adding individual from contact %s (%s)",
678       empathy_contact_get_id (contact), empathy_contact_get_alias (contact));
679
680   account = empathy_contact_get_account (contact);
681   store_id = tp_proxy_get_object_path (TP_PROXY (account));
682
683   /* Get the persona store to use */
684   backend_store = folks_backend_store_dup ();
685   backend =
686       folks_backend_store_dup_backend_by_name (backend_store, "telepathy");
687
688   if (backend == NULL)
689     {
690       g_warning ("Failed to add individual from contact: couldn't get "
691           "'telepathy' backend");
692       goto finish;
693     }
694
695   persona_stores = folks_backend_get_persona_stores (backend);
696   persona_store = gee_map_get (persona_stores, store_id);
697
698   if (persona_store == NULL)
699     {
700       g_warning ("Failed to add individual from contact: couldn't get persona "
701           "store '%s'", store_id);
702       goto finish;
703     }
704
705   details = tp_asv_new (
706       "contact", G_TYPE_STRING, empathy_contact_get_id (contact),
707       NULL);
708
709   folks_individual_aggregator_add_persona_from_details (
710       priv->aggregator, NULL, persona_store, details,
711       aggregator_add_persona_from_details_cb, contact);
712
713   g_hash_table_unref (details);
714   g_object_unref (persona_store);
715
716 finish:
717   tp_clear_object (&backend);
718   tp_clear_object (&backend_store);
719 }
720
721 static void
722 aggregator_remove_individual_cb (GObject *source,
723     GAsyncResult *result,
724     gpointer user_data)
725 {
726   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
727   GError *error = NULL;
728
729   folks_individual_aggregator_remove_individual_finish (
730       aggregator, result, &error);
731   if (error != NULL)
732     {
733       g_warning ("failed to remove individual: %s", error->message);
734       g_clear_error (&error);
735     }
736 }
737
738 /**
739  * Removes the inner contact from the server (and thus the Individual). Not
740  * meant for de-shelling inner personas from an Individual.
741  */
742 void
743 empathy_individual_manager_remove (EmpathyIndividualManager *self,
744     FolksIndividual *individual,
745     const gchar *message)
746 {
747   EmpathyIndividualManagerPriv *priv;
748
749   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
750   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
751
752   priv = GET_PRIV (self);
753
754   DEBUG ("removing individual %s (%s)",
755       folks_individual_get_id (individual),
756       folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
757
758   folks_individual_aggregator_remove_individual (priv->aggregator, individual,
759       aggregator_remove_individual_cb, self);
760 }
761
762 /* FIXME: The parameter @self is not required and the method can be placed in
763  * utilities. I left it as it is to stay coherent with empathy-2.34 */
764 /**
765  * empathy_individual_manager_supports_blocking
766  * @self: the #EmpathyIndividualManager
767  * @individual: an individual to check
768  *
769  * Indicates whether any personas of an @individual can be blocked.
770  *
771  * Returns: %TRUE if any persona supports contact blocking
772  */
773 gboolean
774 empathy_individual_manager_supports_blocking (EmpathyIndividualManager *self,
775     FolksIndividual *individual)
776 {
777   GeeSet *personas;
778   GeeIterator *iter;
779   gboolean retval = FALSE;
780
781   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), FALSE);
782
783   personas = folks_individual_get_personas (individual);
784   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
785   while (!retval && gee_iterator_next (iter))
786     {
787       TpfPersona *persona = gee_iterator_get (iter);
788       TpConnection *conn;
789
790       if (TPF_IS_PERSONA (persona))
791         {
792           TpContact *tp_contact;
793
794           tp_contact = tpf_persona_get_contact (persona);
795           if (tp_contact != NULL)
796             {
797               conn = tp_contact_get_connection (tp_contact);
798
799               if (tp_proxy_has_interface_by_id (conn,
800                     TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING))
801                 retval = TRUE;
802             }
803         }
804       g_clear_object (&persona);
805     }
806   g_clear_object (&iter);
807
808   return retval;
809 }
810
811 void
812 empathy_individual_manager_set_blocked (EmpathyIndividualManager *self,
813     FolksIndividual *individual,
814     gboolean blocked,
815     gboolean abusive)
816 {
817   GeeSet *personas;
818   GeeIterator *iter;
819
820   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
821
822   personas = folks_individual_get_personas (individual);
823   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
824   while (gee_iterator_next (iter))
825     {
826       TpfPersona *persona = gee_iterator_get (iter);
827
828       if (TPF_IS_PERSONA (persona))
829         {
830           TpContact *tp_contact;
831           TpConnection *conn;
832
833           tp_contact = tpf_persona_get_contact (persona);
834           if (tp_contact == NULL)
835             continue;
836
837           conn = tp_contact_get_connection (tp_contact);
838
839           if (!tp_proxy_has_interface_by_id (conn,
840                 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING))
841             continue;
842
843           if (blocked)
844             tp_contact_block_async (tp_contact, abusive, NULL, NULL);
845           else
846             tp_contact_unblock_async (tp_contact, NULL, NULL);
847         }
848       g_clear_object (&persona);
849     }
850   g_clear_object (&iter);
851 }
852
853 static void
854 groups_change_group_cb (GObject *source,
855     GAsyncResult *result,
856     gpointer user_data)
857 {
858   FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
859   GError *error = NULL;
860
861   folks_group_details_change_group_finish (group_details, result, &error);
862   if (error != NULL)
863     {
864       g_warning ("failed to change group: %s", error->message);
865       g_clear_error (&error);
866     }
867 }
868
869 static void
870 remove_group_cb (const gchar *id,
871     FolksIndividual *individual,
872     const gchar *group)
873 {
874   folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual), group,
875       FALSE, groups_change_group_cb, NULL);
876 }
877
878 void
879 empathy_individual_manager_remove_group (EmpathyIndividualManager *manager,
880     const gchar *group)
881 {
882   EmpathyIndividualManagerPriv *priv;
883
884   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager));
885   g_return_if_fail (group != NULL);
886
887   priv = GET_PRIV (manager);
888
889   DEBUG ("removing group %s", group);
890
891   /* Remove every individual from the group */
892   g_hash_table_foreach (priv->individuals, (GHFunc) remove_group_cb,
893       (gpointer) group);
894 }
895
896 gboolean
897 empathy_individual_manager_get_contacts_loaded (EmpathyIndividualManager *self)
898 {
899   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
900
901   return priv->contacts_loaded;
902 }
903
904 GList *
905 empathy_individual_manager_get_top_individuals (EmpathyIndividualManager *self)
906 {
907   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
908
909   return priv->top_individuals;
910 }