]> git.0d.be Git - empathy.git/blob - libempathy/empathy-individual-manager.c
Don't unnecessarily remove Individuals if Individual IDs have been reused
[empathy.git] / libempathy / empathy-individual-manager.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007-2010 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  *          Travis Reitter <travis.reitter@collabora.co.uk>
21  */
22
23 #include <config.h>
24
25 #include <string.h>
26
27 #include <telepathy-glib/account-manager.h>
28 #include <telepathy-glib/enums.h>
29 #include <telepathy-glib/proxy-subclass.h>
30 #include <telepathy-glib/util.h>
31
32 #include <folks/folks.h>
33
34 #include <extensions/extensions.h>
35
36 #include "empathy-individual-manager.h"
37 #include "empathy-contact-manager.h"
38 #include "empathy-contact-list.h"
39 #include "empathy-marshal.h"
40 #include "empathy-utils.h"
41
42 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
43 #include "empathy-debug.h"
44
45 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualManager)
46
47 /* This class only stores and refs Individuals who contain an EmpathyContact.
48  *
49  * This class merely forwards along signals from the aggregator and individuals
50  * and wraps aggregator functions for other client code. */
51 typedef struct
52 {
53   FolksIndividualAggregator *aggregator;
54   EmpathyContactManager *contact_manager;
55   GHashTable *individuals; /* Individual.id -> Individual */
56 } EmpathyIndividualManagerPriv;
57
58 enum
59 {
60   FAVOURITES_CHANGED,
61   GROUPS_CHANGED,
62   MEMBERS_CHANGED,
63   LAST_SIGNAL
64 };
65
66 static guint signals[LAST_SIGNAL] = { 0 };
67
68 G_DEFINE_TYPE (EmpathyIndividualManager, empathy_individual_manager,
69     G_TYPE_OBJECT);
70
71 static EmpathyIndividualManager *manager_singleton = NULL;
72
73 static void
74 individual_group_changed_cb (FolksIndividual *individual,
75     gchar *group,
76     gboolean is_member,
77     EmpathyIndividualManager *self)
78 {
79   g_signal_emit (self, signals[GROUPS_CHANGED], 0, individual, group,
80       is_member);
81 }
82
83 static void
84 individual_notify_is_favourite_cb (FolksIndividual *individual,
85     GParamSpec *pspec,
86     EmpathyIndividualManager *self)
87 {
88   gboolean is_favourite = folks_favourite_get_is_favourite (
89       FOLKS_FAVOURITE (individual));
90   g_signal_emit (self, signals[FAVOURITES_CHANGED], 0, individual,
91       is_favourite);
92 }
93
94 static void
95 add_individual (EmpathyIndividualManager *self, FolksIndividual *individual)
96 {
97   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
98
99   g_hash_table_insert (priv->individuals,
100       (gpointer) folks_individual_get_id (individual),
101       g_object_ref (individual));
102
103   g_signal_connect (individual, "group-changed",
104       G_CALLBACK (individual_group_changed_cb), self);
105   g_signal_connect (individual, "notify::is-favourite",
106       G_CALLBACK (individual_notify_is_favourite_cb), self);
107 }
108
109 static void
110 remove_individual (EmpathyIndividualManager *self, FolksIndividual *individual)
111 {
112   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
113
114   g_signal_handlers_disconnect_by_func (individual,
115       individual_group_changed_cb, self);
116   g_signal_handlers_disconnect_by_func (individual,
117       individual_notify_is_favourite_cb, self);
118
119   g_hash_table_remove (priv->individuals, folks_individual_get_id (individual));
120 }
121
122 /* This is emitted for *all* individuals in the individual aggregator (not
123  * just the ones we keep a reference to), to allow for the case where a new
124  * individual doesn't contain an EmpathyContact, but later has a persona added
125  * which does. */
126 static void
127 individual_notify_personas_cb (FolksIndividual *individual,
128     GParamSpec *pspec,
129     EmpathyIndividualManager *self)
130 {
131   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
132
133   const gchar *id = folks_individual_get_id (individual);
134   gboolean has_contact = empathy_folks_individual_contains_contact (individual);
135   gboolean had_contact = (g_hash_table_lookup (priv->individuals,
136       id) != NULL) ? TRUE : FALSE;
137
138   if (had_contact == TRUE && has_contact == FALSE)
139     {
140       GList *removed = NULL;
141
142       /* The Individual has lost its EmpathyContact */
143       removed = g_list_prepend (removed, individual);
144       g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, NULL, removed,
145           TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
146       g_list_free (removed);
147
148       remove_individual (self, individual);
149     }
150   else if (had_contact == FALSE && has_contact == TRUE)
151     {
152       GList *added = NULL;
153
154       /* The Individual has gained its first EmpathyContact */
155       add_individual (self, individual);
156
157       added = g_list_prepend (added, individual);
158       g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, added, NULL,
159           TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
160       g_list_free (added);
161     }
162 }
163
164 static void
165 aggregator_individuals_changed_cb (FolksIndividualAggregator *aggregator,
166     GList *added,
167     GList *removed,
168     const char *message,
169     FolksPersona *actor,
170     guint reason,
171     EmpathyIndividualManager *self)
172 {
173   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
174   GList *l, *added_filtered = NULL;
175
176   /* Handle the removals first, as one of the added Individuals might have the
177    * same ID as one of the removed Individuals (due to linking). */
178   for (l = removed; l; l = l->next)
179     {
180       FolksIndividual *ind = FOLKS_INDIVIDUAL (l->data);
181
182       g_signal_handlers_disconnect_by_func (ind,
183           individual_notify_personas_cb, self);
184
185       if (g_hash_table_lookup (priv->individuals,
186           folks_individual_get_id (ind)) != NULL)
187         remove_individual (self, ind);
188     }
189
190   /* Filter the individuals for ones which contain EmpathyContacts */
191   for (l = added; l; l = l->next)
192     {
193       FolksIndividual *ind = FOLKS_INDIVIDUAL (l->data);
194
195       g_signal_connect (ind, "notify::personas",
196           G_CALLBACK (individual_notify_personas_cb), self);
197
198       if (empathy_folks_individual_contains_contact (ind) == TRUE)
199         {
200           add_individual (self, ind);
201           added_filtered = g_list_prepend (added_filtered, ind);
202         }
203     }
204
205   /* Bail if we have no individuals left */
206   if (added_filtered == NULL && removed == NULL)
207     return;
208
209   added_filtered = g_list_reverse (added_filtered);
210
211   g_signal_emit (self, signals[MEMBERS_CHANGED], 0, message,
212       added_filtered, removed,
213       tp_chanel_group_change_reason_from_folks_groups_change_reason (reason),
214       TRUE);
215
216   g_list_free (added_filtered);
217 }
218
219 static void
220 individual_manager_dispose (GObject *object)
221 {
222   EmpathyIndividualManagerPriv *priv = GET_PRIV (object);
223
224   g_hash_table_destroy (priv->individuals);
225   tp_clear_object (&priv->contact_manager);
226   tp_clear_object (&priv->aggregator);
227
228   G_OBJECT_CLASS (empathy_individual_manager_parent_class)->dispose (object);
229 }
230
231 static GObject *
232 individual_manager_constructor (GType type,
233     guint n_props,
234     GObjectConstructParam *props)
235 {
236   GObject *retval;
237
238   if (manager_singleton)
239     {
240       retval = g_object_ref (manager_singleton);
241     }
242   else
243     {
244       retval =
245           G_OBJECT_CLASS (empathy_individual_manager_parent_class)->
246           constructor (type, n_props, props);
247
248       manager_singleton = EMPATHY_INDIVIDUAL_MANAGER (retval);
249       g_object_add_weak_pointer (retval, (gpointer) & manager_singleton);
250     }
251
252   return retval;
253 }
254
255 /**
256  * empathy_individual_manager_initialized:
257  *
258  * Reports whether or not the singleton has already been created.
259  *
260  * There can be instances where you want to access the #EmpathyIndividualManager
261  * only if it has been set up for this process.
262  *
263  * Returns: %TRUE if the #EmpathyIndividualManager singleton has previously
264  * been initialized.
265  */
266 gboolean
267 empathy_individual_manager_initialized (void)
268 {
269   return (manager_singleton != NULL);
270 }
271
272 static void
273 empathy_individual_manager_class_init (EmpathyIndividualManagerClass *klass)
274 {
275   GObjectClass *object_class = G_OBJECT_CLASS (klass);
276
277   object_class->dispose = individual_manager_dispose;
278   object_class->constructor = individual_manager_constructor;
279
280   signals[GROUPS_CHANGED] =
281       g_signal_new ("groups-changed",
282           G_TYPE_FROM_CLASS (klass),
283           G_SIGNAL_RUN_LAST,
284           0,
285           NULL, NULL,
286           _empathy_marshal_VOID__OBJECT_STRING_BOOLEAN,
287           G_TYPE_NONE, 3, FOLKS_TYPE_INDIVIDUAL, G_TYPE_STRING, G_TYPE_BOOLEAN);
288
289   signals[FAVOURITES_CHANGED] =
290       g_signal_new ("favourites-changed",
291           G_TYPE_FROM_CLASS (klass),
292           G_SIGNAL_RUN_LAST,
293           0,
294           NULL, NULL,
295           _empathy_marshal_VOID__OBJECT_BOOLEAN,
296           G_TYPE_NONE, 2, FOLKS_TYPE_INDIVIDUAL, G_TYPE_BOOLEAN);
297
298   signals[MEMBERS_CHANGED] =
299       g_signal_new ("members-changed",
300           G_TYPE_FROM_CLASS (klass),
301           G_SIGNAL_RUN_LAST,
302           0,
303           NULL, NULL,
304           _empathy_marshal_VOID__STRING_OBJECT_OBJECT_UINT,
305           G_TYPE_NONE,
306           4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_UINT);
307
308   g_type_class_add_private (object_class,
309       sizeof (EmpathyIndividualManagerPriv));
310 }
311
312 static void
313 empathy_individual_manager_init (EmpathyIndividualManager *self)
314 {
315   EmpathyIndividualManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
316       EMPATHY_TYPE_INDIVIDUAL_MANAGER, EmpathyIndividualManagerPriv);
317
318   self->priv = priv;
319   priv->contact_manager = empathy_contact_manager_dup_singleton ();
320   priv->individuals = g_hash_table_new_full (g_str_hash, g_str_equal,
321       NULL, g_object_unref);
322
323   priv->aggregator = folks_individual_aggregator_new ();
324   g_signal_connect (priv->aggregator, "individuals-changed",
325       G_CALLBACK (aggregator_individuals_changed_cb), self);
326   folks_individual_aggregator_prepare (priv->aggregator, NULL, NULL);
327 }
328
329 EmpathyIndividualManager *
330 empathy_individual_manager_dup_singleton (void)
331 {
332   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MANAGER, NULL);
333 }
334
335 GList *
336 empathy_individual_manager_get_members (EmpathyIndividualManager *self)
337 {
338   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
339
340   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
341
342   return g_hash_table_get_values (priv->individuals);
343 }
344
345 FolksIndividual *
346 empathy_individual_manager_lookup_member (EmpathyIndividualManager *self,
347     const gchar *id)
348 {
349   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
350
351   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
352
353   return g_hash_table_lookup (priv->individuals, id);
354 }
355
356 static void
357 aggregator_add_persona_from_details_cb (GObject *source,
358     GAsyncResult *result,
359     gpointer user_data)
360 {
361   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
362   EmpathyContact *contact = EMPATHY_CONTACT (user_data);
363   FolksPersona *persona;
364   GError *error = NULL;
365
366   persona = folks_individual_aggregator_add_persona_from_details_finish (
367       aggregator, result, &error);
368   if (error != NULL)
369     {
370       g_warning ("failed to add individual from contact: %s", error->message);
371       g_clear_error (&error);
372     }
373
374   /* The persona can be NULL even if there wasn't an error, if the persona was
375    * already in the contact list */
376   if (persona != NULL)
377     {
378       /* Set the contact's persona */
379       empathy_contact_set_persona (contact, persona);
380       g_object_unref (persona);
381     }
382
383   g_object_unref (contact);
384 }
385
386 void
387 empathy_individual_manager_add_from_contact (EmpathyIndividualManager *self,
388     EmpathyContact *contact)
389 {
390   EmpathyIndividualManagerPriv *priv;
391   GHashTable* details;
392   TpAccount *account;
393   const gchar *store_id;
394
395   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
396   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
397
398   priv = GET_PRIV (self);
399
400   /* We need to ref the contact since otherwise its linked TpHandle will be
401    * destroyed. */
402   g_object_ref (contact);
403
404   DEBUG ("adding individual from contact %s (%s)",
405       empathy_contact_get_id (contact), empathy_contact_get_alias (contact));
406
407   account = empathy_contact_get_account (contact);
408   store_id = tp_proxy_get_object_path (TP_PROXY (account));
409
410   details = tp_asv_new (
411       "contact", G_TYPE_STRING, empathy_contact_get_id (contact),
412       NULL);
413
414   folks_individual_aggregator_add_persona_from_details (
415       priv->aggregator, NULL, "telepathy", store_id, details,
416       aggregator_add_persona_from_details_cb, contact);
417
418   g_hash_table_destroy (details);
419 }
420
421 static void
422 aggregator_remove_individual_cb (GObject *source,
423     GAsyncResult *result,
424     gpointer user_data)
425 {
426   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
427   GError *error = NULL;
428
429   folks_individual_aggregator_remove_individual_finish (
430       aggregator, result, &error);
431   if (error != NULL)
432     {
433       g_warning ("failed to remove individual: %s", error->message);
434       g_clear_error (&error);
435     }
436 }
437
438 /**
439  * Removes the inner contact from the server (and thus the Individual). Not
440  * meant for de-shelling inner personas from an Individual.
441  */
442 void
443 empathy_individual_manager_remove (EmpathyIndividualManager *self,
444     FolksIndividual *individual,
445     const gchar *message)
446 {
447   EmpathyIndividualManagerPriv *priv;
448
449   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
450   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
451
452   priv = GET_PRIV (self);
453
454   DEBUG ("removing individual %s (%s)",
455       folks_individual_get_id (individual),
456       folks_individual_get_alias (individual));
457
458   folks_individual_aggregator_remove_individual (priv->aggregator, individual,
459       aggregator_remove_individual_cb, self);
460 }
461
462 static void
463 groups_change_group_cb (GObject *source,
464     GAsyncResult *result,
465     gpointer user_data)
466 {
467   FolksGroups *groups = FOLKS_GROUPS (source);
468   GError *error = NULL;
469
470   folks_groups_change_group_finish (groups, result, &error);
471   if (error != NULL)
472     {
473       g_warning ("failed to change group: %s", error->message);
474       g_clear_error (&error);
475     }
476 }
477
478 static void
479 remove_group_cb (const gchar *id,
480     FolksIndividual *individual,
481     const gchar *group)
482 {
483   folks_groups_change_group (FOLKS_GROUPS (individual), group, FALSE,
484       groups_change_group_cb, NULL);
485 }
486
487 void
488 empathy_individual_manager_remove_group (EmpathyIndividualManager *manager,
489     const gchar *group)
490 {
491   EmpathyIndividualManagerPriv *priv;
492
493   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager));
494   g_return_if_fail (group != NULL);
495
496   priv = GET_PRIV (manager);
497
498   DEBUG ("removing group %s", group);
499
500   /* Remove every individual from the group */
501   g_hash_table_foreach (priv->individuals, (GHFunc) remove_group_cb,
502       (gpointer) group);
503 }
504
505 EmpathyIndividualManagerFlags
506 empathy_individual_manager_get_flags_for_connection (
507     EmpathyIndividualManager *self,
508     TpConnection *connection)
509 {
510   EmpathyIndividualManagerPriv *priv;
511   EmpathyContactListFlags list_flags;
512   EmpathyIndividualManagerFlags flags;
513
514   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self),
515       EMPATHY_INDIVIDUAL_MANAGER_NO_FLAGS);
516
517   priv = GET_PRIV (self);
518
519   list_flags = empathy_contact_manager_get_flags_for_connection (
520     priv->contact_manager, connection);
521
522   flags = EMPATHY_INDIVIDUAL_MANAGER_NO_FLAGS;
523   if (list_flags & EMPATHY_CONTACT_LIST_CAN_ADD)
524     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_ADD;
525   if (list_flags & EMPATHY_CONTACT_LIST_CAN_REMOVE)
526     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE;
527   if (list_flags & EMPATHY_CONTACT_LIST_CAN_ALIAS)
528     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_ALIAS;
529   if (list_flags & EMPATHY_CONTACT_LIST_CAN_GROUP)
530     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_GROUP;
531
532   return flags;
533 }
534
535 static void
536 link_personas_cb (FolksIndividualAggregator *aggregator,
537     GAsyncResult *async_result,
538     gpointer user_data)
539 {
540   GError *error = NULL;
541
542   folks_individual_aggregator_link_personas_finish (aggregator, async_result,
543       &error);
544
545   if (error != NULL)
546     {
547       g_warning ("Failed to link personas: %s", error->message);
548       g_clear_error (&error);
549     }
550 }
551
552 void
553 empathy_individual_manager_link_personas (EmpathyIndividualManager *self,
554     GList *personas)
555 {
556   EmpathyIndividualManagerPriv *priv;
557
558   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
559   g_return_if_fail (personas != NULL);
560
561   priv = GET_PRIV (self);
562
563   DEBUG ("Linking %u personas", g_list_length (personas));
564
565   folks_individual_aggregator_link_personas (priv->aggregator, personas,
566       (GAsyncReadyCallback) link_personas_cb, NULL);
567 }
568
569 static void
570 unlink_individual_cb (FolksIndividualAggregator *aggregator,
571     GAsyncResult *async_result,
572     gpointer user_data)
573 {
574   GError *error = NULL;
575
576   folks_individual_aggregator_unlink_individual_finish (aggregator,
577       async_result, &error);
578
579   if (error != NULL)
580     {
581       g_warning ("Failed to unlink individual: %s", error->message);
582       g_clear_error (&error);
583     }
584 }
585
586 void
587 empathy_individual_manager_unlink_individual (EmpathyIndividualManager *self,
588     FolksIndividual *individual)
589 {
590   EmpathyIndividualManagerPriv *priv;
591
592   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
593   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
594
595   priv = GET_PRIV (self);
596
597   DEBUG ("Unlinking individual '%s'", folks_individual_get_id (individual));
598
599   folks_individual_aggregator_unlink_individual (priv->aggregator, individual,
600       (GAsyncReadyCallback) unlink_individual_cb, NULL);
601 }