]> git.0d.be Git - empathy.git/blob - libempathy/empathy-individual-manager.c
97be8b8a71eaa790515c85cae1608e2c2f61f03f
[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
444   tp_clear_object (&priv->aggregator);
445
446   G_OBJECT_CLASS (empathy_individual_manager_parent_class)->dispose (object);
447 }
448
449 static void
450 individual_manager_finalize (GObject *object)
451 {
452   EmpathyIndividualManagerPriv *priv = GET_PRIV (object);
453
454   g_sequence_free (priv->individuals_pop);
455
456   G_OBJECT_CLASS (empathy_individual_manager_parent_class)->finalize (object);
457 }
458
459 static GObject *
460 individual_manager_constructor (GType type,
461     guint n_props,
462     GObjectConstructParam *props)
463 {
464   GObject *retval;
465
466   if (manager_singleton)
467     {
468       retval = g_object_ref (manager_singleton);
469     }
470   else
471     {
472       retval =
473           G_OBJECT_CLASS (empathy_individual_manager_parent_class)->
474           constructor (type, n_props, props);
475
476       manager_singleton = EMPATHY_INDIVIDUAL_MANAGER (retval);
477       g_object_add_weak_pointer (retval, (gpointer) & manager_singleton);
478     }
479
480   return retval;
481 }
482
483 /**
484  * empathy_individual_manager_initialized:
485  *
486  * Reports whether or not the singleton has already been created.
487  *
488  * There can be instances where you want to access the #EmpathyIndividualManager
489  * only if it has been set up for this process.
490  *
491  * Returns: %TRUE if the #EmpathyIndividualManager singleton has previously
492  * been initialized.
493  */
494 gboolean
495 empathy_individual_manager_initialized (void)
496 {
497   return (manager_singleton != NULL);
498 }
499
500 static void
501 empathy_individual_manager_class_init (EmpathyIndividualManagerClass *klass)
502 {
503   GObjectClass *object_class = G_OBJECT_CLASS (klass);
504   GParamSpec *spec;
505
506   object_class->get_property = individual_manager_get_property;
507   object_class->dispose = individual_manager_dispose;
508   object_class->finalize = individual_manager_finalize;
509   object_class->constructor = individual_manager_constructor;
510
511   spec = g_param_spec_pointer ("top-individuals", "top individuals",
512       "Top Individuals",
513       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
514   g_object_class_install_property (object_class, PROP_TOP_INDIVIDUALS, spec);
515
516   signals[GROUPS_CHANGED] =
517       g_signal_new ("groups-changed",
518           G_TYPE_FROM_CLASS (klass),
519           G_SIGNAL_RUN_LAST,
520           0,
521           NULL, NULL,
522           g_cclosure_marshal_generic,
523           G_TYPE_NONE, 3, FOLKS_TYPE_INDIVIDUAL, G_TYPE_STRING, G_TYPE_BOOLEAN);
524
525   signals[FAVOURITES_CHANGED] =
526       g_signal_new ("favourites-changed",
527           G_TYPE_FROM_CLASS (klass),
528           G_SIGNAL_RUN_LAST,
529           0,
530           NULL, NULL,
531           g_cclosure_marshal_generic,
532           G_TYPE_NONE, 2, FOLKS_TYPE_INDIVIDUAL, G_TYPE_BOOLEAN);
533
534   signals[MEMBERS_CHANGED] =
535       g_signal_new ("members-changed",
536           G_TYPE_FROM_CLASS (klass),
537           G_SIGNAL_RUN_LAST,
538           0,
539           NULL, NULL,
540           g_cclosure_marshal_generic,
541           G_TYPE_NONE,
542           4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_UINT);
543
544   signals[CONTACTS_LOADED] =
545       g_signal_new ("contacts-loaded",
546           G_TYPE_FROM_CLASS (klass),
547           G_SIGNAL_RUN_LAST,
548           0,
549           NULL, NULL,
550           g_cclosure_marshal_generic,
551           G_TYPE_NONE,
552           0);
553
554   g_type_class_add_private (object_class,
555       sizeof (EmpathyIndividualManagerPriv));
556 }
557
558 static void
559 aggregator_is_quiescent_notify_cb (FolksIndividualAggregator *aggregator,
560     GParamSpec *spec,
561     EmpathyIndividualManager *self)
562 {
563   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
564   gboolean is_quiescent;
565
566   if (priv->contacts_loaded)
567     return;
568
569   g_object_get (aggregator, "is-quiescent", &is_quiescent, NULL);
570
571   if (!is_quiescent)
572     return;
573
574   priv->contacts_loaded = TRUE;
575
576   g_signal_emit (self, signals[CONTACTS_LOADED], 0);
577 }
578
579 static void
580 empathy_individual_manager_init (EmpathyIndividualManager *self)
581 {
582   EmpathyIndividualManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
583       EMPATHY_TYPE_INDIVIDUAL_MANAGER, EmpathyIndividualManagerPriv);
584
585   self->priv = priv;
586   priv->individuals = g_hash_table_new_full (g_str_hash, g_str_equal,
587       g_free, g_object_unref);
588
589   priv->individuals_pop = g_sequence_new (g_object_unref);
590
591   priv->aggregator = folks_individual_aggregator_new ();
592   tp_g_signal_connect_object (priv->aggregator, "individuals-changed-detailed",
593       G_CALLBACK (aggregator_individuals_changed_cb), self, 0);
594   tp_g_signal_connect_object (priv->aggregator, "notify::is-quiescent",
595       G_CALLBACK (aggregator_is_quiescent_notify_cb), self, 0);
596   folks_individual_aggregator_prepare (priv->aggregator, NULL, NULL);
597 }
598
599 EmpathyIndividualManager *
600 empathy_individual_manager_dup_singleton (void)
601 {
602   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MANAGER, NULL);
603 }
604
605 GList *
606 empathy_individual_manager_get_members (EmpathyIndividualManager *self)
607 {
608   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
609
610   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
611
612   return g_hash_table_get_values (priv->individuals);
613 }
614
615 FolksIndividual *
616 empathy_individual_manager_lookup_member (EmpathyIndividualManager *self,
617     const gchar *id)
618 {
619   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
620
621   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
622
623   return g_hash_table_lookup (priv->individuals, id);
624 }
625
626 static void
627 aggregator_add_persona_from_details_cb (GObject *source,
628     GAsyncResult *result,
629     gpointer user_data)
630 {
631   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
632   EmpathyContact *contact = EMPATHY_CONTACT (user_data);
633   FolksPersona *persona;
634   GError *error = NULL;
635
636   persona = folks_individual_aggregator_add_persona_from_details_finish (
637       aggregator, result, &error);
638   if (error != NULL)
639     {
640       g_warning ("failed to add individual from contact: %s", error->message);
641       g_clear_error (&error);
642     }
643
644   /* The persona can be NULL even if there wasn't an error, if the persona was
645    * already in the contact list */
646   if (persona != NULL)
647     {
648       /* Set the contact's persona */
649       empathy_contact_set_persona (contact, persona);
650       g_object_unref (persona);
651     }
652
653   g_object_unref (contact);
654 }
655
656 void
657 empathy_individual_manager_add_from_contact (EmpathyIndividualManager *self,
658     EmpathyContact *contact)
659 {
660   EmpathyIndividualManagerPriv *priv;
661   FolksBackendStore *backend_store;
662   FolksBackend *backend;
663   FolksPersonaStore *persona_store;
664   GHashTable* details;
665   GeeMap *persona_stores;
666   TpAccount *account;
667   const gchar *store_id;
668
669   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
670   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
671
672   priv = GET_PRIV (self);
673
674   /* We need to ref the contact since otherwise its linked TpHandle will be
675    * destroyed. */
676   g_object_ref (contact);
677
678   DEBUG ("adding individual from contact %s (%s)",
679       empathy_contact_get_id (contact), empathy_contact_get_alias (contact));
680
681   account = empathy_contact_get_account (contact);
682   store_id = tp_proxy_get_object_path (TP_PROXY (account));
683
684   /* Get the persona store to use */
685   backend_store = folks_backend_store_dup ();
686   backend =
687       folks_backend_store_dup_backend_by_name (backend_store, "telepathy");
688
689   if (backend == NULL)
690     {
691       g_warning ("Failed to add individual from contact: couldn't get "
692           "'telepathy' backend");
693       goto finish;
694     }
695
696   persona_stores = folks_backend_get_persona_stores (backend);
697   persona_store = gee_map_get (persona_stores, store_id);
698
699   if (persona_store == NULL)
700     {
701       g_warning ("Failed to add individual from contact: couldn't get persona "
702           "store '%s'", store_id);
703       goto finish;
704     }
705
706   details = tp_asv_new (
707       "contact", G_TYPE_STRING, empathy_contact_get_id (contact),
708       NULL);
709
710   folks_individual_aggregator_add_persona_from_details (
711       priv->aggregator, NULL, persona_store, details,
712       aggregator_add_persona_from_details_cb, contact);
713
714   g_hash_table_unref (details);
715   g_object_unref (persona_store);
716
717 finish:
718   tp_clear_object (&backend);
719   tp_clear_object (&backend_store);
720 }
721
722 static void
723 aggregator_remove_individual_cb (GObject *source,
724     GAsyncResult *result,
725     gpointer user_data)
726 {
727   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
728   GError *error = NULL;
729
730   folks_individual_aggregator_remove_individual_finish (
731       aggregator, result, &error);
732   if (error != NULL)
733     {
734       g_warning ("failed to remove individual: %s", error->message);
735       g_clear_error (&error);
736     }
737 }
738
739 /**
740  * Removes the inner contact from the server (and thus the Individual). Not
741  * meant for de-shelling inner personas from an Individual.
742  */
743 void
744 empathy_individual_manager_remove (EmpathyIndividualManager *self,
745     FolksIndividual *individual,
746     const gchar *message)
747 {
748   EmpathyIndividualManagerPriv *priv;
749
750   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
751   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
752
753   priv = GET_PRIV (self);
754
755   DEBUG ("removing individual %s (%s)",
756       folks_individual_get_id (individual),
757       folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
758
759   folks_individual_aggregator_remove_individual (priv->aggregator, individual,
760       aggregator_remove_individual_cb, self);
761 }
762
763 /* FIXME: The parameter @self is not required and the method can be placed in
764  * utilities. I left it as it is to stay coherent with empathy-2.34 */
765 /**
766  * empathy_individual_manager_supports_blocking
767  * @self: the #EmpathyIndividualManager
768  * @individual: an individual to check
769  *
770  * Indicates whether any personas of an @individual can be blocked.
771  *
772  * Returns: %TRUE if any persona supports contact blocking
773  */
774 gboolean
775 empathy_individual_manager_supports_blocking (EmpathyIndividualManager *self,
776     FolksIndividual *individual)
777 {
778   GeeSet *personas;
779   GeeIterator *iter;
780   gboolean retval = FALSE;
781
782   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), FALSE);
783
784   personas = folks_individual_get_personas (individual);
785   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
786   while (!retval && gee_iterator_next (iter))
787     {
788       TpfPersona *persona = gee_iterator_get (iter);
789       TpConnection *conn;
790
791       if (TPF_IS_PERSONA (persona))
792         {
793           TpContact *tp_contact;
794
795           tp_contact = tpf_persona_get_contact (persona);
796           if (tp_contact != NULL)
797             {
798               conn = tp_contact_get_connection (tp_contact);
799
800               if (tp_proxy_has_interface_by_id (conn,
801                     TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING))
802                 retval = TRUE;
803             }
804         }
805       g_clear_object (&persona);
806     }
807   g_clear_object (&iter);
808
809   return retval;
810 }
811
812 void
813 empathy_individual_manager_set_blocked (EmpathyIndividualManager *self,
814     FolksIndividual *individual,
815     gboolean blocked,
816     gboolean abusive)
817 {
818   GeeSet *personas;
819   GeeIterator *iter;
820
821   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
822
823   personas = folks_individual_get_personas (individual);
824   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
825   while (gee_iterator_next (iter))
826     {
827       TpfPersona *persona = gee_iterator_get (iter);
828
829       if (TPF_IS_PERSONA (persona))
830         {
831           TpContact *tp_contact;
832           TpConnection *conn;
833
834           tp_contact = tpf_persona_get_contact (persona);
835           if (tp_contact == NULL)
836             continue;
837
838           conn = tp_contact_get_connection (tp_contact);
839
840           if (!tp_proxy_has_interface_by_id (conn,
841                 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING))
842             continue;
843
844           if (blocked)
845             tp_contact_block_async (tp_contact, abusive, NULL, NULL);
846           else
847             tp_contact_unblock_async (tp_contact, NULL, NULL);
848         }
849       g_clear_object (&persona);
850     }
851   g_clear_object (&iter);
852 }
853
854 static void
855 groups_change_group_cb (GObject *source,
856     GAsyncResult *result,
857     gpointer user_data)
858 {
859   FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
860   GError *error = NULL;
861
862   folks_group_details_change_group_finish (group_details, result, &error);
863   if (error != NULL)
864     {
865       g_warning ("failed to change group: %s", error->message);
866       g_clear_error (&error);
867     }
868 }
869
870 static void
871 remove_group_cb (const gchar *id,
872     FolksIndividual *individual,
873     const gchar *group)
874 {
875   folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual), group,
876       FALSE, groups_change_group_cb, NULL);
877 }
878
879 void
880 empathy_individual_manager_remove_group (EmpathyIndividualManager *manager,
881     const gchar *group)
882 {
883   EmpathyIndividualManagerPriv *priv;
884
885   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager));
886   g_return_if_fail (group != NULL);
887
888   priv = GET_PRIV (manager);
889
890   DEBUG ("removing group %s", group);
891
892   /* Remove every individual from the group */
893   g_hash_table_foreach (priv->individuals, (GHFunc) remove_group_cb,
894       (gpointer) group);
895 }
896
897 gboolean
898 empathy_individual_manager_get_contacts_loaded (EmpathyIndividualManager *self)
899 {
900   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
901
902   return priv->contacts_loaded;
903 }
904
905 GList *
906 empathy_individual_manager_get_top_individuals (EmpathyIndividualManager *self)
907 {
908   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
909
910   return priv->top_individuals;
911 }
912
913 static void
914 unprepare_cb (GObject *source,
915     GAsyncResult *result,
916     gpointer user_data)
917 {
918   GError *error = NULL;
919   GSimpleAsyncResult *my_result = user_data;
920
921   folks_individual_aggregator_unprepare_finish (
922       FOLKS_INDIVIDUAL_AGGREGATOR (source), result, &error);
923
924   if (error != NULL)
925     {
926       DEBUG ("Failed to unprepare the aggregator: %s", error->message);
927       g_simple_async_result_take_error (my_result, error);
928     }
929
930   g_simple_async_result_complete (my_result);
931   g_object_unref (my_result);
932 }
933
934 void
935 empathy_individual_manager_unprepare_async (
936     EmpathyIndividualManager *self,
937     GAsyncReadyCallback callback,
938     gpointer user_data)
939 {
940   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
941   GSimpleAsyncResult *result;
942
943   result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
944       empathy_individual_manager_unprepare_async);
945
946   folks_individual_aggregator_unprepare (priv->aggregator, unprepare_cb,
947       result);
948 }
949
950 gboolean
951 empathy_individual_manager_unprepare_finish (
952     EmpathyIndividualManager *self,
953     GAsyncResult *result,
954     GError **error)
955 {
956   empathy_implement_finish_void (self,
957       empathy_individual_manager_unprepare_async)
958 }