]> git.0d.be Git - empathy.git/blob - libempathy/empathy-individual-manager.c
Merge branch 'trivia'
[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   /* Filter the individuals for ones which contain EmpathyContacts */
177   for (l = added; l; l = l->next)
178     {
179       FolksIndividual *ind = FOLKS_INDIVIDUAL (l->data);
180
181       g_signal_connect (ind, "notify::personas",
182           G_CALLBACK (individual_notify_personas_cb), self);
183
184       if (empathy_folks_individual_contains_contact (ind) == TRUE)
185         {
186           add_individual (self, ind);
187           added_filtered = g_list_prepend (added_filtered, ind);
188         }
189     }
190
191   for (l = removed; l; l = l->next)
192     {
193       FolksIndividual *ind = FOLKS_INDIVIDUAL (l->data);
194
195       g_signal_handlers_disconnect_by_func (ind,
196           individual_notify_personas_cb, self);
197
198       if (g_hash_table_lookup (priv->individuals,
199           folks_individual_get_id (ind)) != NULL)
200         remove_individual (self, ind);
201     }
202
203   /* Bail if we have no individuals left */
204   if (added_filtered == NULL && removed == NULL)
205     return;
206
207   added_filtered = g_list_reverse (added_filtered);
208
209   g_signal_emit (self, signals[MEMBERS_CHANGED], 0, message,
210       added_filtered, removed,
211       tp_chanel_group_change_reason_from_folks_groups_change_reason (reason),
212       TRUE);
213
214   g_list_free (added_filtered);
215 }
216
217 static void
218 individual_manager_dispose (GObject *object)
219 {
220   EmpathyIndividualManagerPriv *priv = GET_PRIV (object);
221
222   g_hash_table_destroy (priv->individuals);
223   tp_clear_object (&priv->contact_manager);
224   tp_clear_object (&priv->aggregator);
225
226   G_OBJECT_CLASS (empathy_individual_manager_parent_class)->dispose (object);
227 }
228
229 static GObject *
230 individual_manager_constructor (GType type,
231     guint n_props,
232     GObjectConstructParam *props)
233 {
234   GObject *retval;
235
236   if (manager_singleton)
237     {
238       retval = g_object_ref (manager_singleton);
239     }
240   else
241     {
242       retval =
243           G_OBJECT_CLASS (empathy_individual_manager_parent_class)->
244           constructor (type, n_props, props);
245
246       manager_singleton = EMPATHY_INDIVIDUAL_MANAGER (retval);
247       g_object_add_weak_pointer (retval, (gpointer) & manager_singleton);
248     }
249
250   return retval;
251 }
252
253 /**
254  * empathy_individual_manager_initialized:
255  *
256  * Reports whether or not the singleton has already been created.
257  *
258  * There can be instances where you want to access the #EmpathyIndividualManager
259  * only if it has been set up for this process.
260  *
261  * Returns: %TRUE if the #EmpathyIndividualManager singleton has previously
262  * been initialized.
263  */
264 gboolean
265 empathy_individual_manager_initialized (void)
266 {
267   return (manager_singleton != NULL);
268 }
269
270 static void
271 empathy_individual_manager_class_init (EmpathyIndividualManagerClass *klass)
272 {
273   GObjectClass *object_class = G_OBJECT_CLASS (klass);
274
275   object_class->dispose = individual_manager_dispose;
276   object_class->constructor = individual_manager_constructor;
277
278   signals[GROUPS_CHANGED] =
279       g_signal_new ("groups-changed",
280           G_TYPE_FROM_CLASS (klass),
281           G_SIGNAL_RUN_LAST,
282           0,
283           NULL, NULL,
284           _empathy_marshal_VOID__OBJECT_STRING_BOOLEAN,
285           G_TYPE_NONE, 3, FOLKS_TYPE_INDIVIDUAL, G_TYPE_STRING, G_TYPE_BOOLEAN);
286
287   signals[FAVOURITES_CHANGED] =
288       g_signal_new ("favourites-changed",
289           G_TYPE_FROM_CLASS (klass),
290           G_SIGNAL_RUN_LAST,
291           0,
292           NULL, NULL,
293           _empathy_marshal_VOID__OBJECT_BOOLEAN,
294           G_TYPE_NONE, 2, FOLKS_TYPE_INDIVIDUAL, G_TYPE_BOOLEAN);
295
296   signals[MEMBERS_CHANGED] =
297       g_signal_new ("members-changed",
298           G_TYPE_FROM_CLASS (klass),
299           G_SIGNAL_RUN_LAST,
300           0,
301           NULL, NULL,
302           _empathy_marshal_VOID__STRING_OBJECT_OBJECT_UINT,
303           G_TYPE_NONE,
304           4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_UINT);
305
306   g_type_class_add_private (object_class,
307       sizeof (EmpathyIndividualManagerPriv));
308 }
309
310 static void
311 empathy_individual_manager_init (EmpathyIndividualManager *self)
312 {
313   EmpathyIndividualManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
314       EMPATHY_TYPE_INDIVIDUAL_MANAGER, EmpathyIndividualManagerPriv);
315
316   self->priv = priv;
317   priv->contact_manager = empathy_contact_manager_dup_singleton ();
318   priv->individuals = g_hash_table_new_full (g_str_hash, g_str_equal,
319       NULL, g_object_unref);
320
321   priv->aggregator = folks_individual_aggregator_new ();
322   g_signal_connect (priv->aggregator, "individuals-changed",
323       G_CALLBACK (aggregator_individuals_changed_cb), self);
324   folks_individual_aggregator_prepare (priv->aggregator, NULL, NULL);
325 }
326
327 EmpathyIndividualManager *
328 empathy_individual_manager_dup_singleton (void)
329 {
330   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MANAGER, NULL);
331 }
332
333 GList *
334 empathy_individual_manager_get_members (EmpathyIndividualManager *self)
335 {
336   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
337
338   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
339
340   return g_hash_table_get_values (priv->individuals);
341 }
342
343 FolksIndividual *
344 empathy_individual_manager_lookup_member (EmpathyIndividualManager *self,
345     const gchar *id)
346 {
347   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
348
349   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
350
351   return g_hash_table_lookup (priv->individuals, id);
352 }
353
354 static void
355 aggregator_add_persona_from_details_cb (GObject *source,
356     GAsyncResult *result,
357     gpointer user_data)
358 {
359   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
360   EmpathyContact *contact = EMPATHY_CONTACT (user_data);
361   FolksPersona *persona;
362   GError *error = NULL;
363
364   persona = folks_individual_aggregator_add_persona_from_details_finish (
365       aggregator, result, &error);
366   if (error != NULL)
367     {
368       g_warning ("failed to add individual from contact: %s", error->message);
369       g_clear_error (&error);
370     }
371
372   /* The persona can be NULL even if there wasn't an error, if the persona was
373    * already in the contact list */
374   if (persona != NULL)
375     {
376       /* Set the contact's persona */
377       empathy_contact_set_persona (contact, persona);
378       g_object_unref (persona);
379     }
380
381   g_object_unref (contact);
382 }
383
384 void
385 empathy_individual_manager_add_from_contact (EmpathyIndividualManager *self,
386     EmpathyContact *contact)
387 {
388   EmpathyIndividualManagerPriv *priv;
389   GHashTable* details;
390   TpAccount *account;
391   const gchar *store_id;
392
393   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
394   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
395
396   priv = GET_PRIV (self);
397
398   /* We need to ref the contact since otherwise its linked TpHandle will be
399    * destroyed. */
400   g_object_ref (contact);
401
402   DEBUG ("adding individual from contact %s (%s)",
403       empathy_contact_get_id (contact), empathy_contact_get_alias (contact));
404
405   account = empathy_contact_get_account (contact);
406   store_id = tp_proxy_get_object_path (TP_PROXY (account));
407
408   details = tp_asv_new (
409       "contact", G_TYPE_STRING, empathy_contact_get_id (contact),
410       NULL);
411
412   folks_individual_aggregator_add_persona_from_details (
413       priv->aggregator, NULL, "telepathy", store_id, details,
414       aggregator_add_persona_from_details_cb, contact);
415
416   g_hash_table_destroy (details);
417 }
418
419 static void
420 aggregator_remove_individual_cb (GObject *source,
421     GAsyncResult *result,
422     gpointer user_data)
423 {
424   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
425   GError *error = NULL;
426
427   folks_individual_aggregator_remove_individual_finish (
428       aggregator, result, &error);
429   if (error != NULL)
430     {
431       g_warning ("failed to remove individual: %s", error->message);
432       g_clear_error (&error);
433     }
434 }
435
436 /**
437  * Removes the inner contact from the server (and thus the Individual). Not
438  * meant for de-shelling inner personas from an Individual.
439  */
440 void
441 empathy_individual_manager_remove (EmpathyIndividualManager *self,
442     FolksIndividual *individual,
443     const gchar *message)
444 {
445   EmpathyIndividualManagerPriv *priv;
446
447   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
448   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
449
450   priv = GET_PRIV (self);
451
452   DEBUG ("removing individual %s (%s)",
453       folks_individual_get_id (individual),
454       folks_individual_get_alias (individual));
455
456   folks_individual_aggregator_remove_individual (priv->aggregator, individual,
457       aggregator_remove_individual_cb, self);
458 }
459
460 static void
461 groups_change_group_cb (GObject *source,
462     GAsyncResult *result,
463     gpointer user_data)
464 {
465   FolksGroups *groups = FOLKS_GROUPS (source);
466   GError *error = NULL;
467
468   folks_groups_change_group_finish (groups, result, &error);
469   if (error != NULL)
470     {
471       g_warning ("failed to change group: %s", error->message);
472       g_clear_error (&error);
473     }
474 }
475
476 static void
477 remove_group_cb (const gchar *id,
478     FolksIndividual *individual,
479     const gchar *group)
480 {
481   folks_groups_change_group (FOLKS_GROUPS (individual), group, FALSE,
482       groups_change_group_cb, NULL);
483 }
484
485 void
486 empathy_individual_manager_remove_group (EmpathyIndividualManager *manager,
487     const gchar *group)
488 {
489   EmpathyIndividualManagerPriv *priv;
490
491   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager));
492   g_return_if_fail (group != NULL);
493
494   priv = GET_PRIV (manager);
495
496   DEBUG ("removing group %s", group);
497
498   /* Remove every individual from the group */
499   g_hash_table_foreach (priv->individuals, (GHFunc) remove_group_cb,
500       (gpointer) group);
501 }
502
503 EmpathyIndividualManagerFlags
504 empathy_individual_manager_get_flags_for_connection (
505     EmpathyIndividualManager *self,
506     TpConnection *connection)
507 {
508   EmpathyIndividualManagerPriv *priv;
509   EmpathyContactListFlags list_flags;
510   EmpathyIndividualManagerFlags flags;
511
512   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self),
513       EMPATHY_INDIVIDUAL_MANAGER_NO_FLAGS);
514
515   priv = GET_PRIV (self);
516
517   list_flags = empathy_contact_manager_get_flags_for_connection (
518     priv->contact_manager, connection);
519
520   flags = EMPATHY_INDIVIDUAL_MANAGER_NO_FLAGS;
521   if (list_flags & EMPATHY_CONTACT_LIST_CAN_ADD)
522     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_ADD;
523   if (list_flags & EMPATHY_CONTACT_LIST_CAN_REMOVE)
524     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE;
525   if (list_flags & EMPATHY_CONTACT_LIST_CAN_ALIAS)
526     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_ALIAS;
527   if (list_flags & EMPATHY_CONTACT_LIST_CAN_GROUP)
528     flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_GROUP;
529
530   return flags;
531 }
532
533 static void
534 link_personas_cb (FolksIndividualAggregator *aggregator,
535     GAsyncResult *async_result,
536     gpointer user_data)
537 {
538   GError *error = NULL;
539
540   folks_individual_aggregator_link_personas_finish (aggregator, async_result,
541       &error);
542
543   if (error != NULL)
544     {
545       g_warning ("Failed to link personas: %s", error->message);
546       g_clear_error (&error);
547     }
548 }
549
550 void
551 empathy_individual_manager_link_personas (EmpathyIndividualManager *self,
552     GList *personas)
553 {
554   EmpathyIndividualManagerPriv *priv;
555
556   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
557   g_return_if_fail (personas != NULL);
558
559   priv = GET_PRIV (self);
560
561   DEBUG ("Linking %u personas", g_list_length (personas));
562
563   folks_individual_aggregator_link_personas (priv->aggregator, personas,
564       (GAsyncReadyCallback) link_personas_cb, NULL);
565 }
566
567 static void
568 unlink_individual_cb (FolksIndividualAggregator *aggregator,
569     GAsyncResult *async_result,
570     gpointer user_data)
571 {
572   GError *error = NULL;
573
574   folks_individual_aggregator_unlink_individual_finish (aggregator,
575       async_result, &error);
576
577   if (error != NULL)
578     {
579       g_warning ("Failed to unlink individual: %s", error->message);
580       g_clear_error (&error);
581     }
582 }
583
584 void
585 empathy_individual_manager_unlink_individual (EmpathyIndividualManager *self,
586     FolksIndividual *individual)
587 {
588   EmpathyIndividualManagerPriv *priv;
589
590   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
591   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
592
593   priv = GET_PRIV (self);
594
595   DEBUG ("Unlinking individual '%s'", folks_individual_get_id (individual));
596
597   folks_individual_aggregator_unlink_individual (priv->aggregator, individual,
598       (GAsyncReadyCallback) unlink_individual_cb, NULL);
599 }