]> git.0d.be Git - empathy.git/blobdiff - libempathy/empathy-individual-manager.c
Merge branch 'sasl'
[empathy.git] / libempathy / empathy-individual-manager.c
index de296cd8095fefb214f5af901db1f28b6ccbf662..82884e4d1f6e26003e5d5739afcf4261cc4556a0 100644 (file)
@@ -34,8 +34,6 @@
 #include <extensions/extensions.h>
 
 #include "empathy-individual-manager.h"
-#include "empathy-contact-manager.h"
-#include "empathy-contact-list.h"
 #include "empathy-marshal.h"
 #include "empathy-utils.h"
 
 
 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualManager)
 
-/* This class doesn't store or ref any of the individuals, since they're already
- * stored and referenced in the aggregator.
+/* This class only stores and refs Individuals who contain an EmpathyContact.
  *
  * This class merely forwards along signals from the aggregator and individuals
  * and wraps aggregator functions for other client code. */
 typedef struct
 {
   FolksIndividualAggregator *aggregator;
-  EmpathyContactManager *contact_manager;
+  GHashTable *individuals; /* Individual.id -> Individual */
 } EmpathyIndividualManagerPriv;
 
 enum
@@ -92,43 +89,128 @@ individual_notify_is_favourite_cb (FolksIndividual *individual,
 }
 
 static void
