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