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