-aggregator_individuals_added_cb (FolksIndividualAggregator *aggregator,
-    GList *individuals,
+add_individual (EmpathyIndividualManager *self, FolksIndividual *individual)
+{
+  EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
+
+  g_hash_table_insert (priv->individuals,
+      (gpointer) folks_individual_get_id (individual),
+      g_object_ref (individual));
+
+  g_signal_connect (individual, "group-changed",
+      G_CALLBACK (individual_group_changed_cb), self);
+  g_signal_connect (individual, "notify::is-favourite",
+      G_CALLBACK (individual_notify_is_favourite_cb), self);
+}
+
+static void
+remove_individual (EmpathyIndividualManager *self, FolksIndividual *individual)
+{
+  EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
+
+  g_signal_handlers_disconnect_by_func (individual,
+      individual_group_changed_cb, self);
+  g_signal_handlers_disconnect_by_func (individual,
+      individual_notify_is_favourite_cb, self);
+
+  g_hash_table_remove (priv->individuals, folks_individual_get_id (individual));
+}
+
+/* This is emitted for *all* individuals in the individual aggregator (not
+ * just the ones we keep a reference to), to allow for the case where a new
+ * individual doesn't contain an EmpathyContact, but later has a persona added
+ * which does. */
+static void
+individual_notify_personas_cb (FolksIndividual *individual,
+    GParamSpec *pspec,
     EmpathyIndividualManager *self)
 {
-  GList *l;
+  EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
+
+  const gchar *id = folks_individual_get_id (individual);
+  gboolean has_contact = empathy_folks_individual_contains_contact (individual);
+  gboolean had_contact = (g_hash_table_lookup (priv->individuals,
+      id) != NULL) ? TRUE : FALSE;
 
-  for (l = individuals; l; l = l->next)
+  if (had_contact == TRUE && has_contact == FALSE)
     {
-      g_signal_connect (l->data, "group-changed",
-          G_CALLBACK (individual_group_changed_cb), self);
-      g_signal_connect (l->data, "notify::is-favourite",
-          G_CALLBACK (individual_notify_is_favourite_cb), self);
+      GList *removed = NULL;
+
+      /* The Individual has lost its EmpathyContact */
+      removed = g_list_prepend (removed, individual);
+      g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, NULL, removed,
+          TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
+      g_list_free (removed);
+
+      remove_individual (self, individual);
     }
+  else if (had_contact == FALSE && has_contact == TRUE)
+    {
+      GList *added = NULL;
 
-  /* TODO: don't hard-code the reason or message */
-  g_signal_emit (self, signals[MEMBERS_CHANGED], 0, "individual(s) added",
-      individuals, NULL, TP_CHANNEL_GROUP_CHANGE_REASON_NONE, TRUE);
+      /* The Individual has gained its first EmpathyContact */
+      add_individual (self, individual);
+
+      added = g_list_prepend (added, individual);
+      g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, added, NULL,
+          TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
+      g_list_free (added);
+    }
 }
 
 static void
-aggregator_individuals_removed_cb (FolksIndividualAggregator *aggregator,
-    GList *individuals,
+aggregator_individuals_changed_cb (FolksIndividualAggregator *aggregator,
+    GList *added,
+    GList *removed,
+    const char *message,
+    FolksPersona *actor,
+    guint reason,
     EmpathyIndividualManager *self)
 {
-  GList *l;
+  EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
+  GList *l, *added_filtered = NULL;
 
-  for (l = individuals; l; l = l->next)
+  /* Handle the removals first, as one of the added Individuals might have the
+   * same ID as one of the removed Individuals (due to linking). */
+  for (l = removed; l; l = l->next)
     {
-      g_signal_handlers_disconnect_by_func (l->data,
-          individual_group_changed_cb, self);
-      g_signal_handlers_disconnect_by_func (l->data,
-          individual_notify_is_favourite_cb, self);
+      FolksIndividual *ind = FOLKS_INDIVIDUAL (l->data);
+
+      g_signal_handlers_disconnect_by_func (ind,
+          individual_notify_personas_cb, self);
+
+      if (g_hash_table_lookup (priv->individuals,
+          folks_individual_get_id (ind)) != NULL)
+        remove_individual (self, ind);
     }
 
-  /* TODO: don't hard-code the reason or message */
-  g_signal_emit (self, signals[MEMBERS_CHANGED], 0, "individual(s) removed",
-      NULL, individuals, TP_CHANNEL_GROUP_CHANGE_REASON_NONE, TRUE);
+  /* Filter the individuals for ones which contain EmpathyContacts */
+  for (l = added; l; l = l->next)
+    {
+      FolksIndividual *ind = FOLKS_INDIVIDUAL (l->data);
+
+      g_signal_connect (ind, "notify::personas",
+          G_CALLBACK (individual_notify_personas_cb), self);
+
+      if (empathy_folks_individual_contains_contact (ind) == TRUE)
+        {
+          add_individual (self, ind);
+          added_filtered = g_list_prepend (added_filtered, ind);
+        }
+    }
+
+  /* Bail if we have no individuals left */
+  if (added_filtered == NULL && removed == NULL)
+    return;
+
+  added_filtered = g_list_reverse (added_filtered);
+
+  g_signal_emit (self, signals[MEMBERS_CHANGED], 0, message,
+      added_filtered, removed,
+      tp_channel_group_change_reason_from_folks_groups_change_reason (reason),
+      TRUE);
+
+  g_list_free (added_filtered);
 }
 
 static void
@@ -136,7 +218,7 @@ individual_manager_dispose (GObject *object)
 {
   EmpathyIndividualManagerPriv *priv = GET_PRIV (object);
 
-  tp_clear_object (&priv->contact_manager);
+  g_hash_table_destroy (priv->individuals);
   tp_clear_object (&priv->aggregator);
 
   G_OBJECT_CLASS (empathy_individual_manager_parent_class)->dispose (object);
@@ -230,13 +312,13 @@ empathy_individual_manager_init (EmpathyIndividualManager *self)
       EMPATHY_TYPE_INDIVIDUAL_MANAGER, EmpathyIndividualManagerPriv);
 
   self->priv = priv;
-  priv->contact_manager = empathy_contact_manager_dup_singleton ();
+  priv->individuals = g_hash_table_new_full (g_str_hash, g_str_equal,
+      NULL, g_object_unref);
 
   priv->aggregator = folks_individual_aggregator_new ();
-  g_signal_connect (priv->aggregator, "individuals-added",
-      G_CALLBACK (aggregator_individuals_added_cb), self);
-  g_signal_connect (priv->aggregator, "individuals-removed",
-      G_CALLBACK (aggregator_individuals_removed_cb), self);
+  g_signal_connect (priv->aggregator, "individuals-changed",
+      G_CALLBACK (aggregator_individuals_changed_cb), self);
+  folks_individual_aggregator_prepare (priv->aggregator, NULL, NULL);
 }
 
 EmpathyIndividualManager *
@@ -249,12 +331,10 @@ GList *
 empathy_individual_manager_get_members (EmpathyIndividualManager *self)
 {
   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
-  GHashTable *individuals;
 
   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
 
-  individuals = folks_individual_aggregator_get_individuals (priv->aggregator);
-  return individuals ? g_hash_table_get_values (individuals) : NULL;
+  return g_hash_table_get_values (priv->individuals);
 }
 
 FolksIndividual *
@@ -262,15 +342,10 @@ empathy_individual_manager_lookup_member (EmpathyIndividualManager *self,
     const gchar *id)
 {
   EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
-  GHashTable *individuals;
 
   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
 
-  individuals = folks_individual_aggregator_get_individuals (priv->aggregator);
-  if (individuals != NULL)
-    return g_hash_table_lookup (individuals, id);
-
-  return NULL;
+  return g_hash_table_lookup (priv->individuals, id);
 }
 
 static void
@@ -291,7 +366,15 @@ aggregator_add_persona_from_details_cb (GObject *source,
       g_clear_error (&error);
     }
 
-  /* We can unref the contact now */
+  /* The persona can be NULL even if there wasn't an error, if the persona was
+   * already in the contact list */
+  if (persona != NULL)
+    {
+      /* Set the contact's persona */
+      empathy_contact_set_persona (contact, persona);
+      g_object_unref (persona);
+    }
+
   g_object_unref (contact);
 }
 
