]> git.0d.be Git - empathy.git/blobdiff - libempathy/empathy-contact.c
Merge branch 'gnome-3-8'
[empathy.git] / libempathy / empathy-contact.c
index 5383187c1b07bce2ef8327b86b8a9cf7c49f6d0d..356104434f7916546bd3a147508358ccb4313c5b 100644 (file)
  */
 
 #include "config.h"
+#include "empathy-contact.h"
 
-#include <string.h>
-
-#include <glib/gi18n-lib.h>
-
-#include <telepathy-glib/account-manager.h>
-#include <telepathy-glib/interfaces.h>
-#include <telepathy-glib/util.h>
-
-#include <telepathy-logger/log-manager.h>
-
-#include <folks/folks.h>
-#include <folks/folks-telepathy.h>
-
-#ifdef HAVE_GEOCLUE
-#include <geoclue/geoclue-geocode.h>
+#ifdef HAVE_GEOCODE
+#include <geocode-glib/geocode-glib.h>
 #endif
 
-#include "empathy-contact.h"
-#include "empathy-individual-manager.h"
 #include "empathy-utils.h"
 #include "empathy-enum-types.h"
-#include "empathy-marshal.h"
-#include "empathy-location.h"
 
 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
 #include "empathy-debug.h"
@@ -55,12 +39,12 @@ typedef struct {
   FolksPersona *persona;
   gchar *id;
   gchar *alias;
+  gchar *logged_alias;
   EmpathyAvatar *avatar;
   TpConnectionPresenceType presence;
   guint handle;
   EmpathyCapabilities capabilities;
   gboolean is_user;
-  guint hash;
   /* Location is composed of string keys and GValues.
    * Example: a "city" key would have "Helsinki" as string GValue,
    *          a "latitude" would have 65.0 as double GValue.
@@ -69,7 +53,7 @@ typedef struct {
    * more fields by searching the address using geoclue.
    */
   GHashTable *location;
-  GHashTable *groups;
+  GeeHashSet *groups;
   gchar **client_types;
 } EmpathyContactPriv;
 
