]> git.0d.be Git - empathy.git/blobdiff - libempathy-gtk/empathy-individual-store.c
individual_view_drag_end: remove the auto scroll
[empathy.git] / libempathy-gtk / empathy-individual-store.c
index 623e35626e74ca042c2a4cec5825228a64b5d917..c22e1114939cf0976ed8094bb72cca3c8fabe8d0 100644 (file)
@@ -38,7 +38,6 @@
 
 #include <libempathy/empathy-utils.h>
 #include <libempathy/empathy-enum-types.h>
-#include <libempathy/empathy-individual-manager.h>
 
 #include "empathy-individual-store.h"
 #include "empathy-ui-utils.h"
 /* Time in seconds after connecting which we wait before active users are enabled */
 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5
 
-#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualStore)
-typedef struct
+struct _EmpathyIndividualStorePriv
 {
-  EmpathyIndividualManager *manager;
-  gboolean show_offline;
   gboolean show_avatars;
   gboolean show_groups;
   gboolean is_compact;
   gboolean show_protocols;
-  gboolean show_active;
   EmpathyIndividualStoreSort sort_criterium;
   guint inhibit_active;
-  guint setup_idle_id;
   gboolean dispose_has_run;
   GHashTable *status_icons;
-} EmpathyIndividualStorePriv;
-
-typedef struct
-{
-  GtkTreeIter iter;
-  const gchar *name;
-  gboolean found;
-} FindGroup;
-
-typedef struct
-{
-  FolksIndividual *individual;
-  gboolean found;
-  GList *iters;
-} FindContact;
+  /* List of owned GCancellables for each pending avatar load operation */
+  GList *avatar_cancellables;
+  /* Hash: FolksIndividual* -> GQueue (GtkTreeIter *) */
+  GHashTable                  *folks_individual_cache;
+  /* Hash: char *groupname -> GtkTreeIter * */
+  GHashTable                  *empathy_group_cache;
+  gboolean show_active;
+};
 
 typedef struct
 {
   EmpathyIndividualStore *self;
   FolksIndividual *individual;
   gboolean remove;
+  guint timeout;
 } ShowActiveData;
 
 enum
 {
   PROP_0,
-  PROP_INDIVIDUAL_MANAGER,
-  PROP_SHOW_OFFLINE,
   PROP_SHOW_AVATARS,
   PROP_SHOW_PROTOCOLS,
   PROP_SHOW_GROUPS,
@@ -114,67 +100,87 @@ static void individual_store_contact_update (EmpathyIndividualStore *self,
 G_DEFINE_TYPE (EmpathyIndividualStore, empathy_individual_store,
     GTK_TYPE_TREE_STORE);
 
+static const gchar * const *
+individual_get_client_types (FolksIndividual *individual)
+{
+  GeeSet *personas;
+  GeeIterator *iter;
+  const gchar * const *types = NULL;
+  FolksPresenceType presence_type = FOLKS_PRESENCE_TYPE_UNSET;
+
+  personas = folks_individual_get_personas (individual);
+  iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+  while (gee_iterator_next (iter))
+    {
+      FolksPresenceDetails *presence;
+      FolksPersona *persona = gee_iterator_get (iter);
+
+      /* We only want personas which have presence and a TpContact */
+      if (!empathy_folks_persona_is_interesting (persona))
+        goto while_finish;
+
+      presence = FOLKS_PRESENCE_DETAILS (persona);
+
+      if (folks_presence_details_typecmp (
+              folks_presence_details_get_presence_type (presence),
+              presence_type) > 0)
+        {
+          TpContact *tp_contact;
+
+          presence_type = folks_presence_details_get_presence_type (presence);
+
+          tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+          if (tp_contact != NULL)
+            types = tp_contact_get_client_types (tp_contact);
+        }
+
+while_finish:
+      g_clear_object (&persona);
+    }
+  g_clear_object (&iter);
+
+  return types;
+}
+
 static void
-add_individual_to_store (GtkTreeStore *self,
+add_individual_to_store (GtkTreeStore *store,
     GtkTreeIter *iter,
     GtkTreeIter *parent,
-    FolksIndividual *individual,
-    EmpathyIndividualManagerFlags flags)
+    FolksIndividual *individual)
 {
-  EmpathyContact *contact;
+  EmpathyIndividualStore *self = EMPATHY_INDIVIDUAL_STORE (store);
+  gboolean can_audio_call, can_video_call;
+  const gchar * const *types;
+  GQueue *queue;
+
+  empathy_individual_can_audio_video_call (individual, &can_audio_call,
+      &can_video_call, NULL);
 
-  contact = empathy_contact_dup_from_folks_individual (individual);
+  types = individual_get_client_types (individual);
 
-  gtk_tree_store_insert_with_values (self, iter, parent, 0,
+  gtk_tree_store_insert_with_values (store, iter, parent, 0,
       EMPATHY_INDIVIDUAL_STORE_COL_NAME,
-      folks_individual_get_alias (individual),
+      folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, individual,
       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, FALSE,
       EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
-      EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, flags,
+      EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, can_audio_call,
+      EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, can_video_call,
+      EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES, types,
       -1);
 
-  if (contact != NULL)
+  queue = g_hash_table_lookup (self->priv->folks_individual_cache, individual);
+  if (queue)
     {
-      gtk_tree_store_set (GTK_TREE_STORE (self), iter,
-          EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL,
-            empathy_contact_get_capabilities (contact) &
-              EMPATHY_CAPABILITIES_AUDIO,
-          EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL,
-            empathy_contact_get_capabilities (contact) &
-              EMPATHY_CAPABILITIES_VIDEO,
-          -1);
+      g_queue_push_tail (queue, gtk_tree_iter_copy (iter));
     }
-
-  tp_clear_object (&contact);
-}
-
-static gboolean
-individual_store_get_group_foreach (GtkTreeModel *model,
-    GtkTreePath *path,
-    GtkTreeIter *iter,
-    FindGroup *fg)
-{
-  gchar *str;
-  gboolean is_group;
-
-  /* Groups are only at the top level. */
-  if (gtk_tree_path_get_depth (path) != 1)
-    return FALSE;
-
-  gtk_tree_model_get (model, iter,
-      EMPATHY_INDIVIDUAL_STORE_COL_NAME, &str,
-      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
-
-  if (is_group && !tp_strdiff (str, fg->name))
+  else
     {
-      fg->found = TRUE;
-      fg->iter = *iter;
+      queue = g_queue_new ();
+      g_queue_push_tail (queue, gtk_tree_iter_copy (iter));
+      g_hash_table_insert (self->priv->folks_individual_cache, individual,
+          queue);
     }
-
-  g_free (str);
-
-  return fg->found;
 }
 
 static void
@@ -185,23 +191,15 @@ individual_store_get_group (EmpathyIndividualStore *self,
     gboolean *created,
     gboolean is_fake_group)
 {
-  EmpathyIndividualStorePriv *priv;
   GtkTreeModel *model;
   GtkTreeIter iter_group;
   GtkTreeIter iter_separator;
-  FindGroup fg;
-
-  priv = GET_PRIV (self);
-
-  memset (&fg, 0, sizeof (fg));
-
-  fg.name = name;
+  GtkTreeIter *iter;
 
   model = GTK_TREE_MODEL (self);
-  gtk_tree_model_foreach (model,
-      (GtkTreeModelForeachFunc) individual_store_get_group_foreach, &fg);
+  iter = g_hash_table_lookup (self->priv->empathy_group_cache, name);
 
-  if (!fg.found)
+  if (iter == NULL)
     {
       if (created)
         *created = TRUE;
@@ -216,6 +214,9 @@ individual_store_get_group (EmpathyIndividualStore *self,
           EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake_group,
           -1);
 
+      g_hash_table_insert (self->priv->empathy_group_cache, g_strdup (name),
+          gtk_tree_iter_copy (&iter_group));
+
       if (iter_group_to_set)
         *iter_group_to_set = iter_group;
 
@@ -233,9 +234,9 @@ individual_store_get_group (EmpathyIndividualStore *self,
         *created = FALSE;
 
       if (iter_group_to_set)
-        *iter_group_to_set = fg.iter;
+        *iter_group_to_set = *iter;
 
-      iter_separator = fg.iter;
+      iter_separator = *iter;
 
       if (gtk_tree_model_iter_next (model, &iter_separator))
         {
@@ -250,184 +251,180 @@ individual_store_get_group (EmpathyIndividualStore *self,
     }
 }
 
-static gboolean
-individual_store_find_contact_foreach (GtkTreeModel *model,
-    GtkTreePath *path,
-    GtkTreeIter *iter,
-    FindContact *fc)
-{
-  FolksIndividual *individual;
-
-  gtk_tree_model_get (model, iter,
-      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
-
-  if (individual == fc->individual)
-    {
-      fc->found = TRUE;
-      fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
-    }
-
-  tp_clear_object (&individual);
-
-  return FALSE;
-}
-
 static GList *
 individual_store_find_contact (EmpathyIndividualStore *self,
     FolksIndividual *individual)
 {
-  EmpathyIndividualStorePriv *priv;
-  GtkTreeModel *model;
-  GList *l = NULL;
-  FindContact fc;
-
-  priv = GET_PRIV (self);
-
-  memset (&fc, 0, sizeof (fc));
+  GQueue *row_refs_queue;
+  GList *i;
+  GList *iters_list = NULL;
 
-  fc.individual = individual;
+  row_refs_queue = g_hash_table_lookup (self->priv->folks_individual_cache,
+      individual);
+  if (!row_refs_queue)
+    return NULL;
 
-  model = GTK_TREE_MODEL (self);
-  gtk_tree_model_foreach (model,
-      (GtkTreeModelForeachFunc) individual_store_find_contact_foreach, &fc);
+  for (i = g_queue_peek_head_link (row_refs_queue) ; i != NULL ; i = i->next)
+    {
+      GtkTreeIter *iter = i->data;
 
-  if (fc.found)
-    l = fc.iters;
+      iters_list = g_list_prepend (iters_list, gtk_tree_iter_copy (iter));
+    }
 
-  return l;
+  return iters_list;
 }
 
 static void
-individual_store_remove_individual (EmpathyIndividualStore *self,
+free_iters (GList *iters)
+{
+  g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+  g_list_free (iters);
+}
+
+void
+empathy_individual_store_remove_individual (EmpathyIndividualStore *self,
     FolksIndividual *individual)
 {
-  EmpathyIndividualStorePriv *priv;
   GtkTreeModel *model;
-  GList *iters, *l;
-
-  priv = GET_PRIV (self);
+  GQueue *row_refs;
+  GList *l;
 
-  iters = individual_store_find_contact (self, individual);
-  if (iters == NULL)
+  row_refs = g_hash_table_lookup (self->priv->folks_individual_cache,
+      individual);
+  if (!row_refs)
     return;
 
   /* Clean up model */
   model = GTK_TREE_MODEL (self);
 
-  for (l = iters; l; l = l->next)
+  for (l = g_queue_peek_head_link (row_refs); l; l = l->next)
     {
+      GtkTreeIter *iter = l->data;
       GtkTreeIter parent;
 
       /* NOTE: it is only <= 2 here because we have
        * separators after the group name, otherwise it
        * should be 1.
        */
-      if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
+      if (gtk_tree_model_iter_parent (model, &parent, iter) &&
           gtk_tree_model_iter_n_children (model, &parent) <= 2)
         {
+          gchar *group_name;
+          gtk_tree_model_get (model, &parent,
+              EMPATHY_INDIVIDUAL_STORE_COL_NAME, &group_name,
+              -1);
+          g_hash_table_remove (self->priv->empathy_group_cache,
+              group_name);
           gtk_tree_store_remove (GTK_TREE_STORE (self), &parent);
         }
       else
         {
-          gtk_tree_store_remove (GTK_TREE_STORE (self), l->data);
+          gtk_tree_store_remove (GTK_TREE_STORE (self), iter);
         }
     }
 
-  g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
-  g_list_free (iters);
+  g_hash_table_remove (self->priv->folks_individual_cache, individual);
 }
 
-static void
-individual_store_add_individual (EmpathyIndividualStore *self,
+void
+empathy_individual_store_add_individual (EmpathyIndividualStore *self,
     FolksIndividual *individual)
 {
-  EmpathyIndividualStorePriv *priv;
-  GtkTreeIter iter;
-  GHashTable *group_set = NULL;
-  GList *groups = NULL, *l;
-  EmpathyContact *contact;
-  TpConnection *connection;
-  EmpathyIndividualManagerFlags flags = 0;
-  gchar *protocol_name;
-
-  priv = GET_PRIV (self);
-
-  if (EMP_STR_EMPTY (folks_individual_get_alias (individual)) ||
-      (!priv->show_offline && !folks_individual_is_online (individual)))
+  GtkTreeIter iter, iter_group;
+  GeeSet *group_set = NULL;
+  gboolean grouped = FALSE;
+
+  if (EMP_STR_EMPTY (folks_alias_details_get_alias (
+          FOLKS_ALIAS_DETAILS (individual))))
     return;
 
-  if (priv->show_groups)
+  if (!self->priv->show_groups)
     {
-      group_set = folks_individual_get_groups (individual);
-      groups = g_hash_table_get_keys (group_set);
+      /* add our individual to the toplevel of the store */
+      add_individual_to_store (GTK_TREE_STORE (self), &iter, NULL,
+          individual);
+
+      goto finally;
     }
 
-  contact = empathy_contact_dup_from_folks_individual (individual);
-  connection = empathy_contact_get_connection (contact);
-  flags = empathy_individual_manager_get_flags_for_connection (priv->manager,
-      connection);
+  group_set = folks_group_details_get_groups (
+      FOLKS_GROUP_DETAILS (individual));
 
-  tp_connection_parse_object_path (connection, &protocol_name, NULL);
+  if (gee_collection_get_size (GEE_COLLECTION (group_set)) > 0)
+    {
+      /* add the contact to its groups */
+      GeeIterator *group_iter =
+        gee_iterable_iterator (GEE_ITERABLE (group_set));
+
+      while (group_iter != NULL && gee_iterator_next (group_iter))
+        {
+          gchar *group_name = gee_iterator_get (group_iter);
+
+          individual_store_get_group (self, group_name, &iter_group,
+              NULL, NULL, FALSE);
+
+          add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
+              individual);
+          grouped = TRUE;
+
+          g_free (group_name);
+        }
 
-  if (groups == NULL)
+      g_clear_object (&group_iter);
+    }
+  else
     {
-      GtkTreeIter iter_group, *parent;
+      /* fall-back groups, in case there are no named groups */
+      EmpathyContact *contact;
+      TpConnection *connection;
+      const gchar *protocol_name = NULL;
 
-      parent = &iter_group;
+      contact = empathy_contact_dup_from_folks_individual (individual);
+      if (contact != NULL)
+        {
+          connection = empathy_contact_get_connection (contact);
+          protocol_name = tp_connection_get_protocol_name (connection);
+        }
 
-      if (!priv->show_groups)
-        parent = NULL;
-      else if (!tp_strdiff (protocol_name, "local-xmpp"))
+      if (!tp_strdiff (protocol_name, "local-xmpp"))
         {
           /* these are People Nearby */
           individual_store_get_group (self,
               EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY, &iter_group, NULL, NULL,
               TRUE);
-        }
-      else
-        {
-          individual_store_get_group (self,
-              EMPATHY_INDIVIDUAL_STORE_UNGROUPED,
-              &iter_group, NULL, NULL, TRUE);
+          add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
+              individual);
+          grouped = TRUE;
         }
 
-      add_individual_to_store (GTK_TREE_STORE (self), &iter, parent,
-          individual, flags);
+      g_clear_object (&contact);
     }
 
-  g_free (protocol_name);
-
-  /* Else add to each group. */
-  for (l = groups; l; l = l->next)
+  if (folks_favourite_details_get_is_favourite (
+        FOLKS_FAVOURITE_DETAILS (individual)))
     {
-      GtkTreeIter iter_group;
-
-      individual_store_get_group (self, l->data, &iter_group, NULL, NULL,
-          FALSE);
+      /* Add contact to the fake 'Favorites' group */
+      individual_store_get_group (self, EMPATHY_INDIVIDUAL_STORE_FAVORITE,
+          &iter_group, NULL, NULL, TRUE);
 
       add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
-          individual, flags);
+          individual);
+      grouped = TRUE;
     }
-  g_list_free (groups);
-  if (group_set != NULL)
-    g_hash_table_unref (group_set);
 
-  if (priv->show_groups &&
-      folks_favourite_get_is_favourite (FOLKS_FAVOURITE (individual)))
+  if (!grouped)
     {
-      /* Add contact to the fake 'Favorites' group */
-      GtkTreeIter iter_group;
-
-      individual_store_get_group (self, EMPATHY_INDIVIDUAL_STORE_FAVORITE,
+      /* Else add the contact to 'Ungrouped' */
+      individual_store_get_group (self,
+          EMPATHY_INDIVIDUAL_STORE_UNGROUPED,
           &iter_group, NULL, NULL, TRUE);
-
       add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
-          individual, flags);
+          individual);
     }
 
-  individual_store_contact_update (self, individual);
 
-  tp_clear_object (&contact);
+finally:
+  individual_store_contact_update (self, individual);
 }
 
 static void
@@ -436,11 +433,9 @@ individual_store_contact_set_active (EmpathyIndividualStore *self,
     gboolean active,
     gboolean set_changed)
 {
-  EmpathyIndividualStorePriv *priv;
   GtkTreeModel *model;
   GList *iters, *l;
 
-  priv = GET_PRIV (self);
   model = GTK_TREE_MODEL (self);
 
   iters = individual_store_find_contact (self, individual);
@@ -462,9 +457,27 @@ individual_store_contact_set_active (EmpathyIndividualStore *self,
         }
     }
 
-  g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
-  g_list_free (iters);
+  free_iters (iters);
+}
+
+static void individual_store_contact_active_free (ShowActiveData *data);
 
+static void
+individual_store_contact_active_invalidated (ShowActiveData *data,
+    GObject *old_object)
+{
+  /* Remove the timeout and free the struct, since the individual or individual
+   * store has disappeared. */
+  g_source_remove (data->timeout);
+
+  if (old_object == (GObject *) data->self)
+    data->self = NULL;
+  else if (old_object == (GObject *) data->individual)
+    data->individual = NULL;
+  else
+    g_assert_not_reached ();
+
+  individual_store_contact_active_free (data);
 }
 
 static ShowActiveData *
@@ -475,20 +488,23 @@ individual_store_contact_active_new (EmpathyIndividualStore *self,
   ShowActiveData *data;
 
   DEBUG ("Individual'%s' now active, and %s be removed",
-      folks_individual_get_alias (individual), remove_ ? "WILL" : "WILL NOT");
+      folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
+      remove_ ? "WILL" : "WILL NOT");
 
   data = g_slice_new0 (ShowActiveData);
 
   /* We don't actually want to force either the IndividualStore or the
    * Individual to stay alive, since the user could quit Empathy or disable
    * the account before the contact_active timeout is fired. */
-  g_object_add_weak_pointer (G_OBJECT (self), (gpointer) &(data->self));
-  g_object_add_weak_pointer (G_OBJECT (individual),
-      (gpointer) &(data->individual));
+  g_object_weak_ref (G_OBJECT (self),
+      (GWeakNotify) individual_store_contact_active_invalidated, data);
+  g_object_weak_ref (G_OBJECT (individual),
+      (GWeakNotify) individual_store_contact_active_invalidated, data);
 
   data->self = self;
   data->individual = individual;
   data->remove = remove_;
+  data->timeout = 0;
 
   return data;
 }
@@ -496,35 +512,34 @@ individual_store_contact_active_new (EmpathyIndividualStore *self,
 static void
 individual_store_contact_active_free (ShowActiveData *data)
 {
+  if (data->self != NULL)
+    {
+      g_object_weak_unref (G_OBJECT (data->self),
+          (GWeakNotify) individual_store_contact_active_invalidated, data);
+    }
+
+  if (data->individual != NULL)
+    {
+      g_object_weak_unref (G_OBJECT (data->individual),
+          (GWeakNotify) individual_store_contact_active_invalidated, data);
+    }
+
   g_slice_free (ShowActiveData, data);
 }
 
 static gboolean
 individual_store_contact_active_cb (ShowActiveData *data)
 {
-  EmpathyIndividualStorePriv *priv;
-
-  /* They're weak pointers, so may have been NULLified between ShowActiveData
-   * being created and the timeout being fired. We assume they can only be
-   * destroyed in this thread, so this isn't MT-safe. */
-  if (data->self == NULL || data->individual == NULL)
-    {
-      individual_store_contact_active_free (data);
-      return FALSE;
-    }
-
-  priv = GET_PRIV (data->self);
-
-  if (data->remove &&
-      !priv->show_offline && !folks_individual_is_online (data->individual))
+  if (data->remove)
     {
       DEBUG ("Individual'%s' active timeout, removing item",
-          folks_individual_get_alias (data->individual));
-      individual_store_remove_individual (data->self, data->individual);
+          folks_alias_details_get_alias (
+            FOLKS_ALIAS_DETAILS (data->individual)));
+      empathy_individual_store_remove_individual (data->self, data->individual);
     }
 
   DEBUG ("Individual'%s' no longer active",
-      folks_individual_get_alias (data->individual));
+      folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (data->individual)));
 
   individual_store_contact_set_active (data->self,
       data->individual, FALSE, TRUE);
@@ -534,13 +549,16 @@ individual_store_contact_active_cb (ShowActiveData *data)
   return FALSE;
 }
 
+typedef struct {
+  EmpathyIndividualStore *store; /* weak */
+  GCancellable *cancellable; /* owned */
+} LoadAvatarData;
+
 static void
-individual_avatar_pixbuf_received_cb (GObject *object,
+individual_avatar_pixbuf_received_cb (FolksIndividual *individual,
     GAsyncResult *result,
-    gpointer user_data)
+    LoadAvatarData *data)
 {
-  FolksIndividual *individual = FOLKS_INDIVIDUAL (object);
-  EmpathyIndividualStore *self = user_data;
   GError *error = NULL;
   GdkPixbuf *pixbuf;
 
@@ -550,35 +568,47 @@ individual_avatar_pixbuf_received_cb (GObject *object,
   if (error != NULL)
     {
       DEBUG ("failed to retrieve pixbuf for individual %s: %s",
-          folks_individual_get_alias (individual),
+          folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
           error->message);
       g_clear_error (&error);
     }
-  else
+  else if (data->store != NULL)
     {
       GList *iters, *l;
 
-      iters = individual_store_find_contact (self, individual);
+      iters = individual_store_find_contact (data->store, individual);
       for (l = iters; l; l = l->next)
         {
-          gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
+          gtk_tree_store_set (GTK_TREE_STORE (data->store), l->data,
               EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, pixbuf,
               -1);
         }
+
+      free_iters (iters);
+    }
+
+  /* Free things */
+  if (data->store != NULL)
+    {
+      g_object_remove_weak_pointer (G_OBJECT (data->store),
+          (gpointer *) &data->store);
+      data->store->priv->avatar_cancellables = g_list_remove (
+          data->store->priv->avatar_cancellables, data->cancellable);
     }
+
+  tp_clear_object (&pixbuf);
+  g_object_unref (data->cancellable);
+  g_slice_free (LoadAvatarData, data);
 }
 
 static void
 individual_store_contact_update (EmpathyIndividualStore *self,
     FolksIndividual *individual)
 {
-  EmpathyIndividualStorePriv *priv;
   ShowActiveData *data;
   GtkTreeModel *model;
-  EmpathyContact *contact;
   GList *iters, *l;
   gboolean in_list;
-  gboolean should_be_in_list;
   gboolean was_online = TRUE;
   gboolean now_online = FALSE;
   gboolean set_model = FALSE;
@@ -587,11 +617,9 @@ individual_store_contact_update (EmpathyIndividualStore *self,
   gboolean do_set_refresh = FALSE;
   gboolean show_avatar = FALSE;
   GdkPixbuf *pixbuf_status;
-
-  priv = GET_PRIV (self);
+  LoadAvatarData *load_avatar_data;
 
   model = GTK_TREE_MODEL (self);
-  contact = empathy_contact_dup_from_folks_individual (individual);
 
   iters = individual_store_find_contact (self, individual);
   if (!iters)
@@ -604,55 +632,17 @@ individual_store_contact_update (EmpathyIndividualStore *self,
     }
 
   /* Get online state now. */
-  now_online = folks_individual_is_online (individual);
-
-  if (priv->show_offline || now_online)
-    {
-      should_be_in_list = TRUE;
-    }
-  else
-    {
-      should_be_in_list = FALSE;
-    }
-
-  if (!in_list && !should_be_in_list)
-    {
-      /* Nothing to do. */
-      DEBUG ("Individual:'%s' in list:NO, should be:NO",
-          folks_individual_get_alias (individual));
-
-      g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
-      g_list_free (iters);
-      return;
-    }
-  else if (in_list && !should_be_in_list)
-    {
-      DEBUG ("Individual:'%s' in list:YES, should be:NO",
-          folks_individual_get_alias (individual));
-
-      if (priv->show_active)
-        {
-          do_remove = TRUE;
-          do_set_active = TRUE;
-          do_set_refresh = TRUE;
+  now_online = folks_presence_details_is_online (
+      FOLKS_PRESENCE_DETAILS (individual));
 
-          set_model = TRUE;
-          DEBUG ("Remove item (after timeout)");
-        }
-      else
-        {
-          DEBUG ("Remove item (now)!");
-          individual_store_remove_individual (self, individual);
-        }
-    }
-  else if (!in_list && should_be_in_list)
+  if (!in_list)
     {
       DEBUG ("Individual'%s' in list:NO, should be:YES",
-          folks_individual_get_alias (individual));
+          folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
 
-      individual_store_add_individual (self, individual);
+      empathy_individual_store_add_individual (self, individual);
 
-      if (priv->show_active)
+      if (self->priv->show_active)
         {
           do_set_active = TRUE;
 
@@ -662,7 +652,7 @@ individual_store_contact_update (EmpathyIndividualStore *self,
   else
     {
       DEBUG ("Individual'%s' in list:YES, should be:YES",
-          folks_individual_get_alias (individual));
+          folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
 
       /* Get online state before. */
       if (iters && g_list_length (iters) > 0)
@@ -672,7 +662,7 @@ individual_store_contact_update (EmpathyIndividualStore *self,
         }
 
       /* Is this really an update or an online/offline. */
-      if (priv->show_active)
+      if (self->priv->show_active)
         {
           if (was_online != now_online)
             {
@@ -695,48 +685,61 @@ individual_store_contact_update (EmpathyIndividualStore *self,
       set_model = TRUE;
     }
 
-  if (priv->show_avatars && !priv->is_compact)
+  if (self->priv->show_avatars && !self->priv->is_compact)
     {
       show_avatar = TRUE;
     }
 
-  empathy_pixbuf_avatar_from_individual_scaled_async (individual,
-      individual_avatar_pixbuf_received_cb, 32, 32, self);
+  /* Load the avatar asynchronously */
+  load_avatar_data = g_slice_new (LoadAvatarData);
+  load_avatar_data->store = self;
+  g_object_add_weak_pointer (G_OBJECT (self),
+      (gpointer *) &load_avatar_data->store);
+  load_avatar_data->cancellable = g_cancellable_new ();
+
+  self->priv->avatar_cancellables = g_list_prepend (
+      self->priv->avatar_cancellables, load_avatar_data->cancellable);
+
+  empathy_pixbuf_avatar_from_individual_scaled_async (individual, 32, 32,
+      load_avatar_data->cancellable,
+      (GAsyncReadyCallback) individual_avatar_pixbuf_received_cb,
+      load_avatar_data);
 
   pixbuf_status =
       empathy_individual_store_get_individual_status_icon (self, individual);
 
   for (l = iters; l && set_model; l = l->next)
     {
+      gboolean can_audio_call, can_video_call;
+      const gchar * const *types;
+
+      empathy_individual_can_audio_video_call (individual, &can_audio_call,
+          &can_video_call, NULL);
+
+      types = individual_get_client_types (individual);
+
       gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
           EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf_status,
           EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
           EMPATHY_INDIVIDUAL_STORE_COL_NAME,
-            folks_individual_get_alias (individual),
+            folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
           EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE,
-            folks_individual_get_presence_type (individual),
+            folks_presence_details_get_presence_type (
+                FOLKS_PRESENCE_DETAILS (individual)),
           EMPATHY_INDIVIDUAL_STORE_COL_STATUS,
-            folks_individual_get_presence_message (individual),
-          EMPATHY_INDIVIDUAL_STORE_COL_COMPACT, priv->is_compact,
+            folks_presence_details_get_presence_message (
+                FOLKS_PRESENCE_DETAILS (individual)),
+          EMPATHY_INDIVIDUAL_STORE_COL_COMPACT, self->priv->is_compact,
           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, FALSE,
           EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, now_online,
           EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
+          EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, can_audio_call,
+          EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, can_video_call,
+          EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES, types,
           -1);
-
-      if (contact != NULL)
-        {
-          gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
-              EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL,
-                empathy_contact_get_capabilities (contact) &
-                  EMPATHY_CAPABILITIES_AUDIO,
-              EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL,
-                empathy_contact_get_capabilities (contact) &
-                  EMPATHY_CAPABILITIES_VIDEO,
-              -1);
-        }
     }
 
-  if (priv->show_active && do_set_active)
+  if (self->priv->show_active && do_set_active)
     {
       individual_store_contact_set_active (self, individual, do_set_active,
           do_set_refresh);
@@ -746,7 +749,7 @@ individual_store_contact_update (EmpathyIndividualStore *self,
           data =
               individual_store_contact_active_new (self, individual,
               do_remove);
-          g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME,
+          data->timeout = g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME,
               (GSourceFunc) individual_store_contact_active_cb, data);
         }
     }
@@ -756,231 +759,202 @@ individual_store_contact_update (EmpathyIndividualStore *self,
    * timeout removes the user from the contact list, really we
    * should remove the first timeout.
    */
-  g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
-  g_list_free (iters);
-  tp_clear_object (&contact);
+  free_iters (iters);
 }
 
 static void
-individual_store_contact_updated_cb (FolksIndividual *individual,
+individual_store_individual_updated_cb (FolksIndividual *individual,
     GParamSpec *param,
     EmpathyIndividualStore *self)
 {
   DEBUG ("Individual'%s' updated, checking roster is in sync...",
-      folks_individual_get_alias (individual));
+      folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
 
   individual_store_contact_update (self, individual);
 }
 
 static void
-individual_store_add_individual_and_connect (EmpathyIndividualStore *self,
-    FolksIndividual *individual)
+individual_store_contact_updated_cb (EmpathyContact *contact,
+    GParamSpec *pspec,
+    EmpathyIndividualStore *self)
 {
-  g_signal_connect (individual, "notify::avatar",
-      G_CALLBACK (individual_store_contact_updated_cb), self);
-  g_signal_connect (individual, "notify::presence-type",
-      G_CALLBACK (individual_store_contact_updated_cb), self);
-  g_signal_connect (individual, "notify::presence-message",
-      G_CALLBACK (individual_store_contact_updated_cb), self);
-  g_signal_connect (individual, "notify::alias",
-      G_CALLBACK (individual_store_contact_updated_cb), self);
-  g_signal_connect (individual, "notify::capabilities",
-      G_CALLBACK (individual_store_contact_updated_cb), self);
+  FolksIndividual *individual;
 
-  individual_store_add_individual (self, individual);
-}
+  DEBUG ("Contact '%s' updated, checking roster is in sync...",
+      empathy_contact_get_alias (contact));
 
-static void
-individual_store_remove_individual_and_disconnect (
-    EmpathyIndividualStore *self,
-    FolksIndividual *individual)
-{
-  g_signal_handlers_disconnect_by_func (individual,
-      G_CALLBACK (individual_store_contact_updated_cb), self);
+  individual = g_object_get_data (G_OBJECT (contact), "individual");
+  if (individual == NULL)
+    return;
 
-  individual_store_remove_individual (self, individual);
+  individual_store_contact_update (self, individual);
 }
 
 static void
-individual_store_members_changed_cb (EmpathyIndividualManager *manager,
-    gchar *message,
-    GList *added,
-    GList *removed,
-    guint reason,
+individual_personas_changed_cb (FolksIndividual *individual,
+    GeeSet *added,
+    GeeSet *removed,
     EmpathyIndividualStore *self)
 {
-  GList *l;
+  GeeIterator *iter;
 
-  for (l = added; l; l = l->next)
-    {
-      DEBUG ("Individual %s %s", folks_individual_get_id (l->data), "added");
+  DEBUG ("Individual '%s' personas-changed.",
+      folks_individual_get_id (individual));
 
-      individual_store_add_individual_and_connect (self, l->data);
-    }
-  for (l = removed; l; l = l->next)
+  iter = gee_iterable_iterator (GEE_ITERABLE (removed));
+  /* FIXME: libfolks hasn't grown capabilities support yet, so we have to go
+   * through the EmpathyContacts for them. */
+  while (gee_iterator_next (iter))
     {
-      DEBUG ("Individual %s %s",
-          folks_individual_get_id (l->data), "removed");
+      TpfPersona *persona = gee_iterator_get (iter);
+      TpContact *tp_contact;
+      EmpathyContact *contact;
+
+      if (TPF_IS_PERSONA (persona))
+        {
+          tp_contact = tpf_persona_get_contact (persona);
+          if (tp_contact != NULL)
+            {
+              contact = empathy_contact_dup_from_tp_contact (tp_contact);
+              empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
 
-      individual_store_remove_individual_and_disconnect (self, l->data);
+              g_object_set_data (G_OBJECT (contact), "individual", NULL);
+              g_signal_handlers_disconnect_by_func (contact,
+                  (GCallback) individual_store_contact_updated_cb, self);
+
+              g_object_unref (contact);
+            }
+        }
+
+      g_clear_object (&persona);
     }
-}
+  g_clear_object (&iter);
 
-static void
-individual_store_favourites_changed_cb (EmpathyIndividualManager *manager,
-    FolksIndividual *individual,
-    gboolean is_favourite,
-    EmpathyIndividualStore *self)
-{
-  EmpathyIndividualStorePriv *priv;
+  iter = gee_iterable_iterator (GEE_ITERABLE (added));
+  while (gee_iterator_next (iter))
+    {
+      TpfPersona *persona = gee_iterator_get (iter);
+      TpContact *tp_contact;
+      EmpathyContact *contact;
 
-  priv = GET_PRIV (self);
+      if (TPF_IS_PERSONA (persona))
+        {
+          tp_contact = tpf_persona_get_contact (persona);
+          if (tp_contact != NULL)
+            {
+              contact = empathy_contact_dup_from_tp_contact (tp_contact);
+              empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
 
-  DEBUG ("Individual %s is %s a favourite",
-      folks_individual_get_id (individual),
-      is_favourite ? "now" : "no longer");
+              g_object_set_data (G_OBJECT (contact), "individual", individual);
+              g_signal_connect (contact, "notify::capabilities",
+                  (GCallback) individual_store_contact_updated_cb, self);
+              g_signal_connect (contact, "notify::client-types",
+                  (GCallback) individual_store_contact_updated_cb, self);
+
+              g_object_unref (contact);
+            }
+        }
 
-  individual_store_remove_individual (self, individual);
-  individual_store_add_individual (self, individual);
+      g_clear_object (&persona);
+    }
+  g_clear_object (&iter);
 }
 
 static void
-individual_store_groups_changed_cb (EmpathyIndividualManager *manager,
-    FolksIndividual *individual,
-    gchar *group,
-    gboolean is_member,
+individual_store_favourites_changed_cb (FolksIndividual *individual,
+    GParamSpec *param,
     EmpathyIndividualStore *self)
 {
-  EmpathyIndividualStorePriv *priv;
-  gboolean show_active;
-
-  priv = GET_PRIV (self);
-
-  DEBUG ("Updating groups for individual %s",
-      folks_individual_get_id (individual));
+  DEBUG ("Individual %s is %s a favourite",
+      folks_individual_get_id (individual),
+      folks_favourite_details_get_is_favourite (
+        FOLKS_FAVOURITE_DETAILS (individual)) ? "now" : "no longer");
 
-  /* We do this to make sure the groups are correct, if not, we
-   * would have to check the groups already set up for each
-   * contact and then see what has been updated.
-   */
-  show_active = priv->show_active;
-  priv->show_active = FALSE;
-  individual_store_remove_individual (self, individual);
-  individual_store_add_individual (self, individual);
-  priv->show_active = show_active;
+  empathy_individual_store_remove_individual (self, individual);
+  empathy_individual_store_add_individual (self, individual);
 }
 
-static gboolean
-individual_store_manager_setup (gpointer user_data)
+void
+individual_store_add_individual_and_connect (EmpathyIndividualStore *self,
+    FolksIndividual *individual)
 {
-  EmpathyIndividualStore *self = user_data;
-  EmpathyIndividualStorePriv *priv = GET_PRIV (self);
-  GList *individuals;
-
-  /* Signal connection. */
-
-  /* TODO: implement */
-  DEBUG ("handling individual renames unimplemented");
-
-  g_signal_connect (priv->manager,
-      "members-changed",
-      G_CALLBACK (individual_store_members_changed_cb), self);
-
-  g_signal_connect (priv->manager,
-      "favourites-changed",
-      G_CALLBACK (individual_store_favourites_changed_cb), self);
+  GeeSet *empty_set = gee_set_empty (G_TYPE_NONE, NULL, NULL);
 
-  g_signal_connect (priv->manager,
-      "groups-changed",
-      G_CALLBACK (individual_store_groups_changed_cb), self);
+  empathy_individual_store_add_individual (self, individual);
 
-  /* Add contacts already created. */
-  individuals = empathy_individual_manager_get_members (priv->manager);
-  if (individuals != NULL && FOLKS_IS_INDIVIDUAL (individuals->data))
-    {
-      individual_store_members_changed_cb (priv->manager, "initial add",
-          individuals, NULL, 0, self);
-      g_list_free (individuals);
-    }
-
-  priv->setup_idle_id = 0;
-  return FALSE;
+  g_signal_connect (individual, "notify::avatar",
+      (GCallback) individual_store_individual_updated_cb, self);
+  g_signal_connect (individual, "notify::presence-type",
+      (GCallback) individual_store_individual_updated_cb, self);
+  g_signal_connect (individual, "notify::presence-message",
+      (GCallback) individual_store_individual_updated_cb, self);
+  g_signal_connect (individual, "notify::alias",
+      (GCallback) individual_store_individual_updated_cb, self);
+  g_signal_connect (individual, "personas-changed",
+      (GCallback) individual_personas_changed_cb, self);
+  g_signal_connect (individual, "notify::is-favourite",
+      (GCallback) individual_store_favourites_changed_cb, self);
+
+  /* provide an empty set so the callback can assume non-NULL sets */
+  individual_personas_changed_cb (individual,
+      folks_individual_get_personas (individual), empty_set, self);
+  g_clear_object (&empty_set);
 }
 
-static void
-individual_store_set_individual_manager (EmpathyIndividualStore *self,
-    EmpathyIndividualManager *manager)
+void
+empathy_individual_store_disconnect_individual (EmpathyIndividualStore *self,
+    FolksIndividual *individual)
 {
-  EmpathyIndividualStorePriv *priv = GET_PRIV (self);
+  GeeSet *empty_set = gee_set_empty (G_TYPE_NONE, NULL, NULL);
 
-  priv->manager = g_object_ref (manager);
+  /* provide an empty set so the callback can assume non-NULL sets */
+  individual_personas_changed_cb (individual, empty_set,
+      folks_individual_get_personas (individual), self);
+  g_clear_object (&empty_set);
 
-  /* Let a chance to have all properties set before populating */
-  priv->setup_idle_id = g_idle_add (individual_store_manager_setup, self);
+  g_signal_handlers_disconnect_by_func (individual,
+      (GCallback) individual_store_individual_updated_cb, self);
+  g_signal_handlers_disconnect_by_func (individual,
+      (GCallback) individual_personas_changed_cb, self);
+  g_signal_handlers_disconnect_by_func (individual,
+      (GCallback) individual_store_favourites_changed_cb, self);
 }
 
-static void
-individual_store_member_renamed_cb (EmpathyIndividualManager *manager,
-    FolksIndividual *old_individual,
-    FolksIndividual *new_individual,
-    guint reason,
-    gchar *message,
-    EmpathyIndividualStore *self)
+void
+individual_store_remove_individual_and_disconnect (
+    EmpathyIndividualStore *self,
+    FolksIndividual *individual)
 {
-  EmpathyIndividualStorePriv *priv;
-
-  priv = GET_PRIV (self);
-
-  DEBUG ("Individual %s renamed to %s",
-      folks_individual_get_id (old_individual),
-      folks_individual_get_id (new_individual));
-
-  /* add the new contact */
-  individual_store_add_individual_and_connect (self, new_individual);
-
-  /* remove old contact */
-  individual_store_remove_individual_and_disconnect (self, old_individual);
+  empathy_individual_store_disconnect_individual (self, individual);
+  empathy_individual_store_remove_individual (self, individual);
 }
 
 static void
 individual_store_dispose (GObject *object)
 {
-  EmpathyIndividualStorePriv *priv = GET_PRIV (object);
-  GList *contacts, *l;
+  EmpathyIndividualStore *self = EMPATHY_INDIVIDUAL_STORE (object);
+  GList *l;
 
-  if (priv->dispose_has_run)
+  if (self->priv->dispose_has_run)
     return;
-  priv->dispose_has_run = TRUE;
+  self->priv->dispose_has_run = TRUE;
 
-  contacts = empathy_individual_manager_get_members (priv->manager);
-  for (l = contacts; l; l = l->next)
-    {
-      g_signal_handlers_disconnect_by_func (l->data,
-          G_CALLBACK (individual_store_contact_updated_cb), object);
-    }
-  g_list_free (contacts);
-
-  g_signal_handlers_disconnect_by_func (priv->manager,
-      G_CALLBACK (individual_store_member_renamed_cb), object);
-  g_signal_handlers_disconnect_by_func (priv->manager,
-      G_CALLBACK (individual_store_members_changed_cb), object);
-  g_signal_handlers_disconnect_by_func (priv->manager,
-      G_CALLBACK (individual_store_favourites_changed_cb), object);
-  g_signal_handlers_disconnect_by_func (priv->manager,
-      G_CALLBACK (individual_store_groups_changed_cb), object);
-  g_object_unref (priv->manager);
-
-  if (priv->inhibit_active)
+  /* Cancel any pending avatar load operations */
+  for (l = self->priv->avatar_cancellables; l != NULL; l = l->next)
     {
-      g_source_remove (priv->inhibit_active);
+      /* The cancellables are freed in individual_avatar_pixbuf_received_cb() */
+      g_cancellable_cancel (G_CANCELLABLE (l->data));
     }
+  g_list_free (self->priv->avatar_cancellables);
 
-  if (priv->setup_idle_id != 0)
+  if (self->priv->inhibit_active)
     {
-      g_source_remove (priv->setup_idle_id);
+      g_source_remove (self->priv->inhibit_active);
     }
 
-  g_hash_table_destroy (priv->status_icons);
+  g_hash_table_unref (self->priv->status_icons);
+  g_hash_table_unref (self->priv->folks_individual_cache);
+  g_hash_table_unref (self->priv->empathy_group_cache);
   G_OBJECT_CLASS (empathy_individual_store_parent_class)->dispose (object);
 }
 
@@ -990,32 +964,24 @@ individual_store_get_property (GObject *object,
     GValue *value,
     GParamSpec *pspec)
 {
-  EmpathyIndividualStorePriv *priv;
-
-  priv = GET_PRIV (object);
+  EmpathyIndividualStore *self = EMPATHY_INDIVIDUAL_STORE (object);
 
   switch (param_id)
     {
-    case PROP_INDIVIDUAL_MANAGER:
-      g_value_set_object (value, priv->manager);
-      break;
-    case PROP_SHOW_OFFLINE:
-      g_value_set_boolean (value, priv->show_offline);
-      break;
     case PROP_SHOW_AVATARS:
-      g_value_set_boolean (value, priv->show_avatars);
+      g_value_set_boolean (value, self->priv->show_avatars);
       break;
     case PROP_SHOW_PROTOCOLS:
-      g_value_set_boolean (value, priv->show_protocols);
+      g_value_set_boolean (value, self->priv->show_protocols);
       break;
     case PROP_SHOW_GROUPS:
-      g_value_set_boolean (value, priv->show_groups);
+      g_value_set_boolean (value, self->priv->show_groups);
       break;
     case PROP_IS_COMPACT:
-      g_value_set_boolean (value, priv->is_compact);
+      g_value_set_boolean (value, self->priv->is_compact);
       break;
     case PROP_SORT_CRITERIUM:
-      g_value_set_enum (value, priv->sort_criterium);
+      g_value_set_enum (value, self->priv->sort_criterium);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
@@ -1029,20 +995,8 @@ individual_store_set_property (GObject *object,
     const GValue *value,
     GParamSpec *pspec)
 {
-  EmpathyIndividualStorePriv *priv;
-
-  priv = GET_PRIV (object);
-
   switch (param_id)
     {
-    case PROP_INDIVIDUAL_MANAGER:
-      individual_store_set_individual_manager (EMPATHY_INDIVIDUAL_STORE
-          (object), g_value_get_object (value));
-      break;
-    case PROP_SHOW_OFFLINE:
-      empathy_individual_store_set_show_offline (EMPATHY_INDIVIDUAL_STORE
-          (object), g_value_get_boolean (value));
-      break;
     case PROP_SHOW_AVATARS:
       empathy_individual_store_set_show_avatars (EMPATHY_INDIVIDUAL_STORE
           (object), g_value_get_boolean (value));
@@ -1078,19 +1032,6 @@ empathy_individual_store_class_init (EmpathyIndividualStoreClass *klass)
   object_class->get_property = individual_store_get_property;
   object_class->set_property = individual_store_set_property;
 
-  g_object_class_install_property (object_class,
-      PROP_INDIVIDUAL_MANAGER,
-      g_param_spec_object ("individual-manager",
-          "The individual manager",
-          "The individual manager",
-          EMPATHY_TYPE_INDIVIDUAL_MANAGER,
-          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
-  g_object_class_install_property (object_class,
-      PROP_SHOW_OFFLINE,
-      g_param_spec_boolean ("show-offline",
-          "Show Offline",
-          "Whether contact list should display "
-          "offline contacts", FALSE, G_PARAM_READWRITE));
   g_object_class_install_property (object_class,
       PROP_SHOW_AVATARS,
       g_param_spec_boolean ("show-avatars",
@@ -1237,30 +1178,37 @@ individual_store_contact_sort (FolksIndividual *individual_a,
   g_return_val_if_fail (individual_a != NULL || individual_b != NULL, 0);
 
   /* alias */
-  ret_val = g_utf8_collate (folks_individual_get_alias (individual_a),
-      folks_individual_get_alias (individual_b));
+  ret_val = g_utf8_collate (
+      folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual_a)),
+      folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual_b)));
 
   if (ret_val != 0)
     goto out;
 
   contact_a = empathy_contact_dup_from_folks_individual (individual_a);
   contact_b = empathy_contact_dup_from_folks_individual (individual_b);
-  account_a = empathy_contact_get_account (contact_a);
-  account_b = empathy_contact_get_account (contact_b);
+  if (contact_a != NULL && contact_b != NULL)
+    {
+      account_a = empathy_contact_get_account (contact_a);
+      account_b = empathy_contact_get_account (contact_b);
 
-  /* protocol */
-  ret_val = g_strcmp0 (tp_account_get_protocol (account_a),
-      tp_account_get_protocol (account_b));
+      g_assert (account_a != NULL);
+      g_assert (account_b != NULL);
 
-  if (ret_val != 0)
-    goto out;
+      /* protocol */
+      ret_val = g_strcmp0 (tp_account_get_protocol (account_a),
+          tp_account_get_protocol (account_b));
 
-  /* account ID */
-  ret_val = g_strcmp0 (tp_proxy_get_object_path (account_a),
-      tp_proxy_get_object_path (account_b));
+      if (ret_val != 0)
+        goto out;
 
-  if (ret_val != 0)
-    goto out;
+      /* account ID */
+      ret_val = g_strcmp0 (tp_proxy_get_object_path (account_a),
+          tp_proxy_get_object_path (account_b));
+
+      if (ret_val != 0)
+        goto out;
+    }
 
   /* identifier */
   ret_val = g_utf8_collate (folks_individual_get_id (individual_a),
@@ -1309,8 +1257,12 @@ individual_store_state_sort_func (GtkTreeModel *model,
   /* If we managed to get this far, we can start looking at
    * the presences.
    */
-  folks_presence_type_a = folks_individual_get_presence_type (individual_a);
-  folks_presence_type_b = folks_individual_get_presence_type (individual_b);
+  folks_presence_type_a =
+      folks_presence_details_get_presence_type (
+          FOLKS_PRESENCE_DETAILS (individual_a));
+  folks_presence_type_b =
+      folks_presence_details_get_presence_type (
+          FOLKS_PRESENCE_DETAILS (individual_b));
   tp_presence_a = empathy_folks_presence_type_to_tp (folks_presence_type_a);
   tp_presence_b = empathy_folks_presence_type_to_tp (folks_presence_type_b);
 
@@ -1363,6 +1315,8 @@ individual_store_name_sort_func (GtkTreeModel *model,
 
   tp_clear_object (&individual_a);
   tp_clear_object (&individual_b);
+  g_free (name_a);
+  g_free (name_b);
 
   return ret_val;
 }
@@ -1370,7 +1324,6 @@ individual_store_name_sort_func (GtkTreeModel *model,
 static void
 individual_store_setup (EmpathyIndividualStore *self)
 {
-  EmpathyIndividualStorePriv *priv;
   GType types[] = {
     GDK_TYPE_PIXBUF,            /* Status pixbuf */
     GDK_TYPE_PIXBUF,            /* Avatar pixbuf */
@@ -1386,12 +1339,11 @@ individual_store_setup (EmpathyIndividualStore *self)
     G_TYPE_BOOLEAN,             /* Is separator */
     G_TYPE_BOOLEAN,             /* Can make audio calls */
     G_TYPE_BOOLEAN,             /* Can make video calls */
-    EMPATHY_TYPE_INDIVIDUAL_MANAGER_FLAGS,      /* Flags */
     G_TYPE_BOOLEAN,             /* Is a fake group */
+    G_TYPE_STRV,                /* Client types */
+    G_TYPE_UINT,                /* Event count */
   };
 
-  priv = GET_PRIV (self);
-
   gtk_tree_store_set_column_types (GTK_TREE_STORE (self),
       EMPATHY_INDIVIDUAL_STORE_COL_COUNT, types);
 
@@ -1403,115 +1355,56 @@ individual_store_setup (EmpathyIndividualStore *self)
       EMPATHY_INDIVIDUAL_STORE_COL_STATUS,
       individual_store_state_sort_func, self, NULL);
 
-  priv->sort_criterium = EMPATHY_INDIVIDUAL_STORE_SORT_NAME;
-  empathy_individual_store_set_sort_criterium (self, priv->sort_criterium);
+  self->priv->sort_criterium = EMPATHY_INDIVIDUAL_STORE_SORT_NAME;
+
+  empathy_individual_store_set_sort_criterium (self,
+      self->priv->sort_criterium);
 }
 
 static gboolean
-individual_store_inibit_active_cb (EmpathyIndividualStore *self)
+individual_store_inhibit_active_cb (EmpathyIndividualStore *self)
 {
-  EmpathyIndividualStorePriv *priv;
-
-  priv = GET_PRIV (self);
-
-  priv->show_active = TRUE;
-  priv->inhibit_active = 0;
+  self->priv->show_active = TRUE;
+  self->priv->inhibit_active = 0;
 
   return FALSE;
 }
 
+static void
+g_queue_free_full_iter (gpointer data)
+{
+  GQueue *queue = (GQueue *) data;
+  g_queue_foreach (queue, (GFunc) gtk_tree_iter_free, NULL);
+  g_queue_free (queue);
+}
+
 static void
 empathy_individual_store_init (EmpathyIndividualStore *self)
 {
-  EmpathyIndividualStorePriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
       EMPATHY_TYPE_INDIVIDUAL_STORE, EmpathyIndividualStorePriv);
 
-  self->priv = priv;
-  priv->show_avatars = TRUE;
-  priv->show_groups = TRUE;
-  priv->show_protocols = FALSE;
-  priv->inhibit_active =
+  self->priv->show_avatars = TRUE;
+  self->priv->show_groups = TRUE;
+  self->priv->show_protocols = FALSE;
+  self->priv->inhibit_active =
       g_timeout_add_seconds (ACTIVE_USER_WAIT_TO_ENABLE_TIME,
-      (GSourceFunc) individual_store_inibit_active_cb, self);
-  priv->status_icons =
+      (GSourceFunc) individual_store_inhibit_active_cb, self);
+  self->priv->status_icons =
       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+  self->priv->folks_individual_cache = g_hash_table_new_full (NULL, NULL, NULL,
+      g_queue_free_full_iter);
+  self->priv->empathy_group_cache = g_hash_table_new_full (g_str_hash,
+      g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
   individual_store_setup (self);
 }
 
-EmpathyIndividualStore *
-empathy_individual_store_new (EmpathyIndividualManager *manager)
-{
-  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager), NULL);
-
-  return g_object_new (EMPATHY_TYPE_INDIVIDUAL_STORE,
-      "individual-manager", manager, NULL);
-}
-
-EmpathyIndividualManager *
-empathy_individual_store_get_manager (EmpathyIndividualStore *self)
-{
-  EmpathyIndividualStorePriv *priv;
-
-  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), FALSE);
-
-  priv = GET_PRIV (self);
-
-  return priv->manager;
-}
-
-gboolean
-empathy_individual_store_get_show_offline (EmpathyIndividualStore *self)
-{
-  EmpathyIndividualStorePriv *priv;
-
-  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), FALSE);
-
-  priv = GET_PRIV (self);
-
-  return priv->show_offline;
-}
-
-void
-empathy_individual_store_set_show_offline (EmpathyIndividualStore *self,
-    gboolean show_offline)
-{
-  EmpathyIndividualStorePriv *priv;
-  GList *contacts, *l;
-  gboolean show_active;
-
-  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
-
-  priv = GET_PRIV (self);
-
-  priv->show_offline = show_offline;
-  show_active = priv->show_active;
-
-  /* Disable temporarily. */
-  priv->show_active = FALSE;
-
-  contacts = empathy_individual_manager_get_members (priv->manager);
-  for (l = contacts; l; l = l->next)
-    {
-      individual_store_contact_update (self, l->data);
-    }
-  g_list_free (contacts);
-
-  /* Restore to original setting. */
-  priv->show_active = show_active;
-
-  g_object_notify (G_OBJECT (self), "show-offline");
-}
-
 gboolean
 empathy_individual_store_get_show_avatars (EmpathyIndividualStore *self)
 {
-  EmpathyIndividualStorePriv *priv;
-
   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), TRUE);
 
-  priv = GET_PRIV (self);
-
-  return priv->show_avatars;
+  return self->priv->show_avatars;
 }
 
 static gboolean
@@ -1520,14 +1413,11 @@ individual_store_update_list_mode_foreach (GtkTreeModel *model,
     GtkTreeIter *iter,
     EmpathyIndividualStore *self)
 {
-  EmpathyIndividualStorePriv *priv;
   gboolean show_avatar = FALSE;
   FolksIndividual *individual;
   GdkPixbuf *pixbuf_status;
 
-  priv = GET_PRIV (self);
-
-  if (priv->show_avatars && !priv->is_compact)
+  if (self->priv->show_avatars && !self->priv->is_compact)
     {
       show_avatar = TRUE;
     }
@@ -1546,7 +1436,7 @@ individual_store_update_list_mode_foreach (GtkTreeModel *model,
   gtk_tree_store_set (GTK_TREE_STORE (self), iter,
       EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf_status,
       EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
-      EMPATHY_INDIVIDUAL_STORE_COL_COMPACT, priv->is_compact, -1);
+      EMPATHY_INDIVIDUAL_STORE_COL_COMPACT, self->priv->is_compact, -1);
 
   g_object_unref (individual);
 
@@ -1557,14 +1447,11 @@ void
 empathy_individual_store_set_show_avatars (EmpathyIndividualStore *self,
     gboolean show_avatars)
 {
-  EmpathyIndividualStorePriv *priv;
   GtkTreeModel *model;
 
   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
 
-  priv = GET_PRIV (self);
-
-  priv->show_avatars = show_avatars;
+  self->priv->show_avatars = show_avatars;
 
   model = GTK_TREE_MODEL (self);
 
@@ -1578,27 +1465,20 @@ empathy_individual_store_set_show_avatars (EmpathyIndividualStore *self,
 gboolean
 empathy_individual_store_get_show_protocols (EmpathyIndividualStore *self)
 {
-  EmpathyIndividualStorePriv *priv;
-
   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), TRUE);
 
-  priv = GET_PRIV (self);
-
-  return priv->show_protocols;
+  return self->priv->show_protocols;
 }
 
 void
 empathy_individual_store_set_show_protocols (EmpathyIndividualStore *self,
     gboolean show_protocols)
 {
-  EmpathyIndividualStorePriv *priv;
   GtkTreeModel *model;
 
   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
 
-  priv = GET_PRIV (self);
-
-  priv->show_protocols = show_protocols;
+  self->priv->show_protocols = show_protocols;
 
   model = GTK_TREE_MODEL (self);
 
@@ -1612,33 +1492,29 @@ empathy_individual_store_set_show_protocols (EmpathyIndividualStore *self,
 gboolean
 empathy_individual_store_get_show_groups (EmpathyIndividualStore *self)
 {
-  EmpathyIndividualStorePriv *priv;
-
   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), TRUE);
 
-  priv = GET_PRIV (self);
-
-  return priv->show_groups;
+  return self->priv->show_groups;
 }
 
 void
 empathy_individual_store_set_show_groups (EmpathyIndividualStore *self,
     gboolean show_groups)
 {
-  EmpathyIndividualStorePriv *priv;
+  EmpathyIndividualStoreClass *klass;
 
   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
 
-  priv = GET_PRIV (self);
+  klass = EMPATHY_INDIVIDUAL_STORE_GET_CLASS ( self);
 
-  if (priv->show_groups == show_groups)
+  if (self->priv->show_groups == show_groups)
     {
       return;
     }
 
-  priv->show_groups = show_groups;
+  self->priv->show_groups = show_groups;
 
-  if (priv->setup_idle_id == 0)
+  if (!klass->initial_loading (self))
     {
       /* Remove all contacts and add them back, not optimized but
        * that's the easy way :)
@@ -1646,15 +1522,13 @@ empathy_individual_store_set_show_groups (EmpathyIndividualStore *self,
        * This is only done if there's not a pending setup idle
        * callback, otherwise it will race and the contacts will get
        * added twice */
-      GList *contacts;
 
       gtk_tree_store_clear (GTK_TREE_STORE (self));
-      contacts = empathy_individual_manager_get_members (priv->manager);
+      /* Also clear the cache */
+      g_hash_table_remove_all (self->priv->folks_individual_cache);
+      g_hash_table_remove_all (self->priv->empathy_group_cache);
 
-      individual_store_members_changed_cb (priv->manager,
-          "re-adding members: toggled group visibility",
-          contacts, NULL, 0, self);
-      g_list_free (contacts);
+      klass->reload_individuals (self);
     }
 
   g_object_notify (G_OBJECT (self), "show-groups");
@@ -1663,27 +1537,20 @@ empathy_individual_store_set_show_groups (EmpathyIndividualStore *self,
 gboolean
 empathy_individual_store_get_is_compact (EmpathyIndividualStore *self)
 {
-  EmpathyIndividualStorePriv *priv;
-
   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), TRUE);
 
-  priv = GET_PRIV (self);
-
-  return priv->is_compact;
+  return self->priv->is_compact;
 }
 
 void
 empathy_individual_store_set_is_compact (EmpathyIndividualStore *self,
     gboolean is_compact)
 {
-  EmpathyIndividualStorePriv *priv;
   GtkTreeModel *model;
 
   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
 
-  priv = GET_PRIV (self);
-
-  priv->is_compact = is_compact;
+  self->priv->is_compact = is_compact;
 
   model = GTK_TREE_MODEL (self);
 
@@ -1697,26 +1564,18 @@ empathy_individual_store_set_is_compact (EmpathyIndividualStore *self,
 EmpathyIndividualStoreSort
 empathy_individual_store_get_sort_criterium (EmpathyIndividualStore *self)
 {
-  EmpathyIndividualStorePriv *priv;
-
   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), 0);
 
-  priv = GET_PRIV (self);
-
-  return priv->sort_criterium;
+  return self->priv->sort_criterium;
 }
 
 void
 empathy_individual_store_set_sort_criterium (EmpathyIndividualStore *self,
     EmpathyIndividualStoreSort sort_criterium)
 {
-  EmpathyIndividualStorePriv *priv;
-
   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
 
-  priv = GET_PRIV (self);
-
-  priv->sort_criterium = sort_criterium;
+  self->priv->sort_criterium = sort_criterium;
 
   switch (sort_criterium)
     {
@@ -1729,6 +1588,9 @@ empathy_individual_store_set_sort_criterium (EmpathyIndividualStore *self,
       gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self),
           EMPATHY_INDIVIDUAL_STORE_COL_NAME, GTK_SORT_ASCENDING);
       break;
+
+    default:
+      g_assert_not_reached ();
     }
 
   g_object_notify (G_OBJECT (self), "sort-criterium");
@@ -1758,7 +1620,7 @@ empathy_individual_store_get_parent_group (GtkTreeModel *model,
   GtkTreeIter parent_iter, iter;
   gchar *name = NULL;
   gboolean is_group;
-  gboolean fake;
+  gboolean fake = FALSE;
 
   g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
 
@@ -1816,46 +1678,66 @@ individual_store_get_individual_status_icon_with_icon_name (
     FolksIndividual *individual,
     const gchar *status_icon_name)
 {
-  GdkPixbuf *pixbuf_status = NULL;
-  EmpathyIndividualStorePriv *priv;
+  GdkPixbuf *pixbuf_status;
   const gchar *protocol_name = NULL;
   gchar *icon_name = NULL;
-  GList *personas, *l;
-  guint contact_count;
+  GeeSet *personas;
+  GeeIterator *iter;
+  guint contact_count = 0;
   EmpathyContact *contact = NULL;
   gboolean show_protocols_here;
 
-  priv = GET_PRIV (self);
-
   personas = folks_individual_get_personas (individual);
-  for (l = personas, contact_count = 0; l; l = l->next)
+  iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+  while (gee_iterator_next (iter))
     {
-      if (TPF_IS_PERSONA (l->data))
+      FolksPersona *persona = gee_iterator_get (iter);
+      if (empathy_folks_persona_is_interesting (persona))
         contact_count++;
 
+      g_clear_object (&persona);
+
       if (contact_count > 1)
         break;
     }
+  g_clear_object (&iter);
 
-  show_protocols_here = (priv->show_protocols && (contact_count == 1));
+  show_protocols_here = (self->priv->show_protocols && (contact_count == 1));
   if (show_protocols_here)
     {
       contact = empathy_contact_dup_from_folks_individual (individual);
-      protocol_name = empathy_protocol_name_for_contact (contact);
-      icon_name = g_strdup_printf ("%s-%s", status_icon_name, protocol_name);
+      if (contact != NULL)
+        {
+          protocol_name = empathy_protocol_name_for_contact (contact);
+          icon_name = g_strdup_printf ("%s-%s", status_icon_name,
+              protocol_name);
+        }
+      else
+        {
+          g_warning ("Cannot retrieve contact from individual '%s'",
+              folks_alias_details_get_alias (
+                FOLKS_ALIAS_DETAILS (individual)));
+
+          return NULL;
+        }
     }
   else
     {
       icon_name = g_strdup_printf ("%s", status_icon_name);
     }
+
+  pixbuf_status = g_hash_table_lookup (self->priv->status_icons, icon_name);
+
   if (pixbuf_status == NULL)
     {
       pixbuf_status =
           empathy_pixbuf_contact_status_icon_with_icon_name (contact,
-          status_icon_name, priv->show_protocols);
+          status_icon_name, show_protocols_here);
+
       if (pixbuf_status != NULL)
         {
-          g_hash_table_insert (priv->status_icons,
+          /* pass the reference to the hash table */
+          g_hash_table_insert (self->priv->status_icons,
               g_strdup (icon_name), pixbuf_status);
         }
     }
@@ -1884,3 +1766,16 @@ empathy_individual_store_get_individual_status_icon (
 
   return pixbuf_status;
 }
+
+void
+empathy_individual_store_refresh_individual (EmpathyIndividualStore *self,
+    FolksIndividual *individual)
+{
+  gboolean show_active;
+
+  show_active = self->priv->show_active;
+  self->priv->show_active = FALSE;
+  empathy_individual_store_remove_individual (self, individual);
+  empathy_individual_store_add_individual (self, individual);
+  self->priv->show_active = show_active;
+}