@@ -314,14 +397,14 @@ empathy_individual_manager_add_from_contact (EmpathyIndividualManager *self,
   g_object_ref (contact);
 
   DEBUG ("adding individual from contact %s (%s)",
-      empathy_contact_get_id (contact), empathy_contact_get_name (contact));
+      empathy_contact_get_id (contact), empathy_contact_get_alias (contact));
 
   account = empathy_contact_get_account (contact);
   store_id = tp_proxy_get_object_path (TP_PROXY (account));
 
-  details = g_hash_table_new (g_str_hash, g_str_equal);
-  g_hash_table_insert (details, "contact",
-      (gchar*) empathy_contact_get_id (contact));
+  details = tp_asv_new (
+      "contact", G_TYPE_STRING, empathy_contact_get_id (contact),
+      NULL);
 
   folks_individual_aggregator_add_persona_from_details (
       priv->aggregator, NULL, "telepathy", store_id, details,
@@ -330,6 +413,23 @@ empathy_individual_manager_add_from_contact (EmpathyIndividualManager *self,
   g_hash_table_destroy (details);
 }
 
+static void
+aggregator_remove_individual_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
+  GError *error = NULL;
+
+  folks_individual_aggregator_remove_individual_finish (
+      aggregator, result, &error);
+  if (error != NULL)
+    {
+      g_warning ("failed to remove individual: %s", error->message);
+      g_clear_error (&error);
+    }
+}
+
 /**
  * Removes the inner contact from the server (and thus the Individual). Not
  * meant for de-shelling inner personas from an Individual.
@@ -348,16 +448,35 @@ empathy_individual_manager_remove (EmpathyIndividualManager *self,
 
   DEBUG ("removing individual %s (%s)",
       folks_individual_get_id (individual),
-      folks_individual_get_alias (individual));
+      folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
 
-  folks_individual_aggregator_remove_individual (priv->aggregator, individual);
+  folks_individual_aggregator_remove_individual (priv->aggregator, individual,
+      aggregator_remove_individual_cb, self);
 }
 
 static void
-remove_group_cb (const gchar *id, FolksIndividual *individual,
+groups_change_group_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  FolksGroupable *groupable = FOLKS_GROUPABLE (source);
+  GError *error = NULL;
+
+  folks_groupable_change_group_finish (groupable, result, &error);
+  if (error != NULL)
+    {
+      g_warning ("failed to change group: %s", error->message);
+      g_clear_error (&error);
+    }
+}
+
+static void
+remove_group_cb (const gchar *id,
+    FolksIndividual *individual,
     const gchar *group)
 {
-  folks_groups_change_group (FOLKS_GROUPS (individual), group, FALSE);
+  folks_groupable_change_group (FOLKS_GROUPABLE (individual), group, FALSE,
+      groups_change_group_cb, NULL);
 }
 
 void
@@ -365,7 +484,6 @@ empathy_individual_manager_remove_group (EmpathyIndividualManager *manager,
     const gchar *group)
 {
   EmpathyIndividualManagerPriv *priv;
-  GHashTable *individuals;
 
   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager));
   g_return_if_fail (group != NULL);
@@ -375,37 +493,74 @@ empathy_individual_manager_remove_group (EmpathyIndividualManager *manager,
   DEBUG ("removing group %s", group);
 
   /* Remove every individual from the group */
