]> git.0d.be Git - empathy.git/blob - libempathy/empathy-individual-manager.c
Update Simplified Chinese translation.
[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 #include <folks/folks-telepathy.h>
34
35 #include <extensions/extensions.h>
36
37 #include "empathy-individual-manager.h"
38 #include "empathy-marshal.h"
39 #include "empathy-utils.h"
40 #include "empathy-contact-manager.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   GHashTable *individuals; /* Individual.id -> Individual */
55 } EmpathyIndividualManagerPriv;
56
57 enum
58 {
59   FAVOURITES_CHANGED,
60   GROUPS_CHANGED,
61   MEMBERS_CHANGED,
62   LAST_SIGNAL
63 };
64
65 static guint signals[LAST_SIGNAL] = { 0 };
66
67 G_DEFINE_TYPE (EmpathyIndividualManager, empathy_individual_manager,
68     G_TYPE_OBJECT);
69
70 static EmpathyIndividualManager *manager_singleton = NULL;
71
72 static void
73 individual_group_changed_cb (FolksIndividual *individual,
74     gchar *group,
75     gboolean is_member,
76     EmpathyIndividualManager *self)
77 {
78   g_signal_emit (self, signals[GROUPS_CHANGED], 0, individual, group,
79       is_member);
80 }
81
82 static void
83 individual_notify_is_favourite_cb (FolksIndividual *individual,
84     GParamSpec *pspec,
85     EmpathyIndividualManager *self)
86 {
87   gboolean is_favourite = folks_favourite_details_get_is_favourite (
88       FOLKS_FAVOURITE_DETAILS (individual));
89   g_signal_emit (self, signals[FAVOURITES_CHANGED], 0, individual,
90       is_favourite);
91 }
92
93 static void
94 add_individual (EmpathyIndividualManager *self, FolksIndividual *individual)
95 {
96   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
97
98   g_hash_table_insert (priv->individuals,
99       (gpointer) folks_individual_get_id (individual),
100       g_object_ref (individual));
101
102   g_signal_connect (individual, "group-changed",
103       G_CALLBACK (individual_group_changed_cb), self);
104   g_signal_connect (individual, "notify::is-favourite",
105       G_CALLBACK (individual_notify_is_favourite_cb), self);
106 }
107
108 static void
109 remove_individual (EmpathyIndividualManager *self, FolksIndividual *individual)
110 {
111   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
112
113   g_signal_handlers_disconnect_by_func (individual,
114       individual_group_changed_cb, self);
115   g_signal_handlers_disconnect_by_func (individual,
116       individual_notify_is_favourite_cb, self);
117
118   g_hash_table_remove (priv->individuals, folks_individual_get_id (individual));
119 }
120
121 /* This is emitted for *all* individuals in the individual aggregator (not
122  * just the ones we keep a reference to), to allow for the case where a new
123  * individual doesn't contain an EmpathyContact, but later has a persona added
124  * which does. */
125 static void
126 individual_notify_personas_cb (FolksIndividual *individual,
127     GParamSpec *pspec,
128     EmpathyIndividualManager *self)
129 {
130   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
131
132   const gchar *id = folks_individual_get_id (individual);
133   gboolean has_contact = empathy_folks_individual_contains_contact (individual);
134   gboolean had_contact = (g_hash_table_lookup (priv->individuals,
135       id) != NULL) ? TRUE : FALSE;
136
137   if (had_contact == TRUE && has_contact == FALSE)
138     {
139       GList *removed = NULL;
140
141       /* The Individual has lost its EmpathyContact */
142       removed = g_list_prepend (removed, individual);
143       g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, NULL, removed,
144           TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
145       g_list_free (removed);
146
147       remove_individual (self, individual);
148     }
149   else if (had_contact == FALSE && has_contact == TRUE)
150     {
151       GList *added = NULL;
152
153       /* The Individual has gained its first EmpathyContact */
154       add_individual (self, individual);
155
156       added = g_list_prepend (added, individual);
157       g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, added, NULL,
158           TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
159       g_list_free (added);
160     }
161 }
162
163 static void
164 aggregator_individuals_changed_cb (FolksIndividualAggregator *aggregator,
165     GList *added,
166     GList *removed,
167     const char *message,
168     FolksPersona *actor,
169     guint reason,
170     EmpathyIndividualManager *self)
171 {
172   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
173   GList *l, *added_filtered = NULL;
174
175   /* Handle the removals first, as one of the added Individuals might have the
176    * same ID as one of the removed Individuals (due to linking). */
177   for (l = removed; l; l = l->next)
178     {
179       FolksIndividual *ind = FOLKS_INDIVIDUAL (l->data);
180
181       g_signal_handlers_disconnect_by_func (ind,
182           individual_notify_personas_cb, self);
183
184       if (g_hash_table_lookup (priv->individuals,
185           folks_individual_get_id (ind)) != NULL)
186         remove_individual (self, ind);
187     }
188
189   /* Filter the individuals for ones which contain EmpathyContacts */
190   for (l = added; l; l = l->next)
191     {
192       FolksIndividual *ind = FOLKS_INDIVIDUAL (l->data);
193
194       g_signal_connect (ind, "notify::personas",
195           G_CALLBACK (individual_notify_personas_cb), self);
196
197       if (empathy_folks_individual_contains_contact (ind) == TRUE)
198         {
199           add_individual (self, ind);
200           added_filtered = g_list_prepend (added_filtered, ind);
201         }
202     }
203
204   /* Bail if we have no individuals left */
205   if (added_filtered == NULL && removed == NULL)
206     return;
207
208   added_filtered = g_list_reverse (added_filtered);
209
210   g_signal_emit (self, signals[MEMBERS_CHANGED], 0, message,
211       added_filtered, removed,
212       tp_channel_group_change_reason_from_folks_groups_change_reason (reason),
213       TRUE);
214
215   g_list_free (added_filtered);
216 }
217
218 static void
219 individual_manager_dispose (GObject *object)
220 {
221   EmpathyIndividualManagerPriv *priv = GET_PRIV (object);
222
223   g_hash_table_destroy (priv->individuals);
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->individuals = g_hash_table_new_full (g_str_hash, g_str_equal,
318       NULL, g_object_unref);
319
320   priv->aggregator = folks_individual_aggregator_new ();
321   g_signal_connect (priv->aggregator, "individuals-changed",
322       G_CALLBACK (aggregator_individuals_changed_cb), self);
323   folks_individual_aggregator_prepare (priv->aggregator, NULL, NULL);
324 }
325
326 EmpathyIndividualManager *
327 empathy_individual_manager_dup_singleton (void)
328 {
329   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MANAGER, NULL);
330 }
331
332 GList *
333 empathy_individual_manager_get_members (EmpathyIndividualManager *self)
334 {
335   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
336
337   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
338
339   return g_hash_table_get_values (priv->individuals);
340 }
341
342 FolksIndividual *
343 empathy_individual_manager_lookup_member (EmpathyIndividualManager *self,
344     const gchar *id)
345 {
346   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
347
348   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
349
350   return g_hash_table_lookup (priv->individuals, id);
351 }
352
353 static void
354 aggregator_add_persona_from_details_cb (GObject *source,
355     GAsyncResult *result,
356     gpointer user_data)
357 {
358   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
359   EmpathyContact *contact = EMPATHY_CONTACT (user_data);
360   FolksPersona *persona;
361   GError *error = NULL;
362
363   persona = folks_individual_aggregator_add_persona_from_details_finish (
364       aggregator, result, &error);
365   if (error != NULL)
366     {
367       g_warning ("failed to add individual from contact: %s", error->message);
368       g_clear_error (&error);
369     }
370
371   /* The persona can be NULL even if there wasn't an error, if the persona was
372    * already in the contact list */
373   if (persona != NULL)
374     {
375       /* Set the contact's persona */
376       empathy_contact_set_persona (contact, persona);
377       g_object_unref (persona);
378     }
379
380   g_object_unref (contact);
381 }
382
383 void
384 empathy_individual_manager_add_from_contact (EmpathyIndividualManager *self,
385     EmpathyContact *contact)
386 {
387   EmpathyIndividualManagerPriv *priv;
388   FolksBackendStore *backend_store;
389   FolksBackend *backend;
390   FolksPersonaStore *persona_store;
391   GHashTable* details, *persona_stores;
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   /* Get the persona store to use */
411   backend_store = folks_backend_store_dup ();
412   backend =
413       folks_backend_store_dup_backend_by_name (backend_store, "telepathy");
414
415   if (backend == NULL)
416     {
417       g_warning ("Failed to add individual from contact: couldn't get "
418           "'telepathy' backend");
419       goto finish;
420     }
421
422   persona_stores = folks_backend_get_persona_stores (backend);
423   persona_store = g_hash_table_lookup (persona_stores, store_id);
424
425   if (persona_store == NULL)
426     {
427       g_warning ("Failed to add individual from contact: couldn't get persona "
428           "store '%s'", store_id);
429       goto finish;
430     }
431
432   details = tp_asv_new (
433       "contact", G_TYPE_STRING, empathy_contact_get_id (contact),
434       NULL);
435
436   folks_individual_aggregator_add_persona_from_details (
437       priv->aggregator, NULL, persona_store, details,
438       aggregator_add_persona_from_details_cb, contact);
439
440   g_hash_table_destroy (details);
441
442 finish:
443   tp_clear_object (&backend);
444   tp_clear_object (&backend_store);
445 }
446
447 static void
448 aggregator_remove_individual_cb (GObject *source,
449     GAsyncResult *result,
450     gpointer user_data)
451 {
452   FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
453   GError *error = NULL;
454
455   folks_individual_aggregator_remove_individual_finish (
456       aggregator, result, &error);
457   if (error != NULL)
458     {
459       g_warning ("failed to remove individual: %s", error->message);
460       g_clear_error (&error);
461     }
462 }
463
464 /**
465  * Removes the inner contact from the server (and thus the Individual). Not
466  * meant for de-shelling inner personas from an Individual.
467  */
468 void
469 empathy_individual_manager_remove (EmpathyIndividualManager *self,
470     FolksIndividual *individual,
471     const gchar *message)
472 {
473   EmpathyIndividualManagerPriv *priv;
474
475   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
476   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
477
478   priv = GET_PRIV (self);
479
480   DEBUG ("removing individual %s (%s)",
481       folks_individual_get_id (individual),
482       folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
483
484   folks_individual_aggregator_remove_individual (priv->aggregator, individual,
485       aggregator_remove_individual_cb, self);
486 }
487
488 /* FIXME: The parameter @self is not required and the method can be placed in
489  * utilities. I left it as it is to stay coherent with empathy-2.34 */
490 /**
491  * empathy_individual_manager_supports_blocking
492  * @self: the #EmpathyIndividualManager
493  * @individual: an individual to check
494  *
495  * Indicates whether any personas of an @individual can be blocked.
496  *
497  * Returns: %TRUE if any persona supports contact blocking
498  */
499 gboolean
500 empathy_individual_manager_supports_blocking (EmpathyIndividualManager *self,
501     FolksIndividual *individual)
502 {
503   GList *personas, *l;
504
505   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), FALSE);
506
507   personas = folks_individual_get_personas (individual);
508
509   for (l = personas; l != NULL; l = l->next)
510     {
511       TpfPersona *persona = l->data;
512       TpConnection *conn;
513       EmpathyContactManager *manager;
514
515       if (!TPF_IS_PERSONA (persona))
516         continue;
517
518       conn = tp_contact_get_connection (tpf_persona_get_contact (persona));
519       manager = empathy_contact_manager_dup_singleton ();
520
521       if (empathy_contact_manager_get_flags_for_connection (manager, conn) &
522           EMPATHY_CONTACT_LIST_CAN_BLOCK)
523         return TRUE;
524
525       g_object_unref (manager);
526     }
527
528   return FALSE;
529 }
530
531 void
532 empathy_individual_manager_set_blocked (EmpathyIndividualManager *self,
533     FolksIndividual *individual,
534     gboolean blocked,
535     gboolean abusive)
536 {
537   GList *personas, *l;
538
539   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
540
541   personas = folks_individual_get_personas (individual);
542
543   for (l = personas; l != NULL; l = l->next)
544     {
545       TpfPersona *persona = l->data;
546       EmpathyContact *contact;
547       EmpathyContactManager *manager;
548
549       if (!TPF_IS_PERSONA (persona))
550         continue;
551
552       contact = empathy_contact_dup_from_tp_contact (
553           tpf_persona_get_contact (persona));
554       empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
555       manager = empathy_contact_manager_dup_singleton ();
556       empathy_contact_list_set_blocked (
557           EMPATHY_CONTACT_LIST (manager),
558           contact, blocked, abusive);
559
560       g_object_unref (manager);
561       g_object_unref (contact);
562     }
563 }
564
565 static void
566 groups_change_group_cb (GObject *source,
567     GAsyncResult *result,
568     gpointer user_data)
569 {
570   FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
571   GError *error = NULL;
572
573   folks_group_details_change_group_finish (group_details, result, &error);
574   if (error != NULL)
575     {
576       g_warning ("failed to change group: %s", error->message);
577       g_clear_error (&error);
578     }
579 }
580
581 static void
582 remove_group_cb (const gchar *id,
583     FolksIndividual *individual,
584     const gchar *group)
585 {
586   folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual), group,
587       FALSE, groups_change_group_cb, NULL);
588 }
589
590 void
591 empathy_individual_manager_remove_group (EmpathyIndividualManager *manager,
592     const gchar *group)
593 {
594   EmpathyIndividualManagerPriv *priv;
595
596   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager));
597   g_return_if_fail (group != NULL);
598
599   priv = GET_PRIV (manager);
600
601   DEBUG ("removing group %s", group);
602
603   /* Remove every individual from the group */
604   g_hash_table_foreach (priv->individuals, (GHFunc) remove_group_cb,
605       (gpointer) group);
606 }
607
608 static void
609 link_personas_cb (FolksIndividualAggregator *aggregator,
610     GAsyncResult *async_result,
611     gpointer user_data)
612 {
613   GError *error = NULL;
614
615   folks_individual_aggregator_link_personas_finish (aggregator, async_result,
616       &error);
617
618   if (error != NULL)
619     {
620       g_warning ("Failed to link personas: %s", error->message);
621       g_clear_error (&error);
622     }
623 }
624
625 void
626 empathy_individual_manager_link_personas (EmpathyIndividualManager *self,
627     GList *personas)
628 {
629   EmpathyIndividualManagerPriv *priv;
630
631   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
632   g_return_if_fail (personas != NULL);
633
634   priv = GET_PRIV (self);
635
636   DEBUG ("Linking %u personas", g_list_length (personas));
637
638   folks_individual_aggregator_link_personas (priv->aggregator, personas,
639       (GAsyncReadyCallback) link_personas_cb, NULL);
640 }
641
642 static void
643 unlink_individual_cb (FolksIndividualAggregator *aggregator,
644     GAsyncResult *async_result,
645     gpointer user_data)
646 {
647   GError *error = NULL;
648
649   folks_individual_aggregator_unlink_individual_finish (aggregator,
650       async_result, &error);
651
652   if (error != NULL)
653     {
654       g_warning ("Failed to unlink individual: %s", error->message);
655       g_clear_error (&error);
656     }
657 }
658
659 void
660 empathy_individual_manager_unlink_individual (EmpathyIndividualManager *self,
661     FolksIndividual *individual)
662 {
663   EmpathyIndividualManagerPriv *priv;
664
665   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
666   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
667
668   priv = GET_PRIV (self);
669
670   DEBUG ("Unlinking individual '%s'", folks_individual_get_id (individual));
671
672   folks_individual_aggregator_unlink_individual (priv->aggregator, individual,
673       (GAsyncReadyCallback) unlink_individual_cb, NULL);
674 }