@@ -79,7 +63,7 @@ static void contact_get_property (GObject *object, guint param_id,
 static void contact_set_property (GObject *object, guint param_id,
     const GValue *value, GParamSpec *pspec);
 
-#ifdef HAVE_GEOCLUE
+#ifdef HAVE_GEOCODE
 static void update_geocode (EmpathyContact *contact);
 #endif
 
@@ -108,6 +92,7 @@ enum
   PROP_PERSONA,
   PROP_ID,
   PROP_ALIAS,
+  PROP_LOGGED_ALIAS,
   PROP_AVATAR,
   PROP_PRESENCE,
   PROP_PRESENCE_MESSAGE,
@@ -189,14 +174,12 @@ contact_dispose (GObject *object)
 {
   EmpathyContactPriv *priv = GET_PRIV (object);
 
-  if (priv->tp_contact)
+  if (priv->tp_contact != NULL)
     {
-      g_hash_table_remove (contacts_table, priv->tp_contact);
       g_signal_handlers_disconnect_by_func (priv->tp_contact,
           tp_contact_notify_cb, object);
-      g_object_unref (priv->tp_contact);
     }
-  priv->tp_contact = NULL;
+  tp_clear_object (&priv->tp_contact);
 
   if (priv->account)
     g_object_unref (priv->account);
@@ -231,8 +214,7 @@ contact_constructed (GObject *object)
   EmpathyContact *contact = (EmpathyContact *) object;
   EmpathyContactPriv *priv = GET_PRIV (contact);
   GHashTable *location;
-  TpHandle self_handle;
-  TpHandle handle;
+  TpContact *self_contact;
   const gchar * const *client_types;
 
   if (priv->tp_contact == NULL)
@@ -256,10 +238,9 @@ contact_constructed (GObject *object)
   /* Set is-user property. Note that it could still be the handle is
    * different from the connection's self handle, in the case the handle
    * comes from a group interface. */
-  self_handle = tp_connection_get_self_handle (
+  self_contact = tp_connection_get_self_contact (
       tp_contact_get_connection (priv->tp_contact));
-  handle = tp_contact_get_handle (priv->tp_contact);
-  empathy_contact_set_is_user (contact, self_handle == handle);
+  empathy_contact_set_is_user (contact, self_contact == priv->tp_contact);
 
   g_signal_connect (priv->tp_contact, "notify",
     G_CALLBACK (tp_contact_notify_cb), contact);
@@ -318,6 +299,15 @@ empathy_contact_class_init (EmpathyContactClass *class)
         NULL,
         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  g_object_class_install_property (object_class,
+      PROP_LOGGED_ALIAS,
+      g_param_spec_string ("logged-alias",
+        "Logged alias",
+        "The alias the user had when a message was logged, "
+        "only set when using empathy_contact_from_tpl_contact()",
+        NULL,
+        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   g_object_class_install_property (object_class,
       PROP_AVATAR,
       g_param_spec_boxed ("avatar",
@@ -394,7 +384,7 @@ empathy_contact_class_init (EmpathyContactClass *class)
                   G_SIGNAL_RUN_LAST,
                   0,
                   NULL, NULL,
-                  _empathy_marshal_VOID__UINT_UINT,
+                  g_cclosure_marshal_generic,
                   G_TYPE_NONE,
                   2, G_TYPE_UINT,
                   G_TYPE_UINT);
@@ -424,9 +414,9 @@ contact_finalize (GObject *object)
 
   DEBUG ("finalize: %p", object);
 
-  if (priv->groups != NULL)
-    g_hash_table_destroy (priv->groups);
+  g_clear_object (&priv->groups);
   g_free (priv->alias);
+  g_free (priv->logged_alias);
   g_free (priv->id);
   g_strfreev (priv->client_types);
 
@@ -510,8 +500,8 @@ empathy_contact_set_presence_message (EmpathyContact *contact,
 
   if (priv->persona != NULL)
     {
-      folks_presence_set_presence_message (FOLKS_PRESENCE (priv->persona),
-          message);
+      folks_presence_details_set_presence_message (
+          FOLKS_PRESENCE_DETAILS (priv->persona), message);
     }
 }
 
@@ -559,6 +549,9 @@ contact_get_property (GObject *object,
       case PROP_ALIAS:
         g_value_set_string (value, empathy_contact_get_alias (contact));
         break;
+      case PROP_LOGGED_ALIAS:
+        g_value_set_string (value, empathy_contact_get_logged_alias (contact));
+        break;
       case PROP_AVATAR:
         g_value_set_boxed (value, empathy_contact_get_avatar (contact));
         break;
@@ -610,6 +603,10 @@ contact_set_property (GObject *object,
       case PROP_ALIAS:
         empathy_contact_set_alias (contact, g_value_get_string (value));
         break;
+      case PROP_LOGGED_ALIAS:
+        g_assert (priv->logged_alias == NULL);
+        priv->logged_alias = g_value_dup_string (value);
+        break;
       case PROP_PRESENCE:
         empathy_contact_set_presence (contact, g_value_get_uint (value));
         break;
@@ -631,14 +628,84 @@ contact_set_property (GObject *object,
     };
 }
 
+static void
+remove_tp_contact (gpointer data,
+    GObject *object)
+{
+  g_hash_table_remove (contacts_table, data);
+}
+
 static EmpathyContact *
 empathy_contact_new (TpContact *tp_contact)
 {
+  EmpathyContact *retval;
+
   g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
 
-  return g_object_new (EMPATHY_TYPE_CONTACT,
+  retval = g_object_new (EMPATHY_TYPE_CONTACT,
       "tp-contact", tp_contact,
       NULL);
+
+  g_object_weak_ref (G_OBJECT (retval), remove_tp_contact, tp_contact);
+
+  return retval;
+}
+
+typedef struct
+{
+  TplEntity *entity;
+  TpAccount *account;
+} FindContactData;
+
+static gboolean
+contact_is_tpl_entity (gpointer key,
+    gpointer value,
+    gpointer user_data)
+{
+  EmpathyContact *contact = value;
+  FindContactData *data = user_data;
+  TpAccount *account = empathy_contact_get_account (contact);
+  const gchar *path = NULL;
+
+  if (account != NULL)
+    path = tp_proxy_get_object_path (account);
+
+  return !tp_strdiff (empathy_contact_get_id (contact),
+              tpl_entity_get_identifier (data->entity)) &&
+         !tp_strdiff (tp_proxy_get_object_path (data->account), path);
+}
+
+static void
+get_contacts_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  TpWeakRef *wr = user_data;
+  EmpathyContactPriv *priv;
+  EmpathyContact *self;
+
+  self = tp_weak_ref_dup_object (wr);
+  if (self == NULL)
+    goto out;
+
+  priv = GET_PRIV (self);
+
+  g_return_if_fail (priv->tp_contact == NULL);
+
+  priv->tp_contact = tp_connection_dup_contact_by_id_finish (
+      TP_CONNECTION (source), result, NULL);
+  if (priv->tp_contact == NULL)
+    goto out;
+
+  g_object_notify (G_OBJECT (self), "tp-contact");
+
+  /* Update capabilities now that we have a TpContact */
+  set_capabilities_from_tp_caps (self,
+      tp_contact_get_capabilities (priv->tp_contact));
+
+out:
+  g_clear_object (&self);
+  tp_weak_ref_destroy (wr);
 }
 
 EmpathyContact *
@@ -647,17 +714,58 @@ empathy_contact_from_tpl_contact (TpAccount *account,
 {
   EmpathyContact *retval;
   gboolean is_user;
+  EmpathyContact *existing_contact = NULL;
 
   g_return_val_if_fail (TPL_IS_ENTITY (tpl_entity), NULL);
 
-  is_user = (TPL_ENTITY_SELF == tpl_entity_get_entity_type (tpl_entity));
+  if (contacts_table != NULL)
+    {
+      FindContactData data;
 
-  retval = g_object_new (EMPATHY_TYPE_CONTACT,
-      "id", tpl_entity_get_alias (tpl_entity),
-      "alias", tpl_entity_get_identifier (tpl_entity),
-      "account", account,
-      "is-user", is_user,
-      NULL);
+      data.entity = tpl_entity;
+      data.account = account;
+
+      existing_contact = g_hash_table_find (contacts_table,
+        contact_is_tpl_entity, &data);
+    }
+
+  if (existing_contact != NULL)
+    {
+      retval = g_object_new (EMPATHY_TYPE_CONTACT,
+          "tp-contact", empathy_contact_get_tp_contact (existing_contact),
+          "logged-alias", tpl_entity_get_alias (tpl_entity),
+          NULL);
+    }
+  else
+    {
+      TpConnection *conn;
+      const gchar *id;
+
+      is_user = (TPL_ENTITY_SELF == tpl_entity_get_entity_type (tpl_entity));
+
+      id = tpl_entity_get_identifier (tpl_entity);
+
+      retval = g_object_new (EMPATHY_TYPE_CONTACT,
+          "id", id,
+          "alias", tpl_entity_get_alias (tpl_entity),
+          "account", account,
+          "is-user", is_user,
+          NULL);
+
+      /* Try to get a TpContact associated to have at least contact
+       * capabilities if possible. This is useful for CM supporting calling
+       * offline contacts for example. */
+      conn = tp_account_get_connection (account);
+      if (conn != NULL)
+        {
+          TpContactFeature features[] = { TP_CONTACT_FEATURE_CAPABILITIES };
+          conn = tp_account_get_connection (account);
+
+          tp_connection_dup_contact_by_id_async (conn, id,
+              G_N_ELEMENTS (features), features, get_contacts_cb,
+              tp_weak_ref_new (retval, NULL, NULL));
+        }
+    }
 
   if (!EMP_STR_EMPTY (tpl_entity_get_avatar_token (tpl_entity)))
     contact_load_avatar_cache (retval,
@@ -697,16 +805,16 @@ const gchar *
 empathy_contact_get_alias (EmpathyContact *contact)
 {
   EmpathyContactPriv *priv;
-  const gchar        *alias;
+  const gchar        *alias = NULL;
 
   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
 
   priv = GET_PRIV (contact);
 
-  if (priv->tp_contact != NULL)
-    alias = tp_contact_get_alias (priv->tp_contact);
-  else
+  if (!EMP_STR_EMPTY (priv->alias))
     alias = priv->alias;
+  else if (priv->tp_contact != NULL)
+    alias = tp_contact_get_alias (priv->tp_contact);
 
   if (!EMP_STR_EMPTY (alias))
     return alias;
@@ -714,6 +822,21 @@ empathy_contact_get_alias (EmpathyContact *contact)
     return empathy_contact_get_id (contact);
 }
 
+const gchar *
+empathy_contact_get_logged_alias (EmpathyContact *contact)
+{
+  EmpathyContactPriv *priv;
+
+  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+  priv = GET_PRIV (contact);
+
+  if (priv->logged_alias != NULL)
+    return priv->logged_alias;
+  else
+    return empathy_contact_get_alias (contact);
+}
+
 void
 empathy_contact_set_alias (EmpathyContact *contact,
                           const gchar *alias)
@@ -729,12 +852,12 @@ empathy_contact_set_alias (EmpathyContact *contact,
 
   /* Set the alias on the persona if possible */
   persona = empathy_contact_get_persona (contact);
-  if (persona != NULL && FOLKS_IS_ALIASABLE (persona))
+  if (persona != NULL && FOLKS_IS_ALIAS_DETAILS (persona))
     {
       DEBUG ("Setting alias for contact %s to %s",
           empathy_contact_get_id (contact), alias);
 
-      folks_aliasable_set_alias (FOLKS_ALIASABLE (persona), alias);
+      folks_alias_details_set_alias (FOLKS_ALIAS_DETAILS (persona), alias);
     }
 
   if (tp_strdiff (alias, priv->alias))
@@ -752,10 +875,10 @@ groups_change_group_cb (GObject *source,
     GAsyncResult *result,
     gpointer user_data)
 {
-  FolksGroupable *groupable = FOLKS_GROUPABLE (source);
+  FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
   GError *error = NULL;
 
-  folks_groupable_change_group_finish (groupable, result, &error);
+  folks_group_details_change_group_finish (group_details, result, &error);
   if (error != NULL)
     {
       g_warning ("failed to change group: %s", error->message);
@@ -779,9 +902,9 @@ empathy_contact_change_group (EmpathyContact *contact, const gchar *group,
   persona = empathy_contact_get_persona (contact);
   if (persona != NULL)
     {
-      if (FOLKS_IS_GROUPABLE (persona))
-        folks_groupable_change_group (FOLKS_GROUPABLE (persona), group, is_member,
-          groups_change_group_cb, contact);
+      if (FOLKS_IS_GROUP_DETAILS (persona))
+        folks_group_details_change_group (FOLKS_GROUP_DETAILS (persona), group,
+            is_member, groups_change_group_cb, contact);
       return;
     }
 
@@ -789,12 +912,11 @@ empathy_contact_change_group (EmpathyContact *contact, const gchar *group,
    * does */
   if (priv->groups == NULL)
     {
-      priv->groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
-          NULL);
+      priv->groups = gee_hash_set_new (G_TYPE_STRING, (GBoxedCopyFunc) g_strdup,
+          g_free, NULL, NULL, NULL, NULL, NULL, NULL);
     }
 
-  g_hash_table_insert (priv->groups, g_strdup (group),
-      GUINT_TO_POINTER (is_member));
+  gee_collection_add (GEE_COLLECTION (priv->groups), group);
 }
 
 EmpathyAvatar *
@@ -850,7 +972,7 @@ empathy_contact_get_account (EmpathyContact *contact)
       /* FIXME: This assume the account manager already exists */
       connection = tp_contact_get_connection (priv->tp_contact);
       priv->account =
-        g_object_ref (empathy_get_account_for_connection (connection));
+        g_object_ref (tp_connection_get_account (connection));
     }
 
   return priv->account;
@@ -867,42 +989,14 @@ empathy_contact_get_persona (EmpathyContact *contact)
 
   if (priv->persona == NULL && priv->tp_contact != NULL)
     {
-      /* FIXME: This is disgustingly slow */
-      /* Query for the persona */
-      EmpathyIndividualManager *manager;
-      GList *individuals, *l;
-
-      manager = empathy_individual_manager_dup_singleton ();
-      individuals = empathy_individual_manager_get_members (manager);
+      TpfPersona *persona;
 
-      for (l = individuals; l != NULL; l = l->next)
+      persona = tpf_persona_dup_for_contact (priv->tp_contact);
+      if (persona != NULL)
         {
-          GList *personas, *j;
-          FolksIndividual *individual = FOLKS_INDIVIDUAL (l->data);
-
-          personas = folks_individual_get_personas (individual);
-          for (j = personas; j != NULL; j = j->next)
-            {
-              TpfPersona *persona = j->data;
-
-              if (TPF_IS_PERSONA (persona))
-                {
-                  TpContact *tp_contact = tpf_persona_get_contact (persona);
-
-                  if (tp_contact == priv->tp_contact)
-                    {
-                      /* Found the right persona */
-                      empathy_contact_set_persona (contact,
-                          (FolksPersona *) persona);
-                      goto finished;
-                    }
-                }
-            }
+          empathy_contact_set_persona (contact, (FolksPersona *) persona);
+          g_object_unref (persona);
         }
-
-finished:
-      g_list_free (individuals);
-      g_object_unref (manager);
     }
 
   return priv->persona;
@@ -944,8 +1038,9 @@ empathy_contact_set_persona (EmpathyContact *contact,
   /* Set the persona's groups */
   if (priv->groups != NULL)
     {
-      folks_groupable_set_groups (FOLKS_GROUPABLE (persona), priv->groups);
-      g_hash_table_destroy (priv->groups);
+      folks_group_details_set_groups (FOLKS_GROUP_DETAILS (persona),
+          GEE_SET (priv->groups));
+      g_object_unref (priv->groups);
       priv->groups = NULL;
     }
 }
@@ -991,7 +1086,11 @@ empathy_contact_get_presence_message (EmpathyContact *contact)
   priv = GET_PRIV (contact);
 
   if (priv->persona != NULL)
-    return folks_presence_get_presence_message (FOLKS_PRESENCE (priv->persona));
+    return folks_presence_details_get_presence_message (
+        FOLKS_PRESENCE_DETAILS (priv->persona));
+
+  if (priv->tp_contact != NULL)
+    return tp_contact_get_presence_message (priv->tp_contact);
 
   return NULL;
 }
@@ -1092,6 +1191,18 @@ empathy_contact_get_status (EmpathyContact *contact)
       empathy_contact_get_presence (contact));
 }
 
+gboolean
+empathy_contact_can_sms (EmpathyContact *contact)
+{
+  EmpathyContactPriv *priv;
+
+  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
+
+  priv = GET_PRIV (contact);
+
+  return priv->capabilities & EMPATHY_CAPABILITIES_SMS;
+}
+
 gboolean
 empathy_contact_can_voip (EmpathyContact *contact)
 {
@@ -1157,12 +1268,17 @@ static gboolean
 contact_has_log (EmpathyContact *contact)
 {
   TplLogManager *manager;
+  TplEntity *entity;
   gboolean have_log;
 
   manager = tpl_log_manager_dup_singleton ();
+  entity = tpl_entity_new (empathy_contact_get_id (contact),
+      TPL_ENTITY_CONTACT, NULL, NULL);
+
   have_log = tpl_log_manager_exists (manager,
-      empathy_contact_get_account (contact), empathy_contact_get_id (contact),
-      FALSE);
+      empathy_contact_get_account (contact), entity, TPL_EVENT_MASK_TEXT);
+
+  g_object_unref (entity);
   g_object_unref (manager);
 
   return have_log;
@@ -1179,6 +1295,9 @@ empathy_contact_can_do_action (EmpathyContact *self,
       case EMPATHY_ACTION_CHAT:
         sensitivity = TRUE;
         break;
+      case EMPATHY_ACTION_SMS:
+        sensitivity = empathy_contact_can_sms (self);
+        break;
       case EMPATHY_ACTION_AUDIO_CALL:
         sensitivity = empathy_contact_can_voip_audio (self);
         break;
@@ -1219,8 +1338,8 @@ contact_get_avatar_filename (EmpathyContact *contact,
   avatar_path = g_build_filename (g_get_user_cache_dir (),
       "telepathy",
       "avatars",
-      tp_account_get_connection_manager (account),
-      tp_account_get_protocol (account),
+      tp_account_get_cm_name (account),
+      tp_account_get_protocol_name (account),
       NULL);
   g_mkdir_with_parents (avatar_path, 0700);
 
@@ -1257,17 +1376,16 @@ contact_load_avatar_cache (EmpathyContact *contact,
         }
     }
 
-  if (data)
+  if (data != NULL)
     {
       DEBUG ("Avatar loaded from %s", filename);
       avatar = empathy_avatar_new ((guchar *) data, len, NULL, filename);
       contact_set_avatar (contact, avatar);
       empathy_avatar_unref (avatar);
     }
-  else
-    {
-      g_free (filename);
-    }
+
+  g_free (data);
+  g_free (filename);
 
   return data != NULL;
 }
@@ -1294,24 +1412,23 @@ empathy_avatar_get_type (void)
  * @format: the mime type of the avatar image
  * @filename: the filename where the avatar is stored in cache
  *
- * Create a #EmpathyAvatar from the provided data. This function takes the
- * ownership of @data, @format and @filename.
+ * Create a #EmpathyAvatar from the provided data.
  *
  * Returns: a new #EmpathyAvatar
  */
 EmpathyAvatar *
-empathy_avatar_new (guchar *data,
+empathy_avatar_new (const guchar *data,
                     gsize len,
-                    gchar *format,
-                    gchar *filename)
+                    const gchar *format,
+                    const gchar *filename)
 {
   EmpathyAvatar *avatar;
 
   avatar = g_slice_new0 (EmpathyAvatar);
-  avatar->data = data;
+  avatar->data = g_memdup (data, len);
   avatar->len = len;
-  avatar->format = format;
-  avatar->filename = filename;
+  avatar->format = g_strdup (format);
+  avatar->filename = g_strdup (filename);
   avatar->refcount = 1;
 
   return avatar;
@@ -1415,7 +1532,7 @@ empathy_contact_set_location (EmpathyContact *contact,
     g_hash_table_unref (priv->location);
 
   priv->location = g_hash_table_ref (location);
-#ifdef HAVE_GEOCLUE
+#ifdef HAVE_GEOCODE
   update_geocode (contact);
 #endif
   g_object_notify (G_OBJECT (contact), "location");
@@ -1483,97 +1600,74 @@ empathy_contact_equal (gconstpointer contact1,
   return FALSE;
 }
 
-#ifdef HAVE_GEOCLUE
-#define GEOCODE_SERVICE "org.freedesktop.Geoclue.Providers.Yahoo"
-#define GEOCODE_PATH "/org/freedesktop/Geoclue/Providers/Yahoo"
-
-/* This callback is called by geoclue when it found a position
+#ifdef HAVE_GEOCODE
+/* This callback is called by geocode-glib when it found a position
  * for the given address.  A position is necessary for a contact
  * to show up on the map
  */
 static void
-geocode_cb (GeoclueGeocode *geocode,
-    GeocluePositionFields fields,
-    double latitude,
-    double longitude,
-    double altitude,
-    GeoclueAccuracy *accuracy,
-    GError *error,
-    gpointer contact)
+geocode_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
 {
+  EmpathyContact *contact = user_data;
   EmpathyContactPriv *priv = GET_PRIV (contact);
+  GError *error = NULL;
+  GList *res;
+  GeocodeLocation *loc;
   GHashTable *new_location;
+  GHashTable *resolved = NULL;
+  gdouble latitude, longitude;
 
   if (priv->location == NULL)
     goto out;
 
-  if (error != NULL)
+  res = geocode_forward_search_finish (GEOCODE_FORWARD (source), result,
+      &error);
+
+  if (res == NULL)
     {
-      DEBUG ("Error geocoding location : %s", error->message);
+      DEBUG ("Failed to resolve geocode: %s", error->message);
+      g_error_free (error);
       goto out;
     }
 
-  /* No need to change location if we didn't find the position */
-  if (!(fields & GEOCLUE_POSITION_FIELDS_LATITUDE))
-    goto out;
-
-  if (!(fields & GEOCLUE_POSITION_FIELDS_LONGITUDE))
-    goto out;
+  loc = res->data;
 
   new_location = tp_asv_new (
-      EMPATHY_LOCATION_LAT, G_TYPE_DOUBLE, latitude,
-      EMPATHY_LOCATION_LON, G_TYPE_DOUBLE, longitude,
+      EMPATHY_LOCATION_LAT, G_TYPE_DOUBLE, loc->latitude,
+      EMPATHY_LOCATION_LON, G_TYPE_DOUBLE, loc->longitude,
       NULL);
 
-  DEBUG ("\t - Latitude: %f", latitude);
-  DEBUG ("\t - Longitude: %f", longitude);
+  DEBUG ("\t - Latitude: %f", loc->latitude);
+  DEBUG ("\t - Longitude: %f", loc->longitude);
+
+  g_list_free_full (res, (GDestroyNotify) geocode_location_free);
 
   /* Copy remaning fields. LAT and LON were not defined so we won't overwrite
    * the values we just set. */
   tp_g_hash_table_update (new_location, priv->location,
       (GBoxedCopyFunc) g_strdup, (GBoxedCopyFunc) tp_g_value_slice_dup);
 
-  /* Set the altitude only if it wasn't defined before */
-  if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE &&
-      g_hash_table_lookup (new_location, EMPATHY_LOCATION_ALT) == NULL)
-    {
-      tp_asv_set_double (new_location, g_strdup (EMPATHY_LOCATION_ALT),
-          altitude);
-      DEBUG ("\t - Altitude: %f", altitude);
-    }
-
   /* Don't change the accuracy as we used an address to get this position */
   g_hash_table_unref (priv->location);
   priv->location = new_location;
-  g_object_notify (contact, "location");
+  g_object_notify ((GObject *) contact, "location");
+
 out:
-  g_object_unref (geocode);
+  tp_clear_pointer (&resolved, g_hash_table_unref);
   g_object_unref (contact);
 }
 
-static gchar *
-get_dup_string (GHashTable *location,
-    gchar *key)
-{
-  GValue *value;
-
-  value = g_hash_table_lookup (location, key);
-  if (value != NULL)
-    return g_value_dup_string (value);
-
-  return NULL;
-}
-
 static void
 update_geocode (EmpathyContact *contact)
 {
-  static GeoclueGeocode *geocode;
-  gchar *str;
-  GHashTable *address;
+  GeocodeForward *geocode;
   GHashTable *location;
 
   location = empathy_contact_get_location (contact);
-  if (location == NULL)
+  if (location == NULL ||
+      g_hash_table_size (location) == 0)
     return;
 
   /* No need to search for position if contact published it */
@@ -1581,75 +1675,14 @@ update_geocode (EmpathyContact *contact)
       g_hash_table_lookup (location, EMPATHY_LOCATION_LON) != NULL)
     return;
 
+  geocode = geocode_forward_new_for_params (location);
   if (geocode == NULL)
-    {
-      geocode = geoclue_geocode_new (GEOCODE_SERVICE, GEOCODE_PATH);
-      g_object_add_weak_pointer (G_OBJECT (geocode), (gpointer *) &geocode);
-    }
-  else
-    {
-      g_object_ref (geocode);
-    }
-
-  address = geoclue_address_details_new ();
-
-  str = get_dup_string (location, EMPATHY_LOCATION_COUNTRY_CODE);
-  if (str != NULL)
-    {
-      g_hash_table_insert (address,
-        g_strdup (GEOCLUE_ADDRESS_KEY_COUNTRYCODE), str);
-      DEBUG ("\t - countrycode: %s", str);
-    }
-
-  str = get_dup_string (location, EMPATHY_LOCATION_COUNTRY);
-  if (str != NULL)
-    {
-      g_hash_table_insert (address,
-        g_strdup (GEOCLUE_ADDRESS_KEY_COUNTRY), str);
-      DEBUG ("\t - country: %s", str);
-    }
-
-  str = get_dup_string (location, EMPATHY_LOCATION_POSTAL_CODE);
-  if (str != NULL)
-    {
-      g_hash_table_insert (address,
-        g_strdup (GEOCLUE_ADDRESS_KEY_POSTALCODE), str);
-      DEBUG ("\t - postalcode: %s", str);
-    }
-
-  str = get_dup_string (location, EMPATHY_LOCATION_REGION);
-  if (str != NULL)
-    {
-      g_hash_table_insert (address,
-        g_strdup (GEOCLUE_ADDRESS_KEY_REGION), str);
-      DEBUG ("\t - region: %s", str);
-    }
-
-  str = get_dup_string (location, EMPATHY_LOCATION_LOCALITY);
-  if (str != NULL)
-    {
-      g_hash_table_insert (address,
-        g_strdup (GEOCLUE_ADDRESS_KEY_LOCALITY), str);
-      DEBUG ("\t - locality: %s", str);
-    }
-
-  str = get_dup_string (location, EMPATHY_LOCATION_STREET);
-  if (str != NULL)
-    {
-      g_hash_table_insert (address,
-        g_strdup (GEOCLUE_ADDRESS_KEY_STREET), str);
-      DEBUG ("\t - street: %s", str);
-    }
-
-  if (g_hash_table_size (address) > 0)
-    {
-      g_object_ref (contact);
+    return;
 
-      geoclue_geocode_address_to_position_async (geocode, address,
-          geocode_cb, contact);
-    }
+  geocode_forward_search_async (geocode, NULL, geocode_cb,
+      g_object_ref (contact));
 
-  g_hash_table_unref (address);
+  g_object_unref (geocode);
 }
 #endif
 
@@ -1657,63 +1690,27 @@ static EmpathyCapabilities
 tp_caps_to_capabilities (TpCapabilities *caps)
 {
   EmpathyCapabilities capabilities = 0;
-  guint i;
-  GPtrArray *classes;
 
-  classes = tp_capabilities_get_channel_classes (caps);
+  if (tp_capabilities_supports_file_transfer (caps))
+    capabilities |= EMPATHY_CAPABILITIES_FT;
 
-  for (i = 0; i < classes->len; i++)
-    {
-      GValueArray *class_struct;
-      GHashTable *fixed_prop;
-      GStrv allowed_prop;
-      TpHandleType handle_type;
-      const gchar *chan_type;
-
-      class_struct = g_ptr_array_index (classes, i);
-      tp_value_array_unpack (class_struct, 2,
-          &fixed_prop,
-          &allowed_prop);
-
-      handle_type = tp_asv_get_uint32 (fixed_prop,
-          TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL);
-      if (handle_type != TP_HANDLE_TYPE_CONTACT)
-        continue;
-
-      chan_type = tp_asv_get_string (fixed_prop,
-          TP_PROP_CHANNEL_CHANNEL_TYPE);
-
-      if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
-        {
-          capabilities |= EMPATHY_CAPABILITIES_FT;
-        }
-      else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE))
-        {
-          const gchar *service;
-
-          service = tp_asv_get_string (fixed_prop,
-              TP_PROP_CHANNEL_TYPE_STREAM_TUBE_SERVICE);
+  if (tp_capabilities_supports_stream_tubes (caps, TP_HANDLE_TYPE_CONTACT,
+        "rfb"))
+    capabilities |= EMPATHY_CAPABILITIES_RFB_STREAM_TUBE;
 
-          if (!tp_strdiff (service, "rfb"))
-            capabilities |= EMPATHY_CAPABILITIES_RFB_STREAM_TUBE;
-        }
-      else if (!tp_strdiff (chan_type,
-        TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
-        {
-          guint j;
-
-          for (j = 0; allowed_prop[j] != NULL; j++)
-            {
-              if (!tp_strdiff (allowed_prop[j],
-                    TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_AUDIO))
-                capabilities |= EMPATHY_CAPABILITIES_AUDIO;
-              else if (!tp_strdiff (allowed_prop[j],
-                    TP_PROP_CHANNEL_TYPE_STREAMED_MEDIA_INITIAL_VIDEO))
-                capabilities |= EMPATHY_CAPABILITIES_VIDEO;
-            }
-        }
+  if (tp_capabilities_supports_audio_video_call (caps, TP_HANDLE_TYPE_CONTACT))
+    {
+      capabilities |= EMPATHY_CAPABILITIES_AUDIO;
+      capabilities |= EMPATHY_CAPABILITIES_VIDEO;
+    }
+  else if (tp_capabilities_supports_audio_call (caps, TP_HANDLE_TYPE_CONTACT))
+    {
+      capabilities |= EMPATHY_CAPABILITIES_AUDIO;
     }
 
+  if (tp_capabilities_supports_sms (caps))
+    capabilities |= EMPATHY_CAPABILITIES_SMS;
+
   return capabilities;
 }
 
@@ -1745,12 +1742,26 @@ contact_set_avatar_from_tp_contact (EmpathyContact *contact)
       EmpathyAvatar *avatar;
       gchar *data;
       gsize len;
+      gchar *path;
+      GError *error = NULL;
+
+      if (!g_file_load_contents (file, NULL, &data, &len, NULL, &error))
+        {
+          DEBUG ("Failed to load avatar: %s", error->message);
+
+          g_error_free (error);
+          contact_set_avatar (contact, NULL);
+          return;
+        }
+
+      path = g_file_get_path (file);
+
+      avatar = empathy_avatar_new ((guchar *) data, len, mime, path);
 
-      g_file_load_contents (file, NULL, &data, &len, NULL, NULL);
-      avatar = empathy_avatar_new ((guchar *) data, len, g_strdup (mime),
-          g_file_get_path (file));
       contact_set_avatar (contact, avatar);
       empathy_avatar_unref (avatar);
+      g_free (path);
+      g_free (data);
     }
   else
     {
@@ -1791,15 +1802,16 @@ static int
 presence_cmp_func (EmpathyContact *a,
     EmpathyContact *b)
 {
-  FolksPresence *presence_a, *presence_b;
+  FolksPresenceDetails *presence_a, *presence_b;
 
-  presence_a = FOLKS_PRESENCE (empathy_contact_get_persona (a));
-  presence_b = FOLKS_PRESENCE (empathy_contact_get_persona (b));
+  presence_a = FOLKS_PRESENCE_DETAILS (empathy_contact_get_persona (a));
+  presence_b = FOLKS_PRESENCE_DETAILS (empathy_contact_get_persona (b));
 
   /* We negate the result because we're sorting in reverse order (i.e. such that
    * the Personas with the highest presence are at the beginning of the list. */
-  return -folks_presence_typecmp (folks_presence_get_presence_type (presence_a),
-      folks_presence_get_presence_type (presence_b));
+  return -folks_presence_details_typecmp (
+      folks_presence_details_get_presence_type (presence_a),
+      folks_presence_details_get_presence_type (presence_b));
 }
 
 static gint
@@ -1941,35 +1953,42 @@ EmpathyContact *
 empathy_contact_dup_best_for_action (FolksIndividual *individual,
     EmpathyActionType action_type)
 {
-  GList *personas, *contacts, *l;
+  GeeSet *personas;
+  GeeIterator *iter;
+  GList *contacts;
   EmpathyContact *best_contact = NULL;
 
   /* Build a list of EmpathyContacts that we can sort */
   personas = folks_individual_get_personas (individual);
   contacts = NULL;
 
-  for (l = personas; l != NULL; l = l->next)
+  iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+  while (gee_iterator_next (iter))
     {
+      FolksPersona *persona = gee_iterator_get (iter);
       TpContact *tp_contact;
-      EmpathyContact *contact;
+      EmpathyContact *contact = NULL;
 
-      if (!TPF_IS_PERSONA (l->data))
-        continue;
+      if (!empathy_folks_persona_is_interesting (persona))
+        goto while_finish;
+
+      tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+      if (tp_contact == NULL)
+        goto while_finish;
 
-      tp_contact = tpf_persona_get_contact (TPF_PERSONA (l->data));
       contact = empathy_contact_dup_from_tp_contact (tp_contact);
-      empathy_contact_set_persona (contact, FOLKS_PERSONA (l->data));
+      empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
 
       /* Only choose the contact if they're actually capable of the specified
        * action. */
-      if (!empathy_contact_can_do_action (contact, action_type))
-        {
-          g_object_unref (contact);
-          continue;
-        }
+      if (empathy_contact_can_do_action (contact, action_type))
+        contacts = g_list_prepend (contacts, g_object_ref (contact));
 
-      contacts = g_list_prepend (contacts, contact);
+while_finish:
+      g_clear_object (&contact);
+      g_clear_object (&persona);
     }
+  g_clear_object (&iter);
 
   /* Sort the contacts by some heuristic based on the action type, then take
    * the top contact. */
@@ -1984,3 +2003,53 @@ empathy_contact_dup_best_for_action (FolksIndividual *individual,
 
   return best_contact;
 }
+
+#define declare_contact_cb(name) \
+static void \
+contact_##name##_cb (GObject *source, \
+    GAsyncResult *result, \
+    gpointer user_data) \
+{ \
+  TpContact *contact = (TpContact *) source; \
+  GError *error = NULL; \
+  \
+  if (!tp_contact_##name##_finish (contact, result, &error)) \
+    { \
+      DEBUG ("Failed to ##name## on %s\n", \
+          tp_contact_get_identifier (contact)); \
+      g_error_free (error); \
+    } \
+}
+
+declare_contact_cb (request_subscription)
+declare_contact_cb (authorize_publication)
+declare_contact_cb (unblock)
+
+void
+empathy_contact_add_to_contact_list (EmpathyContact *self,
+    const gchar *message)
+{
+  EmpathyContactPriv *priv = GET_PRIV (self);
+
+  g_return_if_fail (priv->tp_contact != NULL);
+
+  tp_contact_request_subscription_async (priv->tp_contact, message,
+      contact_request_subscription_cb, NULL);
+
+  tp_contact_authorize_publication_async (priv->tp_contact,
+      contact_authorize_publication_cb, NULL);
+
+  tp_contact_unblock_async (priv->tp_contact, contact_unblock_cb, NULL);
+}
+
+declare_contact_cb (remove)
+
+void
+empathy_contact_remove_from_contact_list (EmpathyContact *self)
+{
+  EmpathyContactPriv *priv = GET_PRIV (self);
+
+  g_return_if_fail (priv->tp_contact != NULL);
+
+  tp_contact_remove_async (priv->tp_contact, contact_remove_cb, NULL);
+}