-  individuals = folks_individual_aggregator_get_individuals (priv->aggregator);
-  g_hash_table_foreach (individuals, (GHFunc) remove_group_cb,
+  g_hash_table_foreach (priv->individuals, (GHFunc) remove_group_cb,
       (gpointer) group);
 }
 
-EmpathyIndividualManagerFlags
-empathy_individual_manager_get_flags_for_connection (
-    EmpathyIndividualManager *self,
-    TpConnection *connection)
+static void
+link_personas_cb (FolksIndividualAggregator *aggregator,
+    GAsyncResult *async_result,
+    gpointer user_data)
+{
+  GError *error = NULL;
+
+  folks_individual_aggregator_link_personas_finish (aggregator, async_result,
+      &error);
+
+  if (error != NULL)
+    {
+      g_warning ("Failed to link personas: %s", error->message);
+      g_clear_error (&error);
+    }
+}
+
+void
+empathy_individual_manager_link_personas (EmpathyIndividualManager *self,
+    GList *personas)
 {
   EmpathyIndividualManagerPriv *priv;
-  EmpathyContactListFlags list_flags;
-  EmpathyIndividualManagerFlags flags;
 
-  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self),
-      EMPATHY_INDIVIDUAL_MANAGER_NO_FLAGS);
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
+  g_return_if_fail (personas != NULL);
 
   priv = GET_PRIV (self);
 
-  list_flags = empathy_contact_manager_get_flags_for_connection (
-    priv->contact_manager, connection);
+  DEBUG ("Linking %u personas", g_list_length (personas));
+
+  folks_individual_aggregator_link_personas (priv->aggregator, personas,
+      (GAsyncReadyCallback) link_personas_cb, NULL);
+}
+
+static void
+unlink_individual_cb (FolksIndividualAggregator *aggregator,
+    GAsyncResult *async_result,
+    gpointer user_data)
+{
+  GError *error = NULL;
+
+  folks_individual_aggregator_unlink_individual_finish (aggregator,
+      async_result, &error);
+
+  if (error != NULL)
+    {
+      g_warning ("Failed to unlink individual: %s", error->message);
+      g_clear_error (&error);
+    }
+}
+
+void
+empathy_individual_manager_unlink_individual (EmpathyIndividualManager *self,
+    FolksIndividual *individual)
+{
+  EmpathyIndividualManagerPriv *priv;
+
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
+  g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
+
+  priv = GET_PRIV (self);
 
-  flags = EMPATHY_INDIVIDUAL_MANAGER_NO_FLAGS;
-  if (list_flags & EMPATHY_CONTACT_LIST_CAN_ADD)
-    flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_ADD;
-  if (list_flags & EMPATHY_CONTACT_LIST_CAN_REMOVE)
-    flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE;
-  if (list_flags & EMPATHY_CONTACT_LIST_CAN_ALIAS)
-    flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_ALIAS;
-  if (list_flags & EMPATHY_CONTACT_LIST_CAN_GROUP)
-    flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_GROUP;
+  DEBUG ("Unlinking individual '%s'", folks_individual_get_id (individual));
 
-  return flags;
+  folks_individual_aggregator_unlink_individual (priv->aggregator, individual,
+      (GAsyncReadyCallback) unlink_individual_cb, NULL);
 }