[folks] Initial implementation of the tracker backend



commit 6a2253b8110f04e904b022e9fc21f66f3153a78a
Author: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
Date:   Tue Feb 15 17:13:30 2011 +0000

    Initial implementation of the tracker backend

 backends/Makefile.am                               |   13 +
 backends/tracker/Makefile.am                       |   63 ++
 backends/tracker/lib/Makefile.am                   |  115 +++
 .../tracker/lib/folks-tracker-uninstalled.pc.in    |   12 +
 backends/tracker/lib/folks-tracker.deps            |    4 +
 backends/tracker/lib/folks-tracker.pc.in           |   15 +
 backends/tracker/lib/trf-persona-store.vala        | 1028 ++++++++++++++++++++
 backends/tracker/lib/trf-persona.vala              |  982 +++++++++++++++++++
 backends/tracker/lib/trf-util.vala                 |  139 +++
 backends/tracker/tr-backend-factory.vala           |   59 ++
 backends/tracker/tr-backend.vala                   |  120 +++
 configure.ac                                       |   39 +
 tests/Makefile.am                                  |   17 +
 tests/lib/Makefile.am                              |   13 +
 tests/lib/tracker/Makefile.am                      |   51 +
 tests/lib/tracker/backend.vala                     |  544 +++++++++++
 tests/tools/Makefile.am                            |    1 +
 tests/tools/tracker.sh                             |   34 +
 tests/tools/with-session-bus-tracker.sh            |   45 +
 tests/tracker/Makefile.am                          |  253 +++++
 tests/tracker/add-contact.vala                     |  147 +++
 tests/tracker/additional-names-updates.vala        |  163 +++
 tests/tracker/avatar-details-interface.vala        |  169 ++++
 tests/tracker/avatar-updates.vala                  |  177 ++++
 tests/tracker/birthday-details-interface.vala      |  150 +++
 tests/tracker/birthday-updates.vala                |  181 ++++
 tests/tracker/data/avatar-01.jpg                   |  Bin 0 -> 1157 bytes
 tests/tracker/data/backend-tracker-only.ini        |    6 +
 tests/tracker/default-contact.vala                 |  144 +++
 tests/tracker/email-details-interface.vala         |  150 +++
 tests/tracker/emails-updates.vala                  |  207 ++++
 tests/tracker/family-name-updates.vala             |  160 +++
 tests/tracker/favourite-details-interface.vala     |  166 ++++
 tests/tracker/favourite-updates.vala               |  182 ++++
 tests/tracker/fullname-updates.vala                |  153 +++
 tests/tracker/gender-details-interface.vala        |  143 +++
 tests/tracker/given-name-updates.vala              |  158 +++
 tests/tracker/im-details-interface.vala            |  160 +++
 tests/tracker/imaddresses-updates.vala             |  197 ++++
 tests/tracker/individual-retrieval.vala            |  138 +++
 tests/tracker/name-details-interface.vala          |  188 ++++
 tests/tracker/nickname-updates.vala                |  153 +++
 tests/tracker/note-details-interface.vala          |  151 +++
 tests/tracker/phone-details-interface.vala         |  149 +++
 tests/tracker/phones-updates.vala                  |  196 ++++
 .../tracker/postal-address-details-interface.vala  |  176 ++++
 tests/tracker/prefix-name-updates.vala             |  155 +++
 tests/tracker/remove-contact.vala                  |  140 +++
 tests/tracker/role-details-interface.vala          |  138 +++
 tests/tracker/suffix-name-updates.vala             |  158 +++
 tests/tracker/url-details-interface.vala           |  150 +++
 tests/tracker/website-updates.vala                 |  175 ++++
 52 files changed, 8427 insertions(+), 0 deletions(-)
---
diff --git a/backends/Makefile.am b/backends/Makefile.am
index cd023e8..02ae4f0 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -3,4 +3,17 @@ SUBDIRS = \
 	key-file \
 	$(NULL)
 
+if ENABLE_TRACKER
+SUBDIRS += tracker
+endif
+
+DIST_SUBDIRS = \
+	telepathy \
+	key-file \
+	$(NULL)
+
+if ENABLE_TRACKER
+DIST_SUBDIRS += tracker
+endif
+
 -include $(top_srcdir)/git.mk
diff --git a/backends/tracker/Makefile.am b/backends/tracker/Makefile.am
new file mode 100644
index 0000000..80e9ae8
--- /dev/null
+++ b/backends/tracker/Makefile.am
@@ -0,0 +1,63 @@
+SUBDIRS = lib
+
+BACKEND_NAME = "tracker"
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/folks \
+	-I$(top_srcdir)/backends/tracker/lib \
+	-include $(CONFIG_HEADER) \
+	-DPACKAGE_DATADIR=\"$(pkgdatadir)\" \
+	-DBACKEND_NAME=\"$(BACKEND_NAME)\" \
+	-DG_LOG_DOMAIN=\"$(BACKEND_NAME)\" \
+	$(NULL)
+
+VALAFLAGS += \
+	--vapidir=. \
+	--vapidir=vapi \
+	--vapidir=$(top_builddir)/backends/tracker/lib \
+	--vapidir=$(top_srcdir)/folks \
+	$(addprefix --pkg ,$(folks_backend_tracker_deps)) \
+	$(NULL)
+
+backenddir = $(BACKEND_DIR)/tracker
+backend_LTLIBRARIES = libfolks-backend-tracker.la
+
+libfolks_backend_tracker_la_SOURCES = \
+	tr-backend.vala \
+	tr-backend-factory.vala \
+	$(NULL)
+
+folks_backend_tracker_deps = \
+	folks \
+	folks-tracker \
+	gee-1.0 \
+	gio-2.0 \
+	gobject-2.0 \
+	tracker-sparql-0.10 \
+	$(NULL)
+
+libfolks_backend_tracker_la_CFLAGS = \
+	$(GIO_CFLAGS) \
+	$(GLIB_CFLAGS) \
+	$(GEE_CFLAGS) \
+	$(TRACKER_SPARQL_CFLAGS) \
+	$(NULL)
+
+libfolks_backend_tracker_la_LIBADD = \
+	$(GIO_LIBS) \
+	$(GLIB_LIBS) \
+	$(GEE_LIBS) \
+	$(top_builddir)/folks/libfolks.la \
+	$(TRACKER_SPARQL_LIBS) \
+	lib/libfolks-tracker.la \
+	$(NULL)
+
+libfolks_backend_tracker_la_LDFLAGS = -shared -fPIC -module -avoid-version
+
+GITIGNOREFILES = \
+	folks-backend-tracker.vapi \
+	$(libfolks_backend_tracker_la_SOURCES:.vala=.c) \
+	libfolks_backend_tracker_la_vala.stamp \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/backends/tracker/lib/Makefile.am b/backends/tracker/lib/Makefile.am
new file mode 100644
index 0000000..d4a8b80
--- /dev/null
+++ b/backends/tracker/lib/Makefile.am
@@ -0,0 +1,115 @@
+BACKEND_NAME = "tracker"
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/folks \
+	-include $(CONFIG_HEADER) \
+	-DPACKAGE_DATADIR=\"$(pkgdatadir)\" \
+	-DBACKEND_NAME=\"$(BACKEND_NAME)\" \
+	-DG_LOG_DOMAIN=\"$(BACKEND_NAME)\" \
+	$(NULL)
+
+VAPIGENFLAGS += \
+	--vapidir=. \
+	--vapidir=$(top_srcdir)/folks
+
+folks_trackerdir = $(libdir)
+folks_tracker_LTLIBRARIES = libfolks-tracker.la
+
+CLEANFILES =
+
+##################################################################
+# Support library
+##################################################################
+
+pkgconfig_in = folks-tracker.pc.in
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = $(pkgconfig_in:.in=)
+
+libfolks_tracker_la_vala.stamp:
+
+folks_tracker_valasources = \
+	trf-persona.vala \
+	trf-persona-store.vala \
+	trf-util.vala \
+	$(NULL)
+
+libfolks_tracker_la_SOURCES = \
+	$(folks_tracker_valasources) \
+	$(NULL)
+
+libfolks_tracker_la_VALAFLAGS = \
+	--vapidir=. \
+	--vapidir=$(top_srcdir)/folks \
+	--pkg folks \
+	--pkg gobject-2.0 \
+	--pkg gio-2.0 \
+	--pkg gee-1.0 \
+	--pkg tracker-sparql-0.10 \
+	--vapi folks-tracker.vapi \
+	-H folks-tracker.h \
+	$(NULL)
+
+folks_backend_tracker_deps = \
+	folks \
+	gee-1.0 \
+	gio-2.0 \
+	gobject-2.0 \
+	tracker-sparql-0.10 \
+	$(NULL)
+
+libfolks_tracker_la_CFLAGS = \
+	$(GIO_CFLAGS) \
+	$(GLIB_CFLAGS) \
+	$(GEE_CFLAGS) \
+	$(TRACKER_SPARQL_CFLAGS) \
+	$(NULL)
+
+libfolks_tracker_la_LIBADD = \
+	$(GIO_LIBS) \
+	$(GLIB_LIBS) \
+	$(GEE_LIBS) \
+	$(top_builddir)/folks/libfolks.la \
+	$(TRACKER_SPARQL_LIBS) \
+	$(NULL)
+
+# The quoting here is unnecessary but harmless, and has the useful side-effect
+# that vim quickfix mode (:make) doesn't interpret the libtool --mode=link
+# command as an error message in a bizarrely named file
+libfolks_tracker_la_LDFLAGS = \
+	-version-info "$(LT_CURRENT)":"$(LT_REVISION)":"$(LT_AGE)" \
+	-export-symbols-regex "^(TRF|trf)_.*|" \
+	$(NULL)
+
+folks_tracker_includedir = $(includedir)/folks
+folks_tracker_include_HEADERS = \
+	folks-tracker.h \
+	$(NULL)
+
+vapidir = $(datadir)/vala/vapi
+dist_vapi_DATA = \
+	folks-tracker.vapi \
+	folks-tracker.deps \
+	$(NULL)
+
+##################################################################
+# General
+##################################################################
+
+CLEANFILES += \
+	$(pkgconfig_in:.in=) \
+	folks-tracker-uninstalled.pc \
+	$(NULL)
+
+MAINTAINERCLEANFILES = \
+	$(folks_tracker_valasources:.vala=.c) \
+	libfolks_tracker_la_vala.stamp \
+	folks-tracker.h \
+	folks-tracker.vapi \
+	$(NULL)
+
+EXTRA_DIST = \
+	$(pkgconfig_in) \
+	$(MAINTAINERCLEANFILES) \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/backends/tracker/lib/folks-tracker-uninstalled.pc.in b/backends/tracker/lib/folks-tracker-uninstalled.pc.in
new file mode 100644
index 0000000..51a096b
--- /dev/null
+++ b/backends/tracker/lib/folks-tracker-uninstalled.pc.in
@@ -0,0 +1,12 @@
+prefix=
+exec_prefix=
+abs_top_srcdir= abs_top_srcdir@
+abs_top_builddir= abs_top_builddir@
+vapidir= abs_top_srcdir@/folks
+
+Name: Folks Tracker support library (uninstalled copy)
+Description: Tracker support library for the Folks meta-contacts library
+Version: @VERSION@
+Requires: folks glib-2.0 gobject-2.0 gee-1.0 tracker-sparql-0.10
+Libs: ${abs_top_builddir}/backends/telepathy/libfolks-tracker.la
+Cflags: -I${abs_top_srcdir} -I${abs_top_srcdir}/backends/tracker -I${abs_top_builddir}
diff --git a/backends/tracker/lib/folks-tracker.deps b/backends/tracker/lib/folks-tracker.deps
new file mode 100644
index 0000000..841bba3
--- /dev/null
+++ b/backends/tracker/lib/folks-tracker.deps
@@ -0,0 +1,4 @@
+glib-2.0
+gobject-2.0
+folks
+tracker-sparql-0.10
\ No newline at end of file
diff --git a/backends/tracker/lib/folks-tracker.pc.in b/backends/tracker/lib/folks-tracker.pc.in
new file mode 100644
index 0000000..bff6680
--- /dev/null
+++ b/backends/tracker/lib/folks-tracker.pc.in
@@ -0,0 +1,15 @@
+prefix= prefix@
+exec_prefix= exec_prefix@
+libdir= libdir@
+bindir= bindir@
+includedir= includedir@
+datarootdir= datarootdir@
+datadir= datadir@
+vapidir= datadir@/vala/vapi
+
+Name: Folks tracker support library
+Description: Tracker support library for the Folks meta-contacts library
+Version: @VERSION@
+Requires: folks glib-2.0 gobject-2.0 gee-1.0 tracker-sparql-0.10
+Libs: -L${libdir} -lfolks-tracker
+Cflags: -I${includedir} -I${includedir}/folks
diff --git a/backends/tracker/lib/trf-persona-store.vala b/backends/tracker/lib/trf-persona-store.vala
new file mode 100644
index 0000000..ce43a11
--- /dev/null
+++ b/backends/tracker/lib/trf-persona-store.vala
@@ -0,0 +1,1028 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *         Travis Reitter <travis reitter collabora co uk>
+ *         Philip Withnall <philip withnall collabora co uk>
+ *         Marco Barisione <marco barisione collabora co uk>
+ *         Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ */
+
+using Folks;
+using GLib;
+using Tracker;
+using Tracker.Sparql;
+
+extern const string BACKEND_NAME;
+
+internal enum Trf.Fields
+{
+  TRACKER_ID,
+  FULL_NAME,
+  FAMILY_NAME,
+  GIVEN_NAME,
+  ADDITIONAL_NAMES,
+  PREFIXES,
+  SUFFIXES,
+  NICKNAME,
+  BIRTHDAY,
+  AVATAR_URL,
+  IM_ADDRESSES,
+  PHONES,
+  EMAILS,
+  URLS,
+  FAVOURITE,
+  CONTACT_URN,
+  ROLES,
+  NOTE,
+  GENDER,
+  POSTAL_ADDRESS
+}
+
+internal enum Trf.AfflInfoFields
+{
+  IM_TRACKER_ID,
+  IM_PROTOCOL,
+  IM_ACCOUNT_ID,
+  AFFL_TRACKER_ID,
+  AFFL_ROLE,
+  AFFL_ORG,
+  AFFL_POBOX,
+  AFFL_DISTRICT,
+  AFFL_COUNTY,
+  AFFL_LOCALITY,
+  AFFL_POSTALCODE,
+  AFFL_STREET_ADDRESS,
+  AFFL_ADDRESS_LOCATION,
+  AFFL_EXTENDED_ADDRESS,
+  AFFL_COUNTRY,
+  AFFL_REGION,
+  AFFL_EMAIL,
+  AFFL_PHONE,
+  AFFL_WEBSITE,
+  AFFL_BLOG,
+  AFFL_URL
+}
+
+internal enum Trf.PostalAddressFields
+{
+  TRACKER_ID,
+  POBOX,
+  DISTRICT,
+  COUNTY,
+  LOCALITY,
+  POSTALCODE,
+  STREET_ADDRESS,
+  ADDRESS_LOCATION,
+  EXTENDED_ADDRESS,
+  COUNTRY,
+  REGION
+}
+
+internal enum Trf.UrlsFields
+{
+  TRACKER_ID,
+  BLOG,
+  WEBSITE,
+  URL
+}
+
+internal enum Trf.RoleFields
+{
+  TRACKER_ID,
+  ROLE,
+  DEPARTMENT
+}
+
+internal enum Trf.IMFields
+{
+  TRACKER_ID,
+  PROTO,
+  ID
+}
+
+internal enum Trf.PhoneFields
+{
+  TRACKER_ID,
+  PHONE
+}
+
+internal enum Trf.EmailFields
+{
+  TRACKER_ID,
+  EMAIL
+}
+
+internal enum Trf.TagFields
+{
+  TRACKER_ID
+}
+
+/**
+ * A persona store.
+ * It will create { link Persona}s for each contacts on the main addressbook.
+ */
+public class Trf.PersonaStore : Folks.PersonaStore
+{
+  private const string _OBJECT_NAME = "org.freedesktop.Tracker1";
+  private const string _OBJECT_IFACE = "org.freedesktop.Tracker1.Resources";
+  private const string _OBJECT_PATH = "/org/freedesktop/Tracker1/Resources";
+  private HashTable<string, Persona> _personas;
+  private bool _is_prepared = false;
+  private static const int _default_timeout = 100;
+  private Resources _resources_object;
+  private Tracker.Sparql.Connection _connection;
+  private static Gee.TreeMap<string, string> _urn_prefix = null;
+  private static Gee.TreeMap<string, int> _prefix_tracker_id = null;
+  private static const string _INITIAL_QUERY =
+    "SELECT " +
+    "tracker:id(?_contact) " +
+    "nco:fullname(?_contact) " +
+    "nco:nameFamily(?_contact) " +
+    "nco:nameGiven(?_contact) " +
+    "nco:nameAdditional(?_contact) " +
+    "nco:nameHonorificPrefix(?_contact) " +
+    "nco:nameHonorificSuffix(?_contact) " +
+    "nco:nickname(?_contact) " +
+    "nco:birthDate(?_contact) " +
+    "nie:url(nco:photo(?_contact)) " +
+
+    /* keep synced with Trf.IMFields */
+    "(SELECT " +
+    "GROUP_CONCAT ( " +
+    " fn:concat(tracker:id(?affl),'\t'," +
+    " tracker:coalesce(nco:imProtocol(?a),''), " +
+    "'\t', tracker:coalesce(nco:imID(?a),'')),'\\n') " +
+    "WHERE { ?_contact nco:hasAffiliation ?affl. " +
+    " ?affl nco:hasIMAddress ?a } ) " +
+
+    /* keep synced with Trf.PhoneFields */
+    "(SELECT " +
+    "GROUP_CONCAT " +
+    " (fn:concat(tracker:id(?affl),'\t', " +
+    " nco:phoneNumber(?aff_number)), " +
+    "'\\n') " +
+    "WHERE { ?_contact nco:hasAffiliation ?affl . " +
+    " ?affl nco:hasPhoneNumber ?aff_number  } ) " +
+
+    /* keep synced with Trf.EmailFields */
+    "(SELECT " +
+    "GROUP_CONCAT " +
+    " (fn:concat(tracker:id(?affl), '\t', " +
+    "  nco:emailAddress(?emailaddress)), " +
+    "',') " +
+    "WHERE { ?_contact nco:hasAffiliation ?affl . " +
+    " ?affl nco:hasEmailAddress ?emailaddress }) " +
+
+    /* keep synced with Trf.UrlsFields */
+    " (SELECT " +
+    "GROUP_CONCAT " +
+    " (fn:concat(tracker:id(?affl), '\t'," +
+    "  tracker:coalesce(nco:blogUrl(?affl),'')," +
+    "  '\t'," +
+    "  tracker:coalesce(nco:websiteUrl(?affl),'')" +
+    "  , '\t'," +
+    "  tracker:coalesce(nco:url(?affl),''))," +
+    "  '\\n') " +
+    "WHERE { ?_contact nco:hasAffiliation ?affl  } )" +
+
+    /* keep synced with Trf.TagFields */
+    "(SELECT " +
+    "GROUP_CONCAT(tracker:id(?_tag), " +
+    "',') " +
+    "WHERE { ?_contact nao:hasTag " +
+    "?_tag }) " +
+
+    "?_contact " +
+
+    /* keep synced with Trf.RoleFields */
+    "(SELECT " +
+    "GROUP_CONCAT " +
+    " (fn:concat(tracker:id(?affl), '\t', " +
+    "  tracker:coalesce(nco:role(?affl),''), '\t', " +
+    "  tracker:coalesce(nco:department(?affl),'')),  " +
+    "'\\n') " +
+    "WHERE { ?_contact nco:hasAffiliation " +
+    "?affl }) " +
+
+    "nco:note(?_contact) " +
+    "tracker:id(nco:gender(?_contact)) " +
+
+    /* keep synced with Trf.PostalAddressFields*/
+    "(SELECT " +
+    "GROUP_CONCAT " +
+    " (fn:concat(tracker:id(?affl), '\t', " +
+    "  tracker:coalesce(nco:pobox(?postal),'')" +
+    "  , '\t', " +
+    "  tracker:coalesce(nco:district(?postal),'')" +
+    "  , '\t', " +
+    "  tracker:coalesce(nco:county(?postal),'')" +
+    "  , '\t', " +
+    "  tracker:coalesce(nco:locality(?postal),'')" +
+    "  , '\t', " +
+    "  tracker:coalesce(nco:postalcode(?postal),'')" +
+    "  , '\t', " +
+    "  tracker:coalesce(nco:streetAddress(?postal)" +
+    "  ,''), '\t', " +
+    "  tracker:coalesce(nco:addressLocation(?postal)" +
+    "  ,''), '\t', " +
+    "  tracker:coalesce(nco:extendedAddress(?postal)" +
+    "  ,''), '\t', " +
+    "  tracker:coalesce(nco:country(?postal),'')" +
+    "  , '\t', " +
+    "  tracker:coalesce(nco:region(?postal),'')),  " +
+    "'\\n') " +
+    "WHERE { ?_contact nco:hasAffiliation " +
+    "?affl . ?affl nco:hasPostalAddress ?postal }) " +
+
+    "{ ?_contact a nco:PersonContact . } " +
+    "ORDER BY tracker:id(?_contact) ";
+
+
+  /**
+   * The type of persona store this is.
+   *
+   * See { link Folks.PersonaStore.type_id}.
+   */
+  public override string type_id { get { return BACKEND_NAME; } }
+
+  /**
+   * Whether this PersonaStore can add { link Folks.Persona}s.
+   *
+   * See { link Folks.PersonaStore.can_add_personas}.
+   *
+   * @since UNRELEASED
+   */
+  public override MaybeBool can_add_personas
+    {
+      get { return MaybeBool.FALSE; }
+    }
+
+  /**
+   * Whether this PersonaStore can set the alias of { link Folks.Persona}s.
+   *
+   * See { link Folks.PersonaStore.can_alias_personas}.
+   *
+   * @since UNRELEASED
+   */
+  public override MaybeBool can_alias_personas
+    {
+      get { return MaybeBool.FALSE; }
+    }
+
+  /**
+   * Whether this PersonaStore can set the groups of { link Folks.Persona}s.
+   *
+   * See { link Folks.PersonaStore.can_group_personas}.
+   *
+   * @since UNRELEASED
+   */
+  public override MaybeBool can_group_personas
+    {
+      get { return MaybeBool.FALSE; }
+    }
+
+  /**
+   * Whether this PersonaStore can remove { link Folks.Persona}s.
+   *
+   * See { link Folks.PersonaStore.can_remove_personas}.
+   *
+   * @since UNRELEASED
+   */
+  public override MaybeBool can_remove_personas
+    {
+      get { return MaybeBool.FALSE; }
+    }
+
+  /**
+   * Whether this PersonaStore has been prepared.
+   *
+   * See { link Folks.PersonaStore.is_prepared}.
+   *
+   * @since UNRELEASED
+   */
+  public override bool is_prepared
+    {
+      get { return this._is_prepared; }
+    }
+
+  /**
+   * The { link Persona}s exposed by this PersonaStore.
+   *
+   * See { link Folks.PersonaStore.personas}.
+   */
+  public override HashTable<string, Persona> personas
+    {
+      get { return this._personas; }
+    }
+
+  /**
+   * Create a new PersonaStore.
+   *
+   * Create a new persona store to store the { link Persona}s for the contacts
+   */
+  public PersonaStore ()
+    {
+      Object (id: BACKEND_NAME, display_name: BACKEND_NAME);
+      this._personas = new HashTable<string, Persona> (str_hash, str_equal);
+      debug ("Initial query : \n%s\n", this._INITIAL_QUERY);
+    }
+
+  /**
+   * Add a new { link Persona} to the PersonaStore.
+   *
+   * See { link Folks.PersonaStore.add_persona_from_details}.
+   */
+  public override async Folks.Persona? add_persona_from_details (
+      HashTable<string, Value?> details) throws Folks.PersonaStoreError
+    {
+      throw new PersonaStoreError.READ_ONLY (
+          "Personas cannot be added to this store.");
+    }
+
+  /**
+   * Remove a { link Persona} from the PersonaStore.
+   *
+   * See { link Folks.PersonaStore.remove_persona}.
+   */
+  public override async void remove_persona (Folks.Persona persona)
+      throws Folks.PersonaStoreError
+    {
+      throw new PersonaStoreError.READ_ONLY (
+          "Personas cannot be removed from this store.");
+    }
+
+  /**
+   * Prepare the PersonaStore for use.
+   *
+   * TODO: we should throw different errors dependening on what went wrong
+   *       when we were trying to setup the PersonaStore.
+   *
+   * See { link Folks.PersonaStore.prepare}.
+   */
+  public override async void prepare () throws GLib.Error
+    {
+      lock (this._is_prepared)
+        {
+          if (!this._is_prepared)
+            {
+              try
+                {
+                  this._connection = Tracker.Sparql.Connection.get ();
+
+                  yield this._build_predicates_table ();
+                  yield this._do_add_contacts (this._INITIAL_QUERY);
+
+                  /* Don't add a match rule for all signals from Tracker but
+                   * only for GraphUpdated with the specific class we need. We
+                   * don't want to be woken up for irrelevent updates on the
+                   * graph.
+                   */
+                  this._resources_object = yield GLib.Bus.get_proxy<Resources> (
+                      BusType.SESSION,
+                      this._OBJECT_NAME,
+                      this._OBJECT_PATH,
+                      DBusProxyFlags.DO_NOT_CONNECT_SIGNALS |
+                        DBusProxyFlags.DO_NOT_LOAD_PROPERTIES);
+                  this._resources_object.g_connection.signal_subscribe
+                      (this._OBJECT_NAME, this._OBJECT_IFACE,
+                      "GraphUpdated", this._OBJECT_PATH,
+                      Trf.OntologyDefs.PERSON_CLASS, GLib.DBusSignalFlags.NONE,
+                      this._graph_updated_cb);
+                }
+              catch (GLib.IOError e1)
+                {
+                  warning ("Could not connect to D-Bus service: %s",
+                           e1.message);
+                  throw new PersonaStoreError.INVALID_ARGUMENT (e1.message);
+                }
+              catch (Tracker.Sparql.Error e2)
+                {
+                  warning ("Error fetching SPARQL connection handler: %s",
+                           e2.message);
+                  throw new PersonaStoreError.INVALID_ARGUMENT (e2.message);
+                }
+              catch (GLib.DBusError e3)
+                {
+                  warning ("Could not connect to D-Bus service: %s",
+                           e3.message);
+                  throw new PersonaStoreError.INVALID_ARGUMENT (e3.message);
+                }
+            }
+        }
+    }
+
+  public int get_favorite_id ()
+    {
+      return this._prefix_tracker_id.get
+          (Trf.OntologyDefs.NAO_FAVORITE);
+    }
+
+  public int get_gender_male_id ()
+    {
+      return this._prefix_tracker_id.get
+          (Trf.OntologyDefs.NCO_MALE);
+    }
+
+  public int get_gender_female_id ()
+    {
+      return this._prefix_tracker_id.get
+          (Trf.OntologyDefs.NCO_FEMALE);
+    }
+
+  private async void _build_predicates_table ()
+    {
+      if (this._prefix_tracker_id != null)
+        {
+          return;
+        }
+
+      yield this._build_urn_prefix_table ();
+
+      this._prefix_tracker_id = new Gee.TreeMap<string, int> ();
+
+      string query = "SELECT  ";
+      foreach (var urn_t in this._urn_prefix.keys)
+        {
+          query += " tracker:id(" + urn_t + ")";
+        }
+      query += " WHERE {} ";
+
+      try
+        {
+          Sparql.Cursor cursor = yield this._connection.query_async (query);
+
+          while (cursor.next ())
+            {
+              int i=0;
+              foreach (var urn in this._urn_prefix.keys)
+                {
+                  var tracker_id = (int) cursor.get_integer (i);
+                  var prefix = this._urn_prefix.get (urn).dup ();
+                  this._prefix_tracker_id.set (prefix, tracker_id);
+                  i++;
+                }
+            }
+        }
+      catch (Tracker.Sparql.Error e1)
+        {
+          warning ("Couldn't build predicates table: %s %s", query, e1.message);
+        }
+      catch (GLib.Error e2)
+        {
+          warning ("Couldn't build predicates table: %s %s", query, e2.message);
+        }
+    }
+
+  private async void _build_urn_prefix_table ()
+    {
+      if (this._urn_prefix != null)
+        {
+          return;
+        }
+      this._urn_prefix = new Gee.TreeMap<string, string> ();
+      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#fullname>",
+          Trf.OntologyDefs.NCO_FULLNAME);
+      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#nameFamily>",
+          Trf.OntologyDefs.NCO_FAMILY);
+      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#nameGiven>",
+          Trf.OntologyDefs.NCO_GIVEN);
+      this._urn_prefix.set (
+          Trf.OntologyDefs.NCO_URL_PREFIX + "nco#nameAdditional>",
+          Trf.OntologyDefs.NCO_ADDITIONAL);
+      this._urn_prefix.set (
+          Trf.OntologyDefs.NCO_URL_PREFIX + "nco#nameHonorificSuffix>",
+          Trf.OntologyDefs.NCO_SUFFIX);
+      this._urn_prefix.set (
+         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#nameHonorificPrefix>",
+         Trf.OntologyDefs.NCO_PREFIX);
+      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#nickname>",
+         Trf.OntologyDefs.NCO_NICKNAME);
+      this._urn_prefix.set (
+         Trf.OntologyDefs.RDF_URL_PREFIX + "22-rdf-syntax-ns#type>",
+         Trf.OntologyDefs.RDF_TYPE);
+      this._urn_prefix.set (
+         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#PersonContact>",
+         Trf.OntologyDefs.NCO_PERSON);
+      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#websiteUrl>",
+         Trf.OntologyDefs.NCO_WEBSITE);
+      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#blogUrl>",
+         Trf.OntologyDefs.NCO_BLOG);
+      this._urn_prefix.set (
+         Trf.OntologyDefs.NAO_URL_PREFIX + "nao#predefined-tag-favorite>",
+         Trf.OntologyDefs.NAO_FAVORITE);
+      this._urn_prefix.set (Trf.OntologyDefs.NAO_URL_PREFIX + "nao#hasTag>",
+         Trf.OntologyDefs.NAO_TAG);
+      this._urn_prefix.set (
+         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#hasEmailAddress>",
+         Trf.OntologyDefs.NCO_HAS_EMAIL);
+      this._urn_prefix.set (
+         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#hasPhoneNumber>",
+         Trf.OntologyDefs.NCO_HAS_PHONE);
+      this._urn_prefix.set (
+         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#hasAffiliation>",
+         Trf.OntologyDefs.NCO_HAS_AFFILIATION);
+      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#birthDate>",
+         Trf.OntologyDefs.NCO_BIRTHDAY);
+      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#note>",
+         Trf.OntologyDefs.NCO_NOTE);
+      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#gender>",
+         Trf.OntologyDefs.NCO_GENDER);
+      this._urn_prefix.set (
+         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#gender-male>",
+         Trf.OntologyDefs.NCO_MALE);
+      this._urn_prefix.set (
+         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#gender-female>",
+         Trf.OntologyDefs.NCO_FEMALE);
+      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#photo>",
+         Trf.OntologyDefs.NCO_PHOTO);
+    }
+
+  private void _graph_updated_cb (DBusConnection connection,
+      string sender_name, string object_path, string interface_name,
+      string signal_name, Variant parameters)
+    {
+      string class_name = "";
+      VariantIter iter_del = null;
+      VariantIter iter_ins = null;
+
+      parameters.get("(sa(iiii)a(iiii))", &class_name, &iter_del, &iter_ins);
+
+      if (class_name != Trf.OntologyDefs.PERSON_CLASS)
+        {
+          return;
+        }
+
+      var removed_personas = new Queue<Persona> ();
+      var added_personas = new Queue<Persona> ();
+
+      var nco_person_id =
+          this._prefix_tracker_id.get (Trf.OntologyDefs.NCO_PERSON);
+      var rdf_type_id = this._prefix_tracker_id.get (Trf.OntologyDefs.RDF_TYPE);
+
+      Event e = Event ();
+
+      while (iter_del.next
+          ("(iiii)", &e.graph_id, &e.subject_id, &e.pred_id, &e.object_id))
+        {
+          var p_id = Trf.Persona.build_iid (this.id, e.subject_id.to_string ());
+          var persona = this._personas.lookup (p_id);
+          if (persona != null)
+            {
+              if (e.pred_id == rdf_type_id &&
+                  e.object_id == nco_person_id)
+                {
+                  removed_personas.push_tail (persona);
+                  _personas.remove (persona.iid);
+                }
+              else
+                {
+                  this._do_update (persona, e, false);
+                }
+            }
+        }
+
+      while (iter_ins.next
+          ("(iiii)", &e.graph_id, &e.subject_id, &e.pred_id, &e.object_id))
+        {
+          var subject_tracker_id = e.subject_id.to_string ();
+          var p_id = Trf.Persona.build_iid (this.id, subject_tracker_id);
+          var persona = this._personas.lookup (p_id);
+          if (persona == null)
+            {
+              persona = new Trf.Persona (this, subject_tracker_id);
+              this._personas.insert (persona.iid, persona);
+              added_personas.push_tail (persona);
+            }
+          this._do_update (persona, e);
+        }
+
+      if (removed_personas.length > 0)
+        {
+          this.personas_changed (null, removed_personas.head, null, null, 0);
+        }
+
+      if (added_personas.length > 0)
+        {
+          this.personas_changed (added_personas.head, null, null, null, 0);
+        }
+    }
+
+  private async void _do_add_contacts (string query)
+    {
+      try {
+        var added_personas = new Queue<Persona> ();
+        Sparql.Cursor cursor = yield this._connection.query_async (query);
+
+        while (cursor.next ())
+          {
+            int tracker_id = (int) cursor.get_integer (Trf.Fields.TRACKER_ID);
+            var p_id = Trf.Persona.build_iid (this.id, tracker_id.to_string ());
+            if (this._personas.lookup (p_id) == null)
+              {
+                var persona = new Trf.Persona (this,
+                    tracker_id.to_string (), cursor);
+                this._personas.insert (persona.iid, persona);
+                added_personas.push_tail (persona);
+              }
+          }
+
+        if (added_personas.length > 0)
+          {
+            this.personas_changed (added_personas.head, null, null, null, 0);
+          }
+      } catch (GLib.Error e) {
+        warning ("Couldn't perform queries: %s %s", query, e.message);
+      }
+    }
+
+  private void _do_update (Persona p, Event e, bool adding = true)
+    {
+      if (e.pred_id ==
+          this._prefix_tracker_id.get (Trf.OntologyDefs.NCO_FULLNAME))
+        {
+          string fullname = "";
+          if (adding)
+            {
+              fullname =
+              this._get_property (e.subject_id, Trf.OntologyDefs.NCO_FULLNAME);
+            }
+          p._update_full_name (fullname);
+        }
+      else if (e.pred_id ==
+               this._prefix_tracker_id.get (Trf.OntologyDefs.NCO_NICKNAME))
+        {
+          string nickname = "";
+          if (adding)
+            {
+              nickname =
+                  this._get_property (
+                      e.subject_id, Trf.OntologyDefs.NCO_NICKNAME);
+            }
+          p._update_nickname (nickname);
+        }
+      else if (e.pred_id ==
+               this._prefix_tracker_id.get (Trf.OntologyDefs.NCO_FAMILY))
+        {
+          string family_name = "";
+          if (adding)
+            {
+              family_name = this._get_property
+                  (e.subject_id, Trf.OntologyDefs.NCO_FAMILY);
+            }
+          p._update_family_name (family_name);
+        }
+      else if (e.pred_id ==
+               this._prefix_tracker_id.get (Trf.OntologyDefs.NCO_GIVEN))
+        {
+          string given_name = "";
+          if (adding)
+            {
+              given_name = this._get_property (
+                  e.subject_id, Trf.OntologyDefs.NCO_GIVEN);
+            }
+          p._update_given_name (given_name);
+        }
+      else if (e.pred_id ==
+               this._prefix_tracker_id.get (Trf.OntologyDefs.NCO_ADDITIONAL))
+        {
+          string additional_name = "";
+          if (adding)
+            {
+              additional_name = this._get_property
+                  (e.subject_id, Trf.OntologyDefs.NCO_ADDITIONAL);
+            }
+          p._update_additional_names (additional_name);
+        }
+      else if (e.pred_id == this._prefix_tracker_id.get
+          (Trf.OntologyDefs.NCO_SUFFIX))
+        {
+          string suffix_name = "";
+          if (adding)
+            {
+              suffix_name = this._get_property
+                  (e.subject_id, Trf.OntologyDefs.NCO_SUFFIX);
+            }
+          p._update_suffixes (suffix_name);
+        }
+      else if (e.pred_id == this._prefix_tracker_id.get
+          (Trf.OntologyDefs.NCO_PREFIX))
+        {
+          string prefix_name = "";
+          if (adding)
+            {
+              prefix_name = this._get_property
+                  (e.subject_id, Trf.OntologyDefs.NCO_PREFIX);
+            }
+          p._update_prefixes (prefix_name);
+        }
+      else if (e.pred_id == this._prefix_tracker_id.get
+          ("nao:hasTag"))
+        {
+          if (e.object_id == this.get_favorite_id ())
+            {
+              if (adding)
+                {
+                  p.is_favourite = true;
+                }
+              else
+                {
+                  p.is_favourite = false;
+                }
+            }
+        }
+      else if (e.pred_id == this._prefix_tracker_id.get
+          (Trf.OntologyDefs.NCO_HAS_EMAIL))
+        {
+          if (adding)
+            {
+              var email = this._get_property (
+                  e.object_id,
+                  Trf.OntologyDefs.NCO_EMAIL_PROP,
+                  Trf.OntologyDefs.NCO_EMAIL);
+              p._add_email (email, e.object_id.to_string ());
+            }
+          else
+            {
+              p._remove_email (e.object_id.to_string ());
+            }
+        }
+      else if (e.pred_id == this._prefix_tracker_id.get
+          (Trf.OntologyDefs.NCO_HAS_PHONE))
+        {
+          if (adding)
+            {
+              var phone = this._get_property (
+                  e.object_id, Trf.OntologyDefs.NCO_PHONE_PROP,
+                  Trf.OntologyDefs.NCO_PHONE);
+              p._add_phone (phone, e.object_id.to_string ());
+            }
+          else
+            {
+              p._remove_phone (e.object_id.to_string ());
+            }
+        }
+      else if (e.pred_id == this._prefix_tracker_id.get
+          (Trf.OntologyDefs.NCO_HAS_AFFILIATION))
+        {
+          if (adding)
+            {
+              var affl_info = this._get_affl_info (e.subject_id.to_string (),
+                  e.object_id.to_string ());
+
+              debug ("affl_info : %s", affl_info.to_string ());
+
+              if (affl_info.im_tracker_id != null)
+                {
+                  p._add_im_address (affl_info.affl_tracker_id,
+                      affl_info.im_proto, affl_info.im_account_id);
+                }
+
+              if (affl_info.affl_tracker_id != null)
+                {
+                  if (affl_info.title != null ||
+                      affl_info.org != null)
+                    {
+                      p._add_role (affl_info.affl_tracker_id, affl_info.title,
+                          affl_info.org);
+                    }
+                }
+
+              if (affl_info.postal_address != null)
+                p._add_postal_address (affl_info.postal_address);
+
+              if (affl_info.phone != null)
+                p._add_phone (affl_info.phone, e.object_id.to_string ());
+
+              if (affl_info.email != null)
+                p._add_email (affl_info.email, e.object_id.to_string ());
+
+              if (affl_info.website != null)
+                p._add_url (affl_info.website,
+                    affl_info.affl_tracker_id, "website");
+
+              if (affl_info.blog != null)
+                p._add_url (affl_info.blog,
+                    affl_info.affl_tracker_id, "blog");
+
+              if (affl_info.url != null)
+                p._add_url (affl_info.url,
+                    affl_info.affl_tracker_id, "url");
+            }
+          else
+            {
+              p._remove_im_address (e.object_id.to_string ());
+              p._remove_role (e.object_id.to_string ());
+              p._remove_postal_address (e.object_id.to_string ());
+              p._remove_phone (e.object_id.to_string ());
+              p._remove_email (e.object_id.to_string ());
+              p._remove_url (e.object_id.to_string ());
+            }
+        }
+      else if (e.pred_id == this._prefix_tracker_id.get
+          (Trf.OntologyDefs.NCO_BIRTHDAY))
+        {
+          string bday = "";
+          if (adding)
+            {
+              bday = this._get_property (
+                  e.subject_id, Trf.OntologyDefs.NCO_BIRTHDAY);
+            }
+          p._set_birthday (bday);
+        }
+      else if (e.pred_id == this._prefix_tracker_id.get
+          (Trf.OntologyDefs.NCO_NOTE))
+        {
+          string note = "";
+          if (adding)
+            {
+              note = this._get_property (
+                  e.subject_id, Trf.OntologyDefs.NCO_NOTE);
+            }
+          p._set_note (note);
+        }
+      else if (e.pred_id == this._prefix_tracker_id.get
+          (Trf.OntologyDefs.NCO_GENDER))
+        {
+          if (adding)
+            {
+              p._set_gender (e.object_id);
+            }
+          else
+            {
+              p._set_gender (0);
+            }
+        }
+      else if (e.pred_id == this._prefix_tracker_id.get
+          (Trf.OntologyDefs.NCO_PHOTO))
+        {
+          string avatar_url = "";
+          if (adding)
+            {
+              avatar_url = this._get_property (e.object_id,
+                  Trf.OntologyDefs.NIE_URL, Trf.OntologyDefs.NFO_IMAGE);
+            }
+          p._set_avatar (avatar_url);
+        }
+    }
+
+  private string _get_property
+      (int subject_tracker_id, string property,
+       string subject_type = Trf.OntologyDefs.NCO_PERSON)
+    {
+      string ret = "";
+      const string query_template =
+        "SELECT ?property WHERE" +
+        " { ?p a %s ; " +
+        "   %s ?property " +
+        " . FILTER(tracker:id(?p) = %d ) }";
+
+      string query = query_template.printf (subject_type,
+          property, subject_tracker_id);
+
+      try
+        {
+          Sparql.Cursor cursor = this._connection.query (query);
+          while (cursor.next ())
+            {
+              var prop = cursor.get_string (0);
+              if (prop != null)
+                {
+                  ret = prop.dup ();
+                }
+            }
+        }
+      catch (Tracker.Sparql.Error e1)
+        {
+          warning ("Couldn't fetch propery: %s %s", query, e1.message);
+        }
+      catch (GLib.Error e2)
+        {
+          warning ("Couldn't fetch property: %s %s", query, e2.message);
+        }
+
+      return ret;
+    }
+
+  /*
+   * This should be kept in sync with Trf.AfflInfoFields
+   */
+  private Trf.AfflInfo _get_affl_info (
+      string person_id, string affiliation_id)
+    {
+      Trf.AfflInfo affl_info = new Trf.AfflInfo ();
+      const string query_template =
+        "SELECT " +
+        "tracker:id(?i) " +
+        "nco:imProtocol(?i) " +
+        "nco:imID(?i) " +
+        "tracker:id(?a) " +
+        "nco:role(?a) " +
+        "nco:org(?a) " +
+        "nco:pobox(?postal) " +
+        "nco:district(?postal) " +
+        "nco:county(?postal) " +
+        "nco:locality(?postal) " +
+        "nco:postalcode(?postal) " +
+        "nco:streetAddress(?postal) " +
+        "nco:addressLocation(?postal) " +
+        "nco:extendedAddress(?postal) " +
+        "nco:country(?postal) " +
+        "nco:region(?postal) " +
+        "nco:emailAddress(?e) " +
+        "nco:phoneNumber(?number) " +
+        "nco:websiteUrl(?a) " +
+        "nco:blogUrl(?a) " +
+        "nco:url(?a) " +
+        " WHERE { ?p a nco:PersonContact ; nco:hasAffiliation ?a . " +
+        " OPTIONAL { ?a nco:hasIMAddress ?i } . " +
+        " OPTIONAL { ?a nco:hasPostalAddress ?postal } . " +
+        " OPTIONAL { ?a nco:hasEmailAddress ?e } . " +
+        " OPTIONAL { ?a nco:hasPhoneNumber ?number }  " +
+        " FILTER(tracker:id(?p) = %s" +
+        " && tracker:id(?a) = %s" +
+        " ) } ";
+
+      string query = query_template.printf (person_id, affiliation_id);
+
+      debug ("_get_affl_info: %s", query);
+
+      try
+        {
+          Sparql.Cursor cursor = this._connection.query (query);
+          while (cursor.next ())
+            {
+              affl_info.im_tracker_id = cursor.get_string
+                  (Trf.AfflInfoFields.IM_TRACKER_ID).dup ();
+              affl_info.im_proto = cursor.get_string
+                  (Trf.AfflInfoFields.IM_PROTOCOL).dup ();
+              affl_info.im_account_id = cursor.get_string
+                  (Trf.AfflInfoFields.IM_ACCOUNT_ID).dup ();
+              affl_info.affl_tracker_id = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_TRACKER_ID).dup ();
+              affl_info.title = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_ROLE).dup ();
+              affl_info.org = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_ORG).dup ();
+
+              var po_box = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_POBOX).dup ();
+              var extension = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_EXTENDED_ADDRESS).dup ();
+              var street = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_STREET_ADDRESS).dup ();
+              var locality = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_LOCALITY).dup ();
+              var region = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_REGION).dup ();
+              var postal_code = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_POSTALCODE).dup ();
+              var country = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_COUNTRY).dup ();
+
+              List<string> types = new List<string> ();
+
+              affl_info.postal_address = new Folks.PostalAddress (
+                  po_box, extension, street, locality, region, postal_code,
+                  country, null, (owned) types, affl_info.affl_tracker_id);
+
+              affl_info.email = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_EMAIL).dup ();
+              affl_info.phone = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_PHONE).dup ();
+
+              affl_info.website = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_WEBSITE).dup ();
+              affl_info.blog = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_BLOG).dup ();
+              affl_info.url = cursor.get_string
+                  (Trf.AfflInfoFields.AFFL_URL).dup ();
+            }
+        }
+      catch (Tracker.Sparql.Error e1)
+        {
+          warning ("Couldn't fetch affiliation info: %s %s",
+              query, e1.message);
+        }
+      catch (GLib.Error e2)
+        {
+          warning ("Couldn't fetch affiliation info: %s %s",
+              query, e2.message);
+        }
+
+      return affl_info;
+    }
+}
diff --git a/backends/tracker/lib/trf-persona.vala b/backends/tracker/lib/trf-persona.vala
new file mode 100644
index 0000000..47b5a2d
--- /dev/null
+++ b/backends/tracker/lib/trf-persona.vala
@@ -0,0 +1,982 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *         Travis Reitter <travis reitter collabora co uk>
+ *         Marco Barisione <marco barisione collabora co uk>
+ *         Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ */
+
+using Gee;
+using GLib;
+using Folks;
+using Tracker;
+using Tracker.Sparql;
+
+/**
+ * A persona subclass which represents a single nco:Contact.
+ */
+public class Trf.Persona : Folks.Persona,
+    AvatarDetails,
+    BirthdayDetails,
+    EmailDetails,
+    FavouriteDetails,
+    GenderDetails,
+    ImDetails,
+    NameDetails,
+    NoteDetails,
+    PhoneDetails,
+    PostalAddressDetails,
+    RoleDetails,
+    UrlDetails
+{
+  private const string[] _linkable_properties =
+      {"im-addresses", "email-addresses"};
+  private GLib.List<FieldDetails> _phone_numbers;
+  private GLib.List<FieldDetails> _email_addresses;
+  private weak Sparql.Cursor _cursor;
+  private string _tracker_id;
+
+  /**
+   * { inheritDoc}
+   */
+  public GLib.List<FieldDetails> phone_numbers
+    {
+      get { return this._phone_numbers; }
+      private set
+        {
+          this._phone_numbers = new GLib.List<FieldDetails> ();
+          foreach (unowned FieldDetails ps in value)
+            {
+              this._phone_numbers.prepend (ps);
+            }
+          this._phone_numbers.reverse ();
+        }
+    }
+
+  /**
+   * { inheritDoc}
+   */
+  public GLib.List<FieldDetails> email_addresses
+    {
+      get { return this._email_addresses; }
+      private set
+        {
+          this._email_addresses = new GLib.List<FieldDetails> ();
+          foreach (unowned FieldDetails e in value)
+            {
+              this._email_addresses.prepend (e);
+            }
+          this._email_addresses.reverse ();
+        }
+    }
+
+  /**
+   * { inheritDoc}
+   */
+  public override string[] linkable_properties
+    {
+      get { return this._linkable_properties; }
+    }
+
+  /**
+   * An avatar for the Persona.
+   *
+   * See { link Folks.Avatar.avatar}.
+   */
+  public File avatar { get; set; }
+
+  /**
+   * { inheritDoc}
+   */
+  public StructuredName structured_name { get; private set; }
+
+  /**
+   * { inheritDoc}
+   */
+  public string full_name { get; private set; }
+
+  private string _nickname;
+  /**
+   * { inheritDoc}
+   */
+  public string nickname { get { return this._nickname; } }
+
+  /**
+   * { inheritDoc}
+   */
+  public Gender gender { get; private set; }
+
+  private HashSet<Role> _roles =
+      new HashSet<Role> ((GLib.HashFunc) Role.hash,
+      (GLib.EqualFunc) Role.equal);
+
+  public DateTime birthday { get; set; }
+
+  public string calendar_event_id { get; set; }
+
+  /**
+   * { inheritDoc}
+   */
+  public HashSet<Role> roles
+    {
+      get { return this._roles; }
+      private set
+        {
+          this._roles = value;
+          this.notify_property ("roles");
+        }
+    }
+
+  private HashSet<Note> _notes =
+      new HashSet<Note> ((GLib.HashFunc) Note.hash,
+      (GLib.EqualFunc) Note.equal);
+
+  /**
+   * { inheritDoc}
+   */
+  public HashSet<Note> notes
+    {
+      get { return this._notes; }
+      private set
+        {
+          this._notes = notes;
+          this.notify_property ("notes");
+        }
+    }
+
+  private GLib.List<FieldDetails> _urls;
+  /**
+   * { inheritDoc}
+   */
+  public GLib.List<FieldDetails> urls
+    {
+      get { return this._urls; }
+      private set
+        {
+          this._urls = new GLib.List<FieldDetails> ();
+          foreach (unowned FieldDetails ps in value)
+            this._urls.prepend (ps);
+          this._urls.reverse ();
+        }
+    }
+
+  private GLib.List<PostalAddress> _postal_addresses =
+      new GLib.List<PostalAddress> ();
+  /**
+   * { inheritDoc}
+   */
+  public GLib.List<PostalAddress> postal_addresses
+    {
+      get { return this._postal_addresses; }
+      private set
+        {
+          this._postal_addresses = new GLib.List<PostalAddress> ();
+          foreach (PostalAddress pa in value)
+            this._postal_addresses.prepend (pa);
+          this._postal_addresses.reverse ();
+          this.notify_property ("postal-addresses");
+        }
+    }
+
+  private HashTable<string, HashTable<string, string>> _tracker_ids_ims =
+  new HashTable<string, HashTable<string, string>> (str_hash, str_equal);
+
+  private HashTable<string, LinkedHashSet<string>> _im_addresses =
+      new HashTable<string, LinkedHashSet<string>> (str_hash, str_equal);
+  /**
+   * { inheritDoc}
+   */
+  public HashTable<string, LinkedHashSet<string>> im_addresses
+    {
+      get { return this._im_addresses; }
+      private set {}
+    }
+
+  /**
+   * Whether this contact is a user-defined favourite.
+   */
+  public bool is_favourite { get; set; }
+
+  /**
+   * Build a IID.
+   *
+   * @param store_id the { link PersonaStore.id}
+   * @param tracker_id the tracker id belonging to nco:PersonContact
+   * @return a valid IID
+   *
+   * @since UNRELEASED
+   */
+  internal static string build_iid (string store_id, string tracker_id)
+    {
+      return "%s:%s".printf (store_id, tracker_id);
+    }
+
+  /**
+   * Create a new persona.
+   *
+   * Create a new persona for the { link PersonaStore} `store`, representing
+   * the nco:Contact whose details are stored in the cursor.
+   */
+  public Persona (PersonaStore store, string tracker_id,
+                  Sparql.Cursor? cursor = null)
+    {
+      string uid = this.build_uid (BACKEND_NAME, store.id, tracker_id);
+      string iid = this.build_iid (store.id, tracker_id);
+      bool is_user = false;
+      string fullname = "";
+
+      if (cursor != null)
+        {
+          fullname = cursor.get_string (Trf.Fields.FULL_NAME).dup ();
+          var contact_urn = cursor.get_string (Trf.Fields.CONTACT_URN);
+          if (contact_urn == Trf.OntologyDefs.DEFAULT_CONTACT_URN)
+            {
+              is_user = true;
+            }
+        }
+
+      debug ("Creating new Trf.Persona with id '%s'", fullname);
+
+      Object (display_id: fullname,
+              uid: uid,
+              iid: iid,
+              store: store,
+              gender: Gender.UNSPECIFIED,
+              is_user: is_user);
+
+      this.full_name = fullname;
+      this._tracker_id = tracker_id;
+      this.structured_name = new StructuredName (null, null, null, null, null);
+
+      if (cursor != null)
+        {
+          this._cursor = cursor;
+          this._update ();
+        }
+    }
+
+  ~Persona ()
+    {
+      debug ("Destroying Trf.Persona '%s': %p", this.uid, this);
+    }
+
+  internal void _update_full_name (string? fn)
+    {
+      if (fn != null && this.full_name != fn)
+        {
+          this.full_name = fn;
+        }
+    }
+
+  internal void _update_nickname (string? nickname)
+    {
+      if (nickname != null && this._nickname != nickname)
+        {
+          this._nickname = nickname;
+          this.notify_property ("nickname");
+        }
+    }
+
+  internal void _update_family_name (string? family_name)
+    {
+      if (family_name != null)
+        {
+          this.structured_name.family_name = family_name;
+        }
+    }
+
+  internal void _update_given_name (string? given_name)
+    {
+      if (given_name != null)
+        {
+          this.structured_name.given_name = given_name;
+        }
+    }
+
+  internal void _update_additional_names (string? additional_names)
+    {
+      if (additional_names != null)
+        {
+          this.structured_name.additional_names = additional_names;
+        }
+    }
+
+  internal void _update_prefixes (string? prefixes)
+    {
+      if (prefixes != null)
+        {
+          this.structured_name.prefixes = prefixes;
+        }
+    }
+
+  internal void _update_suffixes (string? suffixes)
+    {
+      if (suffixes != null)
+        {
+          this.structured_name.suffixes = suffixes;
+        }
+    }
+
+  internal void _update ()
+    {
+      this._update_names ();
+      this._update_avatar ();
+      this._update_im_addresses ();
+      this._update_phones ();
+      this._update_email_addresses ();
+      this._update_urls ();
+      this._update_favourite ();
+      this._update_roles ();
+      this._update_bday ();
+      this._update_note ();
+      this._update_gender ();
+      this._update_postal_addresses ();
+    }
+
+  private void _update_postal_addresses ()
+    {
+      string postal_field = this._cursor.get_string
+          (Trf.Fields.POSTAL_ADDRESS).dup ();
+
+      if (postal_field == null)
+        {
+          return;
+        }
+
+      GLib.List<PostalAddress> postal_addresses =
+          new GLib.List<PostalAddress> ();
+
+      string[] addresses_a = postal_field.split ("\n");
+
+      foreach (var a in addresses_a)
+        {
+          bool address_empty = true;
+          string[] a_info = a.split ("\t");
+          for (int i = 0; i < a_info.length; i++)
+            {
+              if (a_info[i] != null && a_info[i] != "")
+                {
+                  address_empty = false;
+                  break;
+                }
+            }
+
+          if (address_empty)
+            continue;
+
+          GLib.List<string> types = new GLib.List<string> ();
+
+          var pa = new PostalAddress (a_info[Trf.PostalAddressFields.POBOX],
+              a_info[Trf.PostalAddressFields.EXTENDED_ADDRESS],
+              a_info[Trf.PostalAddressFields.STREET_ADDRESS],
+              a_info[Trf.PostalAddressFields.LOCALITY],
+              a_info[Trf.PostalAddressFields.REGION],
+              a_info[Trf.PostalAddressFields.POSTALCODE],
+              a_info[Trf.PostalAddressFields.COUNTRY],
+              null, (owned) types,
+              a_info[Trf.PostalAddressFields.TRACKER_ID]);
+
+          postal_addresses.prepend ((owned) pa);
+        }
+
+      postal_addresses.reverse ();
+      this.postal_addresses = (owned) postal_addresses;
+    }
+
+  internal bool _add_postal_address (PostalAddress postal_address)
+    {
+      foreach (unowned PostalAddress pa in this._postal_addresses)
+        {
+          if (postal_address.equal (pa))
+            {
+              return false;
+            }
+        }
+
+      this._postal_addresses.append (postal_address);
+      this.notify_property ("postal-addresses");
+      return true;
+    }
+
+  internal bool _remove_postal_address (string tracker_id)
+    {
+      foreach (unowned PostalAddress pa in this._postal_addresses)
+        {
+          if (pa.uid == tracker_id)
+            {
+              this._postal_addresses.remove (pa);
+              this.notify_property ("postal-addresses");
+              return true;
+            }
+        }
+      return false;
+    }
+
+  private void _update_gender ()
+    {
+      string gender = this._cursor.get_string (Trf.Fields.GENDER).dup ();
+      int gender_id = 0;
+
+      if (gender != null)
+        {
+          gender_id = int.parse (gender);
+        }
+
+      this._set_gender (gender_id);
+    }
+
+  internal void _set_gender (int gender_id)
+    {
+      if (gender_id == 0)
+        {
+          this.gender = Gender.UNSPECIFIED;
+        }
+      else
+        {
+          var trf_store = (Trf.PersonaStore) this.store;
+
+          if (gender_id == trf_store.get_gender_male_id ())
+            {
+              this.gender = Gender.MALE;
+            }
+          else if (gender_id == trf_store.get_gender_female_id ())
+            {
+              this.gender = Gender.FEMALE;
+            }
+        }
+    }
+
+  private void _update_note ()
+    {
+      string note = this._cursor.get_string (Trf.Fields.NOTE).dup ();
+      this._set_note (note);
+    }
+
+  internal void _set_note (string? note_content)
+    {
+      if (note_content != null)
+        {
+          var note = new Note (note_content);
+          this._notes.add ((owned) note);
+        }
+      else
+        {
+          this._notes.clear ();
+        }
+      this.notify_property ("notes");
+    }
+
+  private void _update_bday ()
+    {
+      string bday = this._cursor.get_string (Trf.Fields.BIRTHDAY).dup ();
+      this._set_birthday (bday);
+    }
+
+  internal void _set_birthday (string? birthday)
+    {
+      if (birthday != null && birthday != "")
+        {
+          TimeVal t = TimeVal ();
+          t.from_iso8601 (birthday);
+          this.birthday = new DateTime.from_timeval_utc (t);
+        }
+      else
+        {
+          if (this.birthday != null)
+            {
+              this.birthday = null;
+            }
+        }
+    }
+
+  private void _update_roles ()
+    {
+      string roles_field = this._cursor.get_string (
+          Trf.Fields.ROLES).dup ();
+
+      if (roles_field == null)
+        {
+          return;
+        }
+
+      HashSet<Role> roles = new HashSet<Role> (
+          (GLib.HashFunc) Role.hash,
+          (GLib.EqualFunc) Role.equal);
+
+      string[] roles_a = roles_field.split ("\n");
+
+      foreach (var r in roles_a)
+        {
+          string[] r_info = r.split ("\t");
+          var tracker_id = r_info[Trf.RoleFields.TRACKER_ID];
+          var title = r_info[Trf.RoleFields.ROLE];
+          var organisation = r_info[Trf.RoleFields.DEPARTMENT];
+
+          var role = new Role (title, organisation, tracker_id);
+          roles.add (role);
+        }
+
+      this._roles = roles;
+    }
+
+  internal bool _add_role (string tracker_id, string? title, string? org)
+    {
+      var role = new Role (title, org, tracker_id);
+      if (this._roles.add (role))
+        {
+          this.notify_property ("roles");
+          return true;
+        }
+      return false;
+    }
+
+  internal bool _remove_role (string tracker_id)
+    {
+      foreach (var r in this._roles)
+        {
+          if (r.uid == tracker_id)
+            {
+              this._roles.remove (r);
+              this.notify_property ("roles");
+              return true;
+            }
+        }
+
+      return false;
+    }
+
+  private void _update_names ()
+    {
+      string fullname = this._cursor.get_string (Trf.Fields.FULL_NAME).dup ();
+      this._update_full_name (fullname);
+
+      string nickname = this._cursor.get_string (Trf.Fields.NICKNAME).dup ();
+      this._update_nickname (nickname);
+
+      string family_name = this._cursor.get_string (
+          Trf.Fields.FAMILY_NAME).dup ();
+      this._update_family_name (family_name);
+
+      string given_name  = this._cursor.get_string (
+          Trf.Fields.GIVEN_NAME).dup ();
+      this._update_given_name (given_name);
+
+      string additional_names = this._cursor.get_string (
+          Trf.Fields.ADDITIONAL_NAMES).dup ();
+      this._update_additional_names (additional_names);
+
+      string prefixes = this._cursor.get_string (Trf.Fields.PREFIXES).dup ();
+      this._update_prefixes (prefixes);
+
+      string suffixes = this._cursor.get_string (Trf.Fields.SUFFIXES).dup ();
+      this._update_suffixes (suffixes);
+    }
+
+  private void _update_avatar ()
+    {
+      string avatar_url = this._cursor.get_string (
+          Trf.Fields.AVATAR_URL).dup ();
+      this._set_avatar (avatar_url);
+    }
+
+  internal bool _set_avatar (string? avatar_url)
+    {
+      File _avatar = null;
+      if (avatar_url != null && avatar_url != "")
+        {
+          _avatar = File.new_for_uri (avatar_url);
+        }
+      this.avatar = _avatar;
+      return true;
+    }
+
+  private void _update_im_addresses ()
+    {
+      string addresses = this._cursor.get_string (
+          Trf.Fields.IM_ADDRESSES).dup ();
+
+      if (addresses == null)
+        {
+          return;
+        }
+
+      this._im_addresses.remove_all ();
+
+      string[] addresses_a = addresses.split ("\n");
+
+      foreach (var addr in addresses_a)
+        {
+          string[] addr_info = addr.split ("\t");
+          var tracker_id = addr_info[Trf.IMFields.TRACKER_ID];
+          var proto = addr_info[Trf.IMFields.PROTO];
+          var account_id = addr_info[Trf.IMFields.ID];
+
+          this._add_im_address (tracker_id, proto, account_id, false);
+        }
+
+      this.notify_property ("im-addresses");
+    }
+
+  internal bool _add_im_address (string tracker_id, string im_proto,
+      string account_id, bool notify = true)
+    {
+      LinkedHashSet<string> im_address_array;
+
+      try
+        {
+          var account_id_copy = account_id.dup ();
+          var normalised_addr = (owned) normalise_im_address
+              ((owned) account_id_copy, im_proto);
+
+          im_address_array = this._im_addresses.lookup (im_proto);
+          if (im_address_array == null)
+            {
+              im_address_array = new LinkedHashSet<string> ();
+              im_address_array.add ((owned) normalised_addr);
+              var proto_copy = im_proto.dup ();
+              this._im_addresses.insert ((owned) proto_copy,
+                  (owned) im_address_array);
+            }
+          else
+            {
+              im_address_array.add (normalised_addr);
+            }
+
+          var im_proto_hash = new HashTable<string, string> (str_hash,
+              str_equal);
+          var proto_copy_2 = im_proto.dup ();
+          var account_id_copy_2 = account_id.dup ();
+          im_proto_hash.insert ((owned) proto_copy_2,
+              (owned) account_id_copy_2);
+          var tracker_id_copy = tracker_id.dup ();
+          this._tracker_ids_ims.insert ((owned) tracker_id_copy,
+                  (owned) im_proto_hash);
+
+          if (notify)
+            {
+              this.notify_property ("im-addresses");
+            }
+        }
+      catch (Folks.ImDetailsError e)
+        {
+          GLib.warning (
+              "Problem when trying to normalise address: %s\n",
+              e.message);
+        }
+
+      return true;
+    }
+
+  internal bool _remove_im_address (string tracker_id, bool notify = true)
+    {
+      var proto_im = this._tracker_ids_ims.lookup (tracker_id);
+
+      if (proto_im == null)
+        return false;
+
+      var proto = proto_im.get_keys ().nth_data (0);
+      var im_addr = proto_im.lookup (proto);
+
+      var im_list = this._im_addresses.lookup (proto);
+      if (im_list != null)
+        {
+          foreach (var addr_iter in im_list)
+            {
+              if (addr_iter == im_addr)
+                {
+                  im_list.remove (im_addr);
+                  this._tracker_ids_ims.remove (tracker_id);
+                  if (notify)
+                    {
+                      this.notify_property ("im-addresses");
+                    }
+                  return true;
+                }
+            }
+        }
+
+      return false;
+    }
+
+  private void _update_phones ()
+    {
+      string phones_field = this._cursor.get_string (Trf.Fields.PHONES).dup ();
+
+      if (phones_field == null)
+        {
+          return;
+        }
+
+      var phones = new GLib.List<FieldDetails> ();
+      string[] phones_a = phones_field.split ("\n");
+
+      foreach (var p in phones_a)
+        {
+          if (p != null && p != "")
+            {
+              string[] p_info = p.split ("\t");
+              var fd = new FieldDetails (p_info[Trf.PhoneFields.PHONE]);
+              fd.set_parameter ("tracker_id",
+                  p_info[Trf.PhoneFields.TRACKER_ID]);
+              phones.prepend ((owned) fd);
+            }
+        }
+
+      phones.reverse ();
+      this.phone_numbers = phones;
+    }
+
+  internal bool _add_phone (string phone, string tracker_id)
+    {
+      bool found = false;
+
+      foreach (var p in this._phone_numbers)
+        {
+          var current_tracker_id =
+              p.get_parameter_values ("tracker_id").nth_data (0);
+          if (tracker_id == current_tracker_id)
+            {
+              found = true;
+              break;
+            }
+        }
+
+      if (!found)
+        {
+          var fd = new FieldDetails (phone);
+          fd.set_parameter ("tracker_id", tracker_id);
+          this._phone_numbers.prepend ((owned) fd);
+          this.notify_property ("phone-numbers");
+        }
+
+      return !found;
+    }
+
+  internal bool _remove_phone (string tracker_id)
+    {
+      bool found = false;
+
+      foreach (var p in this._phone_numbers)
+        {
+          var current_tracker_id = p.get_parameter_values
+              ("tracker_id").nth_data(0);
+          if (tracker_id == current_tracker_id)
+            {
+              this._phone_numbers.remove (p);
+              found = true;
+              break;
+            }
+        }
+
+      if (found)
+       {
+         this.notify_property ("phone-numbers");
+       }
+
+      return found;
+    }
+
+  internal bool _add_email (string addr, string tracker_id)
+    {
+      bool found = false;
+
+      foreach (var e in this._email_addresses)
+        {
+          var current_tracker_id =
+              e.get_parameter_values ("tracker_id").nth_data (0);
+          if (tracker_id == current_tracker_id)
+            {
+              found = true;
+              break;
+            }
+        }
+
+      if (!found)
+        {
+          var fd = new FieldDetails (addr);
+          fd.set_parameter ("tracker_id", tracker_id);
+          this._email_addresses.prepend ((owned) fd);
+          this.notify_property ("email-addresses");
+        }
+
+      return !found;
+    }
+
+  internal bool _remove_email (string tracker_id)
+    {
+      bool found = false;
+
+      foreach (var e in this._email_addresses)
+        {
+          var current_tracker_id =
+              e.get_parameter_values ("tracker_id").nth_data(0);
+          if (tracker_id == current_tracker_id)
+            {
+              this._email_addresses.remove (e);
+              found = true;
+              break;
+            }
+        }
+
+      if (found)
+       {
+         this.notify_property ("email-addresses");
+       }
+
+      return found;
+    }
+
+  private void _update_email_addresses ()
+    {
+      string emails_field = this._cursor.get_string (Trf.Fields.EMAILS).dup ();
+
+      if (emails_field == null)
+        {
+          return;
+        }
+
+      var email_addresses = new GLib.List<FieldDetails> ();
+      string[] emails_a = emails_field.split (",");
+
+      foreach (var e in emails_a)
+        {
+          if (e != null && e != "")
+            {
+              string[] id_addr = e.split ("\t");
+              var fd = new FieldDetails (id_addr[Trf.EmailFields.EMAIL]);
+              fd.set_parameter ("tracker_id",
+                  id_addr[Trf.EmailFields.TRACKER_ID]);
+              email_addresses.prepend ((owned) fd);
+            }
+        }
+
+      email_addresses.reverse ();
+      this.email_addresses = email_addresses;
+    }
+
+  private void _update_urls ()
+    {
+      var urls = new GLib.List<FieldDetails> ();
+      var _urls_field = this._cursor.get_string (Trf.Fields.URLS).dup ();
+
+      if (_urls_field == null)
+        return;
+
+      string[] urls_table = _urls_field.split ("\n");
+
+      foreach (var row in urls_table)
+        {
+          string[] u = row.split ("\t");
+          var tracker_id = u[Trf.UrlsFields.TRACKER_ID];
+
+          for (int i=1; i< u.length; i++)
+            {
+              if (u[i] == null || u[i] == "")
+                continue;
+
+              string type = "";
+              switch (i)
+                {
+                  case Trf.UrlsFields.BLOG:
+                    type = "blog";
+                    break;
+                  case Trf.UrlsFields.WEBSITE:
+                    type = "website";
+                    break;
+                  case Trf.UrlsFields.URL:
+                    type = "url";
+                    break;
+                }
+
+              var fd = new FieldDetails (u[i]);
+              fd.set_parameter ("tracker_id", tracker_id);
+              fd.set_parameter ("type", type);
+              urls.prepend ((owned) fd);
+            }
+        }
+
+      urls.reverse ();
+      this.urls = urls;
+    }
+
+  internal bool _add_url (string url, string tracker_id, string type = "")
+    {
+      bool found = false;
+
+      foreach (var p in this._urls)
+        {
+          var t_id = p.get_parameter_values ("tracker_id").nth_data (0);
+          if (tracker_id == t_id)
+            {
+              found = true;
+              break;
+            }
+        }
+
+      if (!found)
+        {
+          var fd = new FieldDetails (url);
+          fd.set_parameter ("tracker_id", tracker_id);
+          fd.set_parameter ("type", type);
+          this._urls.prepend ((owned) fd);
+          this.notify_property ("urls");
+        }
+
+      return !found;
+    }
+
+  internal bool _remove_url (string tracker_id)
+    {
+      bool found = false;
+
+      foreach (var u in this._urls)
+        {
+          var current_tracker_id = u.get_parameter_values
+              ("tracker_id").nth_data(0);
+          if (tracker_id == current_tracker_id)
+            {
+              this._urls.remove (u);
+              found = true;
+            }
+        }
+
+      if (found)
+        this.notify_property ("urls");
+
+      return found;
+    }
+
+  private void _update_favourite ()
+    {
+      var favourite = this._cursor.get_string (Trf.Fields.FAVOURITE).dup ();
+
+      this.is_favourite = false;
+
+      if (favourite != null)
+        {
+          var trf_store = (Trf.PersonaStore) this.store;
+          int favorite_tracker_id = trf_store.get_favorite_id ();
+          foreach (var tag in favourite.split (","))
+            {
+              if (int.parse (tag) == favorite_tracker_id)
+                {
+                  this.is_favourite = true;
+                }
+            }
+        }
+    }
+}
diff --git a/backends/tracker/lib/trf-util.vala b/backends/tracker/lib/trf-util.vala
new file mode 100644
index 0000000..5e93687
--- /dev/null
+++ b/backends/tracker/lib/trf-util.vala
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ */
+
+
+using Gee;
+using Tracker;
+using Tracker.Sparql;
+
+public struct Event
+{
+  int graph_id;
+  int subject_id;
+  int pred_id;
+  int object_id;
+}
+
+[DBus (name = "org.freedesktop.Tracker1.Resources")]
+private interface Resources : DBusProxy {
+  [DBus (name = "GraphUpdated")]
+  public signal void graph_updated
+      (string class_name, Event[] deletes, Event[] inserts);
+}
+
+internal class Trf.AfflInfo : Object
+{
+  public string im_tracker_id { get; set; }
+
+  public string im_proto { get; set; }
+
+  public string im_account_id { get; set; }
+
+  public string affl_tracker_id  { get; set; }
+
+  public string title { get; set; }
+
+  public string org  { get; set; }
+
+  public Folks.PostalAddress postal_address;
+
+  public string email { get; set; }
+
+  public string phone  { get; set; }
+
+  public string website  { get; set; }
+
+  public string blog  { get; set; }
+
+  public string url  { get; set; }
+
+  public string to_string ()
+    {
+      string ret = " { ";
+      bool first = true;
+      unowned ParamSpec[] properties = this.get_class ().list_properties ();
+
+      foreach (unowned ParamSpec pspec in properties)
+        {
+          var property = pspec.get_name ();
+          var prop_value = Value (pspec.value_type);
+          this.get_property (property, ref prop_value);
+          string value = prop_value.get_string ();
+
+          if (first == false)
+            ret += ", ";
+
+          ret += "%s : %s".printf (property, value);
+          first = false;
+       }
+
+      ret += " } ";
+
+      return ret;
+    }
+}
+
+public class Trf.OntologyDefs : Object
+{
+  public static const string DEFAULT_CONTACT_URN =
+  "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#default-contact-me";;
+  public static const string PERSON_CLASS =
+  "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#PersonContact";;
+  public static const string NCO_FULLNAME = "nco:fullname";
+  public static const string NCO_FAMILY = "nco:nameFamily";
+  public static const string NCO_GIVEN = "nco:nameGiven";
+  public static const string NCO_ADDITIONAL = "nco:nameAdditional";
+  public static const string NCO_SUFFIX = "nco:nameHonorificSuffix";
+  public static const string NCO_PREFIX = "nco:nameHonorificPrefix";
+  public static const string NCO_NICKNAME = "nco:nickname";
+  public static const string RDF_TYPE = "ns:type";
+  public static const string NCO_PERSON = "nco:PersonContact";
+  public static const string NCO_WEBSITE = "nco:websiteUrl";
+  public static const string NCO_BLOG = "nco:blogUrl";
+  public static const string NAO_FAVORITE = "nao:predefined-tag-favorite";
+  public static const string NAO_TAG = "nao:hasTag";
+  public static const string NCO_HAS_EMAIL = "nco:hasEmailAddress";
+  public static const string NCO_EMAIL = "nco:EmailAddress";
+  public static const string NCO_EMAIL_PROP = "nco:emailAddress";
+  public static const string NCO_HAS_PHONE = "nco:hasPhoneNumber";
+  public static const string NCO_PHONE = "nco:PhoneNumber";
+  public static const string NCO_PHONE_PROP = "nco:phoneNumber";
+  public static const string NCO_HAS_AFFILIATION = "nco:hasAffiliation";
+  public static const string NCO_AFFILIATION = "nco:Affiliation";
+  public static const string NCO_BIRTHDAY = "nco:birthDate";
+  public static const string NCO_NOTE = "nco:note";
+  public static const string NCO_GENDER = "nco:gender";
+  public static const string NCO_MALE = "nco:gender-male";
+  public static const string NCO_FEMALE = "nco:gender-female";
+  public static const string NCO_PHOTO = "nco:photo";
+  public static const string NIE_URL = "nie:url";
+  public static const string NFO_IMAGE = "nfo:Image";
+  public static const string NCO_IMADDRESS = "nco:IMAddress";
+  public static const string NCO_HAS_IMADDRESS = "nco:hasIMAddress";
+  public static const string NCO_IMPROTOCOL = "nco:imProtocol";
+  public static const string NCO_IMID = "nco:imID";
+  public static const string NCO_POSTAL_ADDRESS = "nco:PostalAddress";
+  public static const string NCO_URL_PREFIX =
+      "<http://www.semanticdesktop.org/ontologies/2007/03/22/";;
+  public static const string NAO_URL_PREFIX =
+      "<http://www.semanticdesktop.org/ontologies/2007/08/15/";;
+  public static const string RDF_URL_PREFIX =
+      "<http://www.w3.org/1999/02/";;
+}
diff --git a/backends/tracker/tr-backend-factory.vala b/backends/tracker/tr-backend-factory.vala
new file mode 100644
index 0000000..c3ff71e
--- /dev/null
+++ b/backends/tracker/tr-backend-factory.vala
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2009 Zeeshan Ali (Khattak) <zeeshanak gnome org>.
+ * Copyright (C) 2009 Nokia Corporation.
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *          Travis Reitter <travis reitter collabora co uk>
+ *          Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ * This file was originally part of Rygel.
+ */
+
+using Folks;
+using Folks.Backends.Tr;
+
+private BackendFactory _backend_factory = null;
+
+/**
+ * The tracker backend module entry point.
+ */
+public void module_init (BackendStore backend_store)
+{
+  _backend_factory = new BackendFactory (backend_store);
+}
+
+/**
+ * The tracker backend module exit point.
+ */
+public void module_finalize (BackendStore backend_store)
+{
+  _backend_factory = null;
+}
+
+/**
+ * A backend factory to create a single { link Backend}.
+ */
+public class Folks.Backends.Tr.BackendFactory : Object
+{
+  /**
+   * { inheritDoc}
+   */
+  public BackendFactory (BackendStore backend_store)
+    {
+      backend_store.add_backend (new Backend ());
+    }
+}
diff --git a/backends/tracker/tr-backend.vala b/backends/tracker/tr-backend.vala
new file mode 100644
index 0000000..ef98afb
--- /dev/null
+++ b/backends/tracker/tr-backend.vala
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Travis Reitter <travis reitter collabora co uk>
+ *       Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ */
+
+using Folks;
+using Folks.Backends.Tr;
+using GLib;
+
+extern const string BACKEND_NAME;
+
+/**
+ * A backend which connects to Tracker and creates a { link PersonaStore}
+ * for each service.
+ */
+public class Folks.Backends.Tr.Backend : Folks.Backend
+{
+  private bool _is_prepared = false;
+  private HashTable<string, PersonaStore> _persona_stores;
+
+  /**
+   * { inheritDoc}
+   */
+  public override string name { get { return BACKEND_NAME; } }
+
+  /**
+   * { inheritDoc}
+   */
+  public override HashTable<string, PersonaStore> persona_stores
+    {
+      get { return this._persona_stores; }
+    }
+
+  /**
+   * { inheritDoc}
+   */
+  public Backend ()
+    {
+      this._persona_stores = new HashTable<string, PersonaStore>
+          (str_hash, str_equal);
+    }
+
+  /**
+   * Whether this Backend has been prepared.
+   *
+   * See { link Folks.Backend.is_prepared}.
+   */
+  public override bool is_prepared
+    {
+      get { return this._is_prepared; }
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   */
+  public override async void prepare () throws GLib.Error
+    {
+      lock (this._is_prepared)
+        {
+          if (!this._is_prepared)
+            {
+              this._add_default_persona_store ();
+              this._is_prepared = true;
+              this.notify_property ("is-prepared");
+            }
+        }
+    }
+
+  /**
+   * { inheritDoc}
+   */
+  public override async void unprepare () throws GLib.Error
+    {
+      this._persona_stores.foreach ((k, v) =>
+        {
+          this.persona_store_removed ((PersonaStore) v);
+        });
+
+      this._persona_stores.remove_all ();
+      this.notify_property ("persona-stores");
+
+      this._is_prepared = false;
+      this.notify_property ("is-prepared");
+    }
+
+  /**
+   * Add a the default Persona Store.
+   */
+  private void _add_default_persona_store ()
+    {
+      var store = new Trf.PersonaStore ();
+      this._persona_stores.insert (store.id, store);
+      store.removed.connect (this._store_removed_cb);
+      this.notify_property ("persona-stores");
+      this.persona_store_added (store);
+    }
+
+  private void _store_removed_cb (Folks.PersonaStore store)
+    {
+      this.persona_store_removed (store);
+      this.persona_stores.remove (store.id);
+    }
+}
diff --git a/configure.ac b/configure.ac
index 9f68dc1..15c4def 100644
--- a/configure.ac
+++ b/configure.ac
@@ -66,6 +66,14 @@ AC_SUBST([LT_AGE])
 FOLKS_MAJOR_MINOR_VERSION=folks_major_minor_version
 AC_SUBST([FOLKS_MAJOR_MINOR_VERSION])
 
+AC_ARG_ENABLE(tracker-backend,
+        AC_HELP_STRING([--enable-tracker-backend],
+                       [ build the Tracker backend]),
+        enable_tracker_backend=$enableval,
+        enable_tracker_backend=no )
+
+AM_CONDITIONAL([ENABLE_TRACKER], [test "x$enable_tracker_backend" = "xyes"])
+
 # -----------------------------------------------------------
 # Dependencies
 # -----------------------------------------------------------
@@ -74,6 +82,7 @@ GLIB_REQUIRED=2.24.0
 TP_GLIB_REQUIRED=0.13.1
 VALA_REQUIRED=0.11.6
 VALADOC_REQUIRED=0.2.1
+TRACKER_SPARQL_REQUIRED=0.10
 
 PKG_CHECK_MODULES([GLIB],
                   [glib-2.0 >= $GLIB_REQUIRED
@@ -86,6 +95,11 @@ PKG_CHECK_MODULES([DBUS_GLIB], [dbus-glib-1])
 PKG_CHECK_MODULES([GEE], [gee-1.0 < 0.7])
 PKG_CHECK_MODULES([TP_GLIB], [telepathy-glib >= $TP_GLIB_REQUIRED])
 
+if test x$enable_tracker_backend = xyes; then
+        PKG_CHECK_MODULES([TRACKER_SPARQL],
+                          [tracker-sparql-0.10 >= $TRACKER_SPARQL_REQUIRED])
+fi
+
 #
 # Vala building options -- allows tarball builds without installing Vala
 #
@@ -160,6 +174,10 @@ if test "x$enable_vala" = "xyes" ; then
                              gio-2.0
                              gee-1.0])
 
+        if test x$enable_tracker_backend = xyes; then
+          VALA_CHECK_PACKAGES([tracker-sparql-0.10])
+        fi
+
         # this will set HAVE_INTROSPECTION
         GOBJECT_INTROSPECTION_REQUIRE([0.9.12])
 fi
@@ -186,8 +204,17 @@ AC_SUBST([BACKEND_KF])
 BACKEND_TP='$(top_builddir)/backends/telepathy/.libs/libfolks-backend-telepathy.so'
 AC_SUBST([BACKEND_TP])
 
+if test x$enable_tracker_backend = xyes; then
+  BACKEND_TRACKER='$(top_builddir)/backends/tracker/.libs/libfolks-backend-tracker.so'
+  AC_SUBST([BACKEND_TRACKER])
+fi
+
 # All of the backend libraries in our tree; to be used by the tests
 BACKEND_UNINST_PATH='$(BACKEND_KF):$(BACKEND_TP)'
+if test x$enable_tracker_backend = xyes; then
+  TRACKER_BACKEND_UNINST_PATH='$(BACKEND_TRACKER)'
+  BACKEND_UNINST_PATH="$BACKEND_UNINST_PATH:$TRACKER_BACKEND_UNINST_PATH"
+fi
 AC_SUBST([BACKEND_UNINST_PATH])
 
 # -----------------------------------------------------------
@@ -348,6 +375,17 @@ AC_CONFIG_FILES([
     tools/inspect/Makefile
 ])
 
+if test x$enable_tracker_backend = xyes; then
+AC_CONFIG_FILES([
+    backends/tracker/lib/folks-tracker.pc
+    backends/tracker/lib/folks-tracker-uninstalled.pc
+    backends/tracker/Makefile
+    backends/tracker/lib/Makefile
+    tests/tracker/Makefile
+    tests/lib/tracker/Makefile
+])
+fi
+
 AC_OUTPUT
 
 echo "
@@ -362,6 +400,7 @@ Configure summary:
         Tests.......................:  ${have_tp_glib_for_tests}
         Import tool.................:  ${with_import_tool}
         Inspector tool..............:  ${with_inspect_tool}
+        Tracker backend.............:  ${enable_tracker_backend}
 
 
 "
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 76d6432..c10d4c5 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -7,6 +7,23 @@ SUBDIRS = \
     telepathy \
     $(NULL)
 
+if ENABLE_TRACKER
+SUBDIRS += tracker
+endif
+
+DIST_SUBDIRS = \
+    data \
+    lib \
+    tools \
+    folks \
+    key-file \
+    telepathy \
+    $(NULL)
+
+if ENABLE_TRACKER
+DIST_SUBDIRS += tracker
+endif
+
 TESTS_ENVIRONMENT = \
     abs_top_builddir= abs_top_builddir@ \
     abs_top_srcdir= abs_top_srcdir@ \
diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am
index 8f633b7..497f3bb 100644
--- a/tests/lib/Makefile.am
+++ b/tests/lib/Makefile.am
@@ -3,6 +3,19 @@ SUBDIRS = \
 	telepathy \
 	$(NULL)
 
+if ENABLE_TRACKER
+SUBDIRS += tracker
+endif
+
+DIST_SUBDIRS = \
+	key-file \
+	telepathy \
+	$(NULL)
+
+if ENABLE_TRACKER
+DIST_SUBDIRS += tracker
+endif
+
 noinst_LTLIBRARIES = libfolks-test.la
 
 libfolks_test_la_SOURCES = test-case.vala
diff --git a/tests/lib/tracker/Makefile.am b/tests/lib/tracker/Makefile.am
new file mode 100644
index 0000000..31cda54
--- /dev/null
+++ b/tests/lib/tracker/Makefile.am
@@ -0,0 +1,51 @@
+VALAFLAGS += \
+	--library=tracker-test \
+	--header=tracker-test.h \
+	--vapidir=$(top_srcdir)/folks \
+	--vapidir=$(top_srcdir)/backends/tracker/lib \
+	--vapidir=. \
+	--pkg gobject-2.0 \
+	--pkg gio-2.0 \
+	--pkg gee-1.0 \
+	--pkg folks \
+	--pkg tracker-sparql-0.10 \
+	--pkg folks-tracker \
+	$(NULL)
+
+noinst_LTLIBRARIES = libtracker-test.la
+
+libtracker_test_la_SOURCES = \
+	backend.vala \
+	$(NULL)
+
+libtracker_test_la_CFLAGS = \
+	$(GLIB_CFLAGS) \
+	$(GEE_CFLAGS) \
+	$(TRACKER_SPARQL_CFLAGS) \
+	-I$(top_srcdir)/folks \
+	-I$(top_srcdir)/backends/tracker/lib \
+	$(NULL)
+
+libtracker_test_la_LIBADD = \
+	$(GLIB_LIBS) \
+	$(GEE_LIBS) \
+	$(top_builddir)/folks/libfolks.la \
+	$(top_builddir)/backends/tracker/libfolks-backend-tracker.la \
+	$(NULL)
+
+MAINTAINERCLEANFILES = \
+	tracker-test.vapi \
+	tracker-test.h \
+	$(NULL)
+
+EXTRA_DIST = \
+	tracker-test.vapi \
+	tracker-test.h \
+	$(NULL)
+
+GITIGNOREFILES = \
+	$(libtracker_test_la_SOURCES:.vala=.c) \
+	libtracker_test_la_vala.stamp \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/tests/lib/tracker/backend.vala b/tests/lib/tracker/backend.vala
new file mode 100644
index 0000000..01a6394
--- /dev/null
+++ b/tests/lib/tracker/backend.vala
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors :Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker;
+using Tracker.Sparql;
+
+errordomain TrackerTest.BackendSetupError
+{
+  ADD_CONTACT_FAILED,
+}
+
+public class TrackerTest.Backend
+{
+  public static const string URN = "urn:contact";
+  public static const string URLS = "nco:urls";
+  public bool debug { get; set; }
+  private GLib.List<Gee.HashMap<string, string>> _contacts;
+  private Tracker.Sparql.Connection _connection;
+
+
+  public Backend ()
+    {
+      this.debug = false;
+      this._contacts = new GLib.List<Gee.HashMap<string, string>> ();
+   }
+
+  public void add_contact (Gee.HashMap<string, string> c)
+    {
+      var contact = this._copy_hash_map (c);
+      this._contacts.prepend (contact);
+    }
+
+  /* Remove contacts */
+  public void tear_down ()
+    {
+
+    }
+
+  public void reset ()
+    {
+      this._contacts = new GLib.List<Gee.HashMap<string, string>> ();
+    }
+
+  /* Insert contacts */
+  public void set_up ()
+    {
+      try
+        {
+          this._setup_connection ();
+          this._add_contacts ();
+        }
+      catch (BackendSetupError e)
+        {
+          GLib.warning ("unable to create test data: %s\n", e.message);
+        }
+    }
+
+  public bool update_contact (string contact_urn, string predicate,
+                              string literal_subject)
+    {
+      const string delete_query_t = "DELETE { %s %s ?a } WHERE " +
+          "{ ?p a nco:PersonContact " +
+          " ; %s ?a . FILTER(?p = %s ) } ";
+      const string update_query_t = "INSERT { %s %s '%s' } ";
+
+      string delete_query = delete_query_t.printf (contact_urn, predicate,
+          predicate, contact_urn);
+      if (this._do_update_query (delete_query) == false)
+        {
+          GLib.warning ("Couldn't delete the old triplet");
+          return false;
+        }
+
+      string update_query = update_query_t.printf (contact_urn, predicate,
+          literal_subject);
+      if (this._do_update_query (update_query) == false)
+        {
+          GLib.warning ("Couldn't insert the triplet");
+          return false;
+        }
+
+      return true;
+    }
+
+  public bool update_favourite (string contact_urn, bool is_favourite)
+    {
+      string q = "";
+
+      if (is_favourite)
+        {
+          q += "INSERT { ";
+        }
+      else
+        {
+          q += "DELETE { ";
+        }
+      q += contact_urn + " nao:hasTag nao:predefined-tag-favorite } ";
+
+      if (this._do_update_query (q) == false)
+        {
+          GLib.warning ("Couldn't change favourite status");
+          return false;
+        }
+
+      return true;
+    }
+
+  public bool remove_contact (string tracker_id)
+    {
+      string delete_query = "DELETE { ?p a nco:PersonContact } ";
+      delete_query += "WHERE { ?p a nco:PersonContact . FILTER(tracker:id(?p) ";
+      delete_query += "= " + tracker_id + ") } ";
+
+      if (this._do_update_query (delete_query) == false)
+        {
+          GLib.warning ("Couldn't delete the contact");
+          return false;
+        }
+
+      return true;
+    }
+
+  public bool remove_triplet (string subject_urn, string pred,
+      string object_urn)
+    {
+      var builder = new Tracker.Sparql.Builder.update ();
+      builder.delete_open (null);
+      builder.subject (subject_urn);
+      builder.predicate (pred);
+      builder.object (object_urn);
+      builder.delete_close ();
+
+      if (this._do_update_query (builder.result) == false)
+        {
+          GLib.warning ("Couldn't delete triplet with query: %s\n",
+              builder.result);
+          return false;
+        }
+
+      return true;
+    }
+
+  public bool insert_triplet (string subject_iri, string pred,
+      string object_iri,
+      string? pred_b = null, string? obj_literal_b = null,
+      string? pred_c = null, string? obj_literal_c = null)
+    {
+      var builder = new Tracker.Sparql.Builder.update ();
+      builder.insert_open (null);
+      builder.subject (subject_iri);
+      builder.predicate (pred);
+      builder.object (object_iri);
+
+      if (pred_b != null)
+        {
+          builder.predicate (pred_b);
+          builder.object_string (obj_literal_b);
+        }
+
+      if (pred_c != null)
+        {
+          builder.predicate (pred_c);
+          builder.object_string (obj_literal_c);
+        }
+
+      builder.insert_close ();
+
+      if (this._do_update_query (builder.result) == false)
+        {
+          GLib.warning ("Couldn't insert triplet with query: %s\n",
+              builder.result);
+          return false;
+        }
+
+      return true;
+    }
+
+  private bool _do_update_query (string query)
+    {
+      bool ret = false;
+
+      if (this.debug)
+        {
+          GLib.stdout.printf ("_do_update_query : %s\n", query);
+        }
+
+      try
+        {
+          this._connection.update (query);
+          ret = true;
+        }
+      catch (Tracker.Sparql.Error e1)
+        {
+          GLib.warning ("Problem getting connection : %s\n", e1.message);
+        }
+      catch (GLib.IOError e2)
+        {
+          GLib.warning ("Problem saving data : %s\n", e2.message);
+        }
+      catch (GLib.DBusError e3)
+        {
+          GLib.warning ("Problem with the D-Bus connection : %s\n", e3.message);
+        }
+
+      return ret;
+    }
+
+  private Gee.HashMap<string, string> _copy_hash_map (
+      Gee.HashMap<string, string> orig)
+    {
+      Gee.HashMap<string, string> copy = new Gee.HashMap<string, string> ();
+      foreach (var k in orig.keys)
+        {
+          var v = orig.get (k);
+          copy.set (k.dup (), v.dup ());
+        }
+      return copy;
+    }
+
+  private void _setup_connection () throws BackendSetupError
+    {
+      try
+        {
+          this._connection = Tracker.Sparql.Connection.get ();
+        }
+      catch (GLib.IOError e1)
+        {
+          throw new BackendSetupError.ADD_CONTACT_FAILED
+          ("Could not connect to D-Bus service : %s\n", e1.message);
+        }
+      catch (Tracker.Sparql.Error e2)
+        {
+          throw new BackendSetupError.ADD_CONTACT_FAILED
+          ("Error fetching SPARQL connection handler : %s\n", e2.message);
+        }
+      catch (GLib.DBusError e3)
+        {
+          throw new BackendSetupError.ADD_CONTACT_FAILED
+          ("Error fetching SPARQL connection handler : %s\n", e3.message);
+        }
+    }
+
+  private void _add_contacts () throws BackendSetupError
+    {
+      string query = "";
+
+      this._contacts.reverse ();
+      foreach (var c in this._contacts)
+        {
+          query = query + "\n" + this._get_insert_query (c);
+        }
+
+      try
+        {
+          this._connection.update (query);
+        }
+      catch (Tracker.Sparql.Error e1)
+        {
+          throw new BackendSetupError.ADD_CONTACT_FAILED
+          ("Problem getting connection : %s\n", e1.message);
+        }
+      catch (GLib.IOError e2)
+        {
+          throw new BackendSetupError.ADD_CONTACT_FAILED
+          ("Error fetching SPARQL connection handler : %s\n", e2.message);
+        }
+      catch (GLib.DBusError e3)
+        {
+          throw new BackendSetupError.ADD_CONTACT_FAILED
+          ("Could not connect to D-Bus service : %s\n", e3.message);
+        }
+    }
+
+  private string _get_insert_query (Gee.HashMap<string, string> contact)
+    {
+      const string q_photo_uri_t = " . <%s> a nfo:Image, " +
+          "nie:DataObject ; nie:url '%s' ; nie:title '%s' ";
+      const string im_addr_t = " . <%s> a nco:IMAddress, " +
+          "nie:InformationElement; nco:imProtocol " +
+          "'%s' ; nco:imID '%s';   " +
+          "nco:imNickname '%s'; " +
+          "nco:imPresence nco:presence-status-available " +
+          " . <%smyimaccount> a nco:IMAccount; " +
+          "nco:imDisplayName '%s'; nco:hasIMContact " +
+          "<%s>  ";
+      const string affl_t = " . <%smyaffiliation> a nco:Affiliation " +
+          " . <%smyaffiliation> nco:hasIMAddress " +
+          " <%s>  ";
+      const string af_t = " . <affl:001> a nco:Affiliation; " +
+          "nco:role '%s'; nco:department '%s' ";
+      const string postal_t = " . <affl:001> a nco:Affiliation ; " +
+          "nco:hasPostalAddress <postal:001> . " +
+          " <postal:001> a nco:PostalAddress ; " +
+          "nco:pobox '%s'; " +
+          "nco:district '%s'; " +
+          "nco:county '%s'; " +
+          "nco:locality '%s'; " +
+          "nco:postalcode '%s'; " +
+          "nco:streetAddress '%s'; " +
+          "nco:addressLocation '%s'; " +
+          "nco:extendedAddress '%s'; " +
+          "nco:country '%s'; " +
+          "nco:region '%s' ";
+
+      string urn_contact;
+      if (contact.unset (this.URN, out urn_contact) == false)
+        {
+          urn_contact = "_:x";
+        }
+
+      string photo_uri = "";
+      string q = "INSERT { " + urn_contact + " a nco:PersonContact  ";
+      Gee.HashMap<string, string> addresses = null;
+      string[] phones = null;
+      string[] emails = null;
+      string[] urls = null;
+      string affiliation = "";
+      string postal_address = "";
+
+      foreach (var k in contact.keys)
+        {
+          string v = contact.get (k);
+          if (k == Trf.OntologyDefs.NCO_PHOTO)
+            {
+              photo_uri = v;
+              v = "<" + v + ">";
+            }
+          else if (k == Trf.OntologyDefs.NCO_IMADDRESS)
+            {
+              addresses = this._parse_addrs (v);
+              k = "";
+              v = "";
+              foreach (var addr in addresses.keys)
+                {
+                  string vtemp;
+                  vtemp = " nco:hasAffiliation [ a nco:Affiliation ; ";
+                  vtemp += "nco:hasIMAddress <" + addr + "> ] ";
+                  if (v != "")
+                    {
+                      v += "; ";
+                    }
+                  v += vtemp;
+                }
+            }
+          else if (k == Trf.OntologyDefs.NCO_PHONE_PROP)
+            {
+              phones = v.split (",");
+              k = "";
+              v = this._build_relation (Trf.OntologyDefs.NCO_HAS_AFFILIATION,
+                  phones);
+            }
+          else if (k == Trf.OntologyDefs.NCO_EMAIL_PROP)
+            {
+              emails = v.split (",");
+              k = "";
+              v = this._build_relation (Trf.OntologyDefs.NCO_HAS_AFFILIATION,
+                  emails);
+            }
+          else if (k == this.URLS)
+            {
+              urls = v.split (",");
+              k = "";
+              v = this._build_relation (Trf.OntologyDefs.NCO_HAS_AFFILIATION,
+                  urls);
+            }
+          else if (k == Trf.OntologyDefs.NAO_TAG)
+            {
+              v = Trf.OntologyDefs.NAO_FAVORITE;
+            }
+          else if (k == Trf.OntologyDefs.NCO_HAS_AFFILIATION)
+            {
+              affiliation = v;
+              v = "<affl:001>";
+            }
+          else  if (k == Trf.OntologyDefs.NCO_GENDER)
+            {
+
+            }
+          else  if (k == Trf.OntologyDefs.NCO_POSTAL_ADDRESS)
+            {
+              postal_address = v;
+              k = Trf.OntologyDefs.NCO_HAS_AFFILIATION;
+              v = "<affl:001>";
+            }
+          else
+            {
+              v = "'" + v + "'";
+            }
+
+          q += "; ";
+          string s = k + " " + v;
+          q += s;
+        }
+
+      if (photo_uri != "")
+        {
+          q += q_photo_uri_t.printf (photo_uri, photo_uri, photo_uri);
+        }
+
+      if (addresses != null && addresses.size > 0)
+        {
+          foreach (var addr in addresses.keys)
+            {
+              string proto = addresses.get (addr);
+              string q1 = im_addr_t.printf (addr, proto, addr, addr, addr,
+                  addr, addr);
+
+              string q2 = affl_t.printf (addr, addr, addr);
+
+              q += "%s%s".printf (q1, q2);
+            }
+        }
+
+      if (phones != null && phones.length > 0)
+        {
+          foreach (var p in phones)
+            {
+              var phone_urn = "<phone:%s>".printf (p);
+              var affl = "<%s>".printf (p);
+              this.insert_triplet (phone_urn, "a", Trf.OntologyDefs.NCO_PHONE,
+                  Trf.OntologyDefs.NCO_PHONE_PROP, p);
+              this.insert_triplet (affl, "a", Trf.OntologyDefs.NCO_AFFILIATION);
+              this.insert_triplet (affl,
+                  Trf.OntologyDefs.NCO_HAS_PHONE, phone_urn);
+            }
+        }
+
+      if (emails != null && emails.length > 0)
+        {
+          foreach (var p in emails)
+            {
+              var email_urn = "<email:%s>".printf (p);
+              var affl = "<%s>".printf (p);
+              this.insert_triplet (email_urn, "a", Trf.OntologyDefs.NCO_EMAIL,
+                  Trf.OntologyDefs.NCO_EMAIL_PROP, p);
+              this.insert_triplet (affl, "a", Trf.OntologyDefs.NCO_AFFILIATION);
+              this.insert_triplet (affl,
+                  Trf.OntologyDefs.NCO_HAS_EMAIL, email_urn);
+            }
+        }
+
+      if (urls != null && urls.length > 0)
+        {
+          int i = 0;
+          foreach (var p in urls)
+            {
+              string website_type = "";
+              var affl = "<%s>".printf (p);
+              switch (i % 3)
+                {
+                  case 0:
+                    website_type = Trf.OntologyDefs.NCO_WEBSITE;
+                    break;
+                  case 1:
+                    website_type = Trf.OntologyDefs.NCO_BLOG;
+                    break;
+                  case 2:
+                    website_type = "nco:url";
+                    break;
+                }
+
+              this.insert_triplet (affl, "a", Trf.OntologyDefs.NCO_AFFILIATION,
+                  website_type, p);
+              i++;
+            }
+        }
+
+      if (affiliation != "")
+        {
+          string[] role_info = affiliation.split (",");
+          q += af_t.printf (role_info[0], role_info[1]);
+        }
+
+      if (postal_address != "")
+        {
+          string[] postal_info = postal_address.split (":");
+          q += postal_t.printf (postal_info[0], postal_info[1],
+               postal_info[2], postal_info[3], postal_info[4],
+               postal_info[5], postal_info[6], postal_info[7],
+               postal_info[8], postal_info[9]);
+        }
+
+      q += " . }";
+
+      if (this.debug)
+        {
+          GLib.stdout.printf ("_get_insert_query : %s\n", q);
+        }
+
+      return q;
+    }
+
+  private Gee.HashMap<string, string> _parse_addrs (string addr_s)
+    {
+      Gee.HashMap<string, string> ret = new Gee.HashMap<string, string> ();
+      string[] im_addrs = addr_s.split (",");
+
+      foreach (var a in im_addrs)
+        {
+          string[] info = a.split ("#");
+          string proto = info[0];
+          string addr = info[1];
+
+          ret.set ((owned) addr, (owned) proto);
+        }
+
+      return ret;
+    }
+
+  private string _build_relation (string predicate, string[] objects)
+    {
+      string ret = "";
+
+      foreach (var obj in objects)
+        {
+          string vtemp1;
+          vtemp1 = " " + predicate + " <" + obj + "> ";
+          if (ret != "")
+            {
+              ret += "; ";
+            }
+          ret += vtemp1;
+        }
+
+      return ret;
+    }
+}
\ No newline at end of file
diff --git a/tests/tools/Makefile.am b/tests/tools/Makefile.am
index bb36dd2..bf328f0 100644
--- a/tests/tools/Makefile.am
+++ b/tests/tools/Makefile.am
@@ -1,5 +1,6 @@
 EXTRA_DIST = \
 	with-session-bus.sh \
+	with-session-bus-tracker.sh \
 	manager-file.py \
 	$(NULL)
 
diff --git a/tests/tools/tracker.sh b/tests/tools/tracker.sh
new file mode 100644
index 0000000..f2b2eca
--- /dev/null
+++ b/tests/tools/tracker.sh
@@ -0,0 +1,34 @@
+#
+# Helper functions to start your own Tracker instance. This depends
+# on you having your own D-Bus session bus started (first).
+#
+#
+# Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.
+
+tracker_tmpdir=$(mktemp -d)
+
+tracker_init_settings () {
+    export XDG_DATA_HOME=$tracker_tmpdir/.local
+    export XDG_CACHE_HOME=$tracker_tmpdir/.cache
+    export XDG_CONFIG_HOME=$tracker_tmpdir/.config
+}
+
+# This should be called on INT TERM and EXIT
+tracker_cleanup () {
+    rm -rf $tracker_tmpdir
+    rm -rf $tracker_tmpdir
+}
+
+tracker_start () {
+    tracker-control -rs > /dev/null 2>&1
+}
+
+tracker_stop () {
+    tracker_cleanup
+    tracker-control -r > /dev/null 2>&1
+}
+
diff --git a/tests/tools/with-session-bus-tracker.sh b/tests/tools/with-session-bus-tracker.sh
new file mode 100755
index 0000000..e0c10b7
--- /dev/null
+++ b/tests/tools/with-session-bus-tracker.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+# with-session-bus.sh - run a program with a temporary D-Bus session daemon
+#
+# interesting bits have been move into dbus to permit reusability
+#
+# Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.
+
+
+cur_dir=`dirname $0`
+
+. $cur_dir"/dbus-session.sh"
+. $cur_dir"/tracker.sh"
+
+dbus_parse_args $@
+while test "z$1" != "z--"; do
+    shift
+done
+shift
+if test "z$1" = "z"; then dbus_usage; fi
+
+cleanup ()
+{
+    tracker_stop
+    dbus_stop
+}
+
+trap cleanup INT HUP TERM
+
+tracker_init_settings
+dbus_init 0
+
+dbus_start
+tracker_start
+
+e=0
+"$@" || e=$?
+
+trap - INT HUP TERM
+cleanup
+
+exit $e
diff --git a/tests/tracker/Makefile.am b/tests/tracker/Makefile.am
new file mode 100644
index 0000000..5793a8d
--- /dev/null
+++ b/tests/tracker/Makefile.am
@@ -0,0 +1,253 @@
+AM_CPPFLAGS = \
+	$(GLIB_CFLAGS) \
+	$(GEE_CFLAGS) \
+	$(TRACKER_SPARQL_CFLAGS) \
+	-I$(top_srcdir)/folks \
+	-I$(top_srcdir)/backends/tracker/lib \
+	-I$(top_srcdir)/tests/lib \
+	-I$(top_srcdir)/tests/lib/tracker \
+	-include $(CONFIG_HEADER) \
+	$(NULL)
+
+LDADD = \
+	$(top_builddir)/tests/lib/libfolks-test.la \
+	$(top_builddir)/tests/lib/tracker/libtracker-test.la \
+	$(top_builddir)/backends/tracker/lib/libfolks-tracker.la \
+	$(top_builddir)/folks/libfolks.la \
+	$(GLIB_LIBS) \
+	$(GEE_LIBS) \
+	$(TRACKER_SPARQL_LIBS) \
+	-L$(top_srcdir)/backends/tracker/lib \
+	$(NULL)
+
+RUN_WITH_PRIVATE_BUS = $(top_srcdir)/tests/tools/with-session-bus-tracker.sh
+
+AM_VALAFLAGS = \
+	--vapidir=. \
+	--vapidir=$(top_srcdir)/folks \
+	--vapidir=$(top_srcdir)/backends/tracker/lib \
+	--vapidir=$(top_srcdir)/tests/lib \
+	--vapidir=$(top_srcdir)/tests/lib/tracker \
+	--pkg gobject-2.0 \
+	--pkg gio-2.0 \
+	--pkg gee-1.0 \
+	--pkg folks \
+	--pkg folks-test \
+	--pkg folks-tracker \
+	--pkg tracker-sparql-0.10 \
+	--pkg tracker-test \
+	$(NULL)
+
+# in order from least to most complex
+noinst_PROGRAMS = \
+	individual-retrieval \
+	name-details-interface \
+	avatar-details-interface \
+	im-details-interface \
+	phone-details-interface \
+	email-details-interface \
+	url-details-interface \
+	favourite-details-interface \
+	fullname-updates \
+	add-contact \
+	default-contact \
+	remove-contact \
+	nickname-updates \
+	family-name-updates \
+	given-name-updates \
+	additional-names-updates \
+	prefix-name-updates \
+	suffix-name-updates \
+	website-updates \
+	favourite-updates \
+	emails-updates \
+	phones-updates \
+	imaddresses-updates \
+	role-details-interface \
+	birthday-details-interface \
+	birthday-updates \
+	note-details-interface  \
+	gender-details-interface  \
+	postal-address-details-interface  \
+	avatar-updates  \
+	$(NULL)
+
+backend_store_key_file=$(srcdir)/data/backend-tracker-only.ini
+avatar_file=$(srcdir)/data/avatar-01.jpg
+TESTS_ENVIRONMENT = \
+	GCONF_DEFAULT_SOURCE_PATH= abs_top_srcdir@/tests/data/gconf.path \
+	FOLKS_BACKEND_PATH=$(BACKEND_UNINST_PATH) \
+	FOLKS_BACKEND_STORE_KEY_FILE_PATH=$(backend_store_key_file) \
+	AVATAR_FILE_PATH=$(avatar_file) \
+	$(RUN_WITH_PRIVATE_BUS) \
+	--session \
+	--
+TESTS = $(noinst_PROGRAMS)
+
+individual_retrieval_SOURCES = \
+	individual-retrieval.vala \
+	$(NULL)
+
+name_details_interface_SOURCES = \
+	name-details-interface.vala \
+	$(NULL)
+
+avatar_details_interface_SOURCES = \
+	avatar-details-interface.vala \
+	$(NULL)
+
+im_details_interface_SOURCES = \
+	im-details-interface.vala \
+	$(NULL)
+
+phone_details_interface_SOURCES = \
+	phone-details-interface.vala \
+	$(NULL)
+
+email_details_interface_SOURCES = \
+	email-details-interface.vala \
+	$(NULL)
+
+url_details_interface_SOURCES = \
+	url-details-interface.vala \
+	$(NULL)
+
+favourite_details_interface_SOURCES = \
+	favourite-details-interface.vala \
+	$(NULL)
+
+fullname_updates_SOURCES = \
+	fullname-updates.vala \
+	$(NULL)
+
+add_contact_SOURCES = \
+	add-contact.vala \
+	$(NULL)
+
+default_contact_SOURCES = \
+	default-contact.vala \
+	$(NULL)
+
+remove_contact_SOURCES = \
+	remove-contact.vala \
+	$(NULL)
+
+nickname_updates_SOURCES = \
+	nickname-updates.vala \
+	$(NULL)
+
+family_name_updates_SOURCES = \
+	family-name-updates.vala \
+	$(NULL)
+
+given_name_updates_SOURCES = \
+	given-name-updates.vala \
+	$(NULL)
+
+additional_names_updates_SOURCES = \
+	additional-names-updates.vala \
+	$(NULL)
+
+prefix_name_updates_SOURCES = \
+	prefix-name-updates.vala \
+	$(NULL)
+
+suffix_name_updates_SOURCES = \
+	suffix-name-updates.vala \
+	$(NULL)
+
+website_updates_SOURCES = \
+	website-updates.vala \
+	$(NULL)
+
+favourite_updates_SOURCES = \
+	favourite-updates.vala \
+	$(NULL)
+
+emails_updates_SOURCES = \
+	emails-updates.vala \
+	$(NULL)
+
+phones_updates_SOURCES = \
+	phones-updates.vala \
+	$(NULL)
+
+imaddresses_updates_SOURCES = \
+	imaddresses-updates.vala \
+	$(NULL)
+
+role_details_interface_SOURCES = \
+	role-details-interface.vala \
+	$(NULL)
+
+birthday_details_interface_SOURCES = \
+	birthday-details-interface.vala \
+	$(NULL)
+
+birthday_updates_SOURCES = \
+	birthday-updates.vala \
+	$(NULL)
+
+note_details_interface_SOURCES = \
+	note-details-interface.vala \
+	$(NULL)
+
+gender_details_interface_SOURCES = \
+	gender-details-interface.vala \
+	$(NULL)
+
+postal_address_details_interface_SOURCES = \
+	postal-address-details-interface.vala \
+	$(NULL)
+
+avatar_updates_SOURCES = \
+	avatar-updates.vala \
+	$(NULL)
+
+CLEANFILES = \
+        *.pid \
+        *.address \
+        $(TESTS) \
+        $(NULL)
+
+MAINTAINERCLEANFILES = \
+        $(addsuffix .c,$(noinst_PROGRAMS)) \
+        individual_retrieval_vala.stamp \
+        name_details_interface_vala.stamp \
+        avatar_details_interface_vala.stamp \
+        im_details_interface_vala.stamp \
+        phone_details_interface_vala.stamp \
+        email_details_interface_vala.stamp \
+        url_details_interface_vala.stamp \
+        favourite_details_interface_vala.stamp \
+        fullname_updates_vala.stamp \
+        add_contact_vala.stamp \
+        default_contact_vala.stamp \
+        remove_contact_vala.stamp \
+        nickname_updates_vala.stamp \
+        family_name_updates_vala.stamp \
+        given_name_updates_vala.stamp \
+        additional_names_updates_vala.stamp \
+        prefix_name_updates_vala.stamp \
+        suffix_name_updates_vala.stamp \
+        website_updates_vala.stamp \
+        favourite_updates_vala.stamp \
+        emails_updates_vala.stamp \
+        phones_updates_vala.stamp \
+        imaddresses_updates_vala.stamp \
+        role_details_interface_vala.stamp \
+        birthday_details_interface_vala.stamp \
+        birthday_updates_vala.stamp \
+        note_details_interface_vala.stamp \
+        gender_details_interface_vala.stamp \
+        postal_address_details_interface_vala.stamp \
+        avatar_updates_vala.stamp \
+        $(NULL)
+
+EXTRA_DIST = \
+	$(MAINTAINERCLEANFILES) \
+	$(backend_store_key_file) \
+	$(srcdir)/data/avatar-01.jpg \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/tests/tracker/add-contact.vala b/tests/tracker/add-contact.vala
new file mode 100644
index 0000000..79ed324
--- /dev/null
+++ b/tests/tracker/add-contact.vala
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class AddContactTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private bool _contact_added;
+  private IndividualAggregator _aggregator;
+  private string _persona_fullname;
+  private GLib.MainLoop _main_loop;
+
+  public AddContactTests ()
+    {
+      base ("AddContactTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+
+      this.add_test ("test adding contacts ", this.test_add_contact);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_add_contact ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      this._persona_fullname = "persona #1";
+      this._contact_added = false;
+
+      var store = BackendStore.dup ();
+
+      this._test_add_contact_async (store, (o, r) =>
+        {
+          this._test_add_contact_async.end (r);
+        });
+
+      Timeout.add_seconds (5, () =>
+          {
+            this._main_loop.quit ();
+            assert_not_reached ();
+          });
+
+      this._main_loop.run ();
+      assert (this._contact_added == true);
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_add_contact_async (BackendStore store)
+    {
+      yield store.prepare ();
+
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+
+      try
+        {
+          yield this._aggregator.prepare ();
+
+          Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+          c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._persona_fullname);
+          this._tracker_backend.add_contact (c1);
+          this._tracker_backend.set_up ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          string full_name = i.full_name;
+          i.notify["full-name"].connect (this._notify_full_name_cb);
+          if (full_name != null)
+            {
+              if (full_name == this._persona_fullname)
+                {
+                  this._contact_added = true;
+                  this._main_loop.quit ();
+                }
+            }
+        }
+
+        assert (removed == null);
+    }
+
+  private void _notify_full_name_cb ()
+    {
+      GLib.List<Individual> individuals =
+          this._aggregator.individuals.get_values ();
+      foreach (unowned Individual i in individuals)
+        {
+          if (i.full_name == this._persona_fullname)
+            {
+              this._contact_added = true;
+              this._main_loop.quit ();
+            }
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new AddContactTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/additional-names-updates.vala b/tests/tracker/additional-names-updates.vala
new file mode 100644
index 0000000..8a0ac9a
--- /dev/null
+++ b/tests/tracker/additional-names-updates.vala
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class AdditionalNamesUpdatesTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private bool _updated_additional_names_found;
+  private string _updated_additional_names;
+  private string _individual_id;
+  private GLib.MainLoop _main_loop;
+  private bool _initial_additional_names_found;
+  private string _contact_urn;
+  private string _initial_additional_names;
+
+  public AdditionalNamesUpdatesTests ()
+    {
+      base ("AdditionalNamesUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("additional names updates",
+          this.test_additional_names_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_additional_names_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      string initial_fullname = "persona #1";
+      this._initial_additional_names = "additional name #1";
+      this._updated_additional_names = "updated additional name #1";
+      this._contact_urn = "<urn:contact001>";
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, initial_fullname);
+      c1.set (Trf.OntologyDefs.NCO_ADDITIONAL,
+          this._initial_additional_names);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._initial_additional_names_found = false;
+      this._updated_additional_names_found = false;
+      this._individual_id = "";
+
+      var store = BackendStore.dup ();
+      _test_additional_names_updates_async (store);
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_additional_names_found == true);
+      assert (this._updated_additional_names_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_additional_names_updates_async (BackendStore store)
+    {
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+      {
+        i.structured_name.notify["additional-names"].connect
+        (this._notify_additional_names_cb);
+        var additional_names = i.structured_name.additional_names;
+        if (additional_names == this._initial_additional_names)
+          {
+            this._individual_id = i.id;
+            this._initial_additional_names_found = true;
+            this._tracker_backend.update_contact (this._contact_urn,
+                Trf.OntologyDefs.NCO_ADDITIONAL,
+                this._updated_additional_names);
+          }
+      }
+
+      assert (removed == null);
+    }
+
+  private void _notify_additional_names_cb ()
+    {
+      var i = this._aggregator.individuals.lookup (this._individual_id);
+
+      if (i == null)
+        return;
+
+      var additional_names = i.structured_name.additional_names;
+
+      if (additional_names == this._updated_additional_names)
+        {
+          this._updated_additional_names_found = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new AdditionalNamesUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/avatar-details-interface.vala b/tests/tracker/avatar-details-interface.vala
new file mode 100644
index 0000000..5fd0f33
--- /dev/null
+++ b/tests/tracker/avatar-details-interface.vala
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class AvatarDetailsInterfaceTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private string _avatar_uri;
+  private bool _avatars_are_equal;
+  private GLib.MainLoop _main_loop;
+  IndividualAggregator _aggregator;
+
+  public AvatarDetailsInterfaceTests ()
+    {
+      base ("AvatarDetailsInterfaceTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+
+      this.add_test ("test avatar details interface",
+          this.test_avatar_details_interface);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_avatar_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      string avatar_path = Environment.get_variable ("AVATAR_FILE_PATH");
+      var temp_file = GLib.File.new_for_path (avatar_path);
+      var full_avatar_path = temp_file.get_path ();
+      this._avatar_uri = "file://" + full_avatar_path;
+      this._avatars_are_equal = false;
+
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, "persona #1");
+      c1.set (Trf.OntologyDefs.NCO_PHOTO, this._avatar_uri);
+      this._tracker_backend.add_contact (c1);
+      this._tracker_backend.set_up ();
+
+      test_avatar_details_interface_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+      assert (this._avatars_are_equal);
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void test_avatar_details_interface_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+
+      /* Set up the aggregator */
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          string full_name = ((Folks.NameDetails) i).full_name;
+          if (full_name != null)
+            {
+              i.notify["avatar"].connect (this._notify_avatar_cb);
+              if (i.avatar != null)
+                {
+                  var src_avatar = File.new_for_uri (this._avatar_uri);
+                  this._avatars_are_equal =
+                      this._compare_files (src_avatar, i.avatar);
+                  this._main_loop.quit ();
+                }
+            }
+        }
+    }
+
+  private void _notify_avatar_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual individual = (Folks.Individual) individual_obj;
+      var src_avatar = File.new_for_uri (this._avatar_uri);
+      this._avatars_are_equal = this._compare_files (src_avatar,
+          individual.avatar);
+      this._main_loop.quit ();
+    }
+
+  private bool _compare_files (File a, File b)
+    {
+      string content_a = "a";
+      string content_b = "b";
+
+      try
+        {
+          a.load_contents (null, out content_a);
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("couldn't load file a");
+        }
+
+      try
+        {
+          b.load_contents (null, out content_b);
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("couldn't load file b");
+        }
+
+      return content_a == content_b;
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new AvatarDetailsInterfaceTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/avatar-updates.vala b/tests/tracker/avatar-updates.vala
new file mode 100644
index 0000000..8f77cc4
--- /dev/null
+++ b/tests/tracker/avatar-updates.vala
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class AvatarUpdatesTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private bool _updated_avatar_found;
+  private string _updated_avatar;
+  private string _individual_id;
+  private GLib.MainLoop _main_loop;
+  private bool _initial_avatar_found;
+  private string _initial_fullname;
+  private string _initial_avatar;
+  private string _contact_urn;
+  private string _photo_urn;
+
+  public AvatarUpdatesTests ()
+    {
+      base ("AvatarUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("avatar updates", this.test_avatar_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_avatar_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._initial_fullname = "persona #1";
+      this._initial_avatar = "file:///tmp/avatar-01";
+      this._contact_urn = "<urn:contact001>";
+      this._photo_urn = "<" + this._initial_avatar + ">";
+      this._updated_avatar = "file:///tmp/avatar-02";
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname);
+      c1.set (Trf.OntologyDefs.NCO_PHOTO, this._initial_avatar);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._initial_avatar_found = false;
+      this._updated_avatar_found = false;
+      this._individual_id = "";
+
+      test_avatar_updates_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_avatar_found == true);
+      assert (this._updated_avatar_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void test_avatar_updates_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          if (i.full_name == this._initial_fullname)
+            {
+              i.notify["avatar"].connect (this._notify_avatar_cb);
+              this._individual_id = i.id;
+
+              if (i.avatar != null &&
+                  i.avatar.get_uri () == this._initial_avatar)
+                {
+                  this._initial_avatar_found = true;
+
+                  this._tracker_backend.remove_triplet (this._contact_urn,
+                      Trf.OntologyDefs.NCO_PHOTO, this._photo_urn);
+
+                  string photo_urn_2 = "<" + this._updated_avatar;
+                  photo_urn_2 += ">";
+                  this._tracker_backend.insert_triplet (photo_urn_2,
+                      "a", "nfo:Image, nie:DataObject",
+                      Trf.OntologyDefs.NIE_URL,
+                      this._updated_avatar);
+
+                  this._tracker_backend.insert_triplet
+                      (this._contact_urn,
+                      Trf.OntologyDefs.NCO_PHOTO, photo_urn_2);
+
+                }
+            }
+
+          assert (removed == null);
+        }
+    }
+
+  private void _notify_avatar_cb ()
+    {
+      var i = this._aggregator.individuals.lookup (this._individual_id);
+      if (i == null)
+        return;
+
+      if (i.avatar != null &&
+          i.avatar.get_uri () == this._updated_avatar)
+        {
+          this._main_loop.quit ();
+          this._updated_avatar_found = true;
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new AvatarUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/birthday-details-interface.vala b/tests/tracker/birthday-details-interface.vala
new file mode 100644
index 0000000..b6ab65c
--- /dev/null
+++ b/tests/tracker/birthday-details-interface.vala
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class BirthdayDetailsInterfaceTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private bool _found_birthday;
+  private DateTime _dobj;
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private string _fullname;
+
+  public BirthdayDetailsInterfaceTests ()
+    {
+      base ("BirthdayDetailsInterfaceTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("test birthday details interface",
+          this.test_birthay_details_interface);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_birthay_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._fullname = "persona #1";
+      string birthday = "2001-10-26T20:32:52Z";
+      TimeVal t = TimeVal ();
+      t.from_iso8601 (birthday);
+      this._dobj = new  DateTime.from_timeval_utc (t);
+
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._fullname);
+      c1.set (Trf.OntologyDefs.NCO_BIRTHDAY, birthday);
+      this._tracker_backend.add_contact (c1);
+      this._tracker_backend.set_up ();
+
+      this._found_birthday = false;
+
+      this._test_birthay_details_interface ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._found_birthday == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_birthay_details_interface ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          if (i.full_name == this._fullname)
+            {
+              i.notify["birthday"].connect (this._notify_birthday_cb);
+              if (i.birthday != null)
+                {
+                  if (i.birthday.compare (this._dobj) == 0)
+                    {
+                      this._found_birthday = true;
+                      this._main_loop.quit ();
+                    }
+                }
+            }
+        }
+        assert (removed == null);
+    }
+
+  void _notify_birthday_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual individual = (Folks.Individual) individual_obj;
+      if (individual.birthday != null &&
+          individual.birthday.compare (this._dobj) == 0)
+        {
+          this._found_birthday = true;
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new BirthdayDetailsInterfaceTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/birthday-updates.vala b/tests/tracker/birthday-updates.vala
new file mode 100644
index 0000000..7562970
--- /dev/null
+++ b/tests/tracker/birthday-updates.vala
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class BirthdayUpdatesTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private string _initial_birthday;
+  private string _updated_birthday;
+  private string _individual_id;
+  private bool _initial_birthday_found;
+  private bool _updated_birthday_found;
+  private string _contact_urn;
+  private DateTime _initial_bday_obj;
+  private DateTime _updated_bday_obj;
+  private string _initial_fullname;
+  private GLib.MainLoop _main_loop;
+
+  public BirthdayUpdatesTests ()
+    {
+      base ("BirthdayUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("birthday updates", this.test_birthday_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_birthday_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._initial_fullname = "persona #1";
+      this._initial_birthday = "2001-10-26T20:32:52Z";
+      this._updated_birthday = "1991-10-26T20:32:52Z";
+      this._contact_urn = "<urn:contact001>";
+
+      TimeVal t1 = TimeVal ();
+      t1.from_iso8601 (this._initial_birthday);
+      this._initial_bday_obj = new  DateTime.from_timeval_utc (t1);
+
+      TimeVal t2 = TimeVal ();
+      t2.from_iso8601 (this._updated_birthday);
+      this._updated_bday_obj = new  DateTime.from_timeval_utc (t2);
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname);
+      c1.set (Trf.OntologyDefs.NCO_BIRTHDAY, this._initial_birthday);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._initial_birthday_found = false;
+      this._updated_birthday_found = false;
+      this._individual_id = "";
+
+      test_birthday_updates_async ();
+
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_birthday_found == true);
+      assert (this._updated_birthday_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void test_birthday_updates_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      /* Set up the aggregator */
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          if (i.full_name == this._initial_fullname)
+            {
+              i.notify["birthday"].connect (this._notify_birthday_cb);
+              if (i.birthday != null &&
+                  i.birthday.compare (this._initial_bday_obj) == 0)
+                {
+                  this._individual_id = i.id;
+                  this._initial_birthday_found = true;
+                  this._tracker_backend.update_contact (this._contact_urn,
+                      Trf.OntologyDefs.NCO_BIRTHDAY, this._updated_birthday);
+                }
+            }
+        }
+        assert (removed == null);
+    }
+
+  void _notify_birthday_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+
+      if (i.birthday == null)
+        {
+          return;
+        }
+
+      if (i.birthday.compare (this._initial_bday_obj) == 0)
+        {
+          this._individual_id = i.id;
+          this._initial_birthday_found = true;
+          this._tracker_backend.update_contact (this._contact_urn,
+              Trf.OntologyDefs.NCO_BIRTHDAY, this._updated_birthday);
+        }
+      else if (i.birthday.compare (this._updated_bday_obj) == 0)
+        {
+          this._updated_birthday_found = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new BirthdayUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/data/avatar-01.jpg b/tests/tracker/data/avatar-01.jpg
new file mode 100644
index 0000000..36e5465
Binary files /dev/null and b/tests/tracker/data/avatar-01.jpg differ
diff --git a/tests/tracker/data/backend-tracker-only.ini b/tests/tracker/data/backend-tracker-only.ini
new file mode 100644
index 0000000..c274f45
--- /dev/null
+++ b/tests/tracker/data/backend-tracker-only.ini
@@ -0,0 +1,6 @@
+[tracker]
+enabled=true
+
+[all-others]
+enabled=false
+
diff --git a/tests/tracker/default-contact.vala b/tests/tracker/default-contact.vala
new file mode 100644
index 0000000..bacd4d0
--- /dev/null
+++ b/tests/tracker/default-contact.vala
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class DefaultContactTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private bool _found_default_user;
+  private bool _found_not_user;
+  private bool _found_unknown_user;
+  private GLib.MainLoop _main_loop;
+  private string _fullname_persona;
+  private IndividualAggregator _aggregator;
+  public DefaultContactTests ()
+    {
+      base ("DefaultContactTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+
+      this.add_test ("test default contact", this.test_default_contact);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_default_contact ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._fullname_persona = "persona #1";
+
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._fullname_persona);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._found_default_user = false;
+      this._found_not_user = false;
+      this._found_unknown_user = false;
+
+      _test_default_contact_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._found_default_user == true);
+      assert (this._found_not_user == true);
+      assert (this._found_unknown_user == false);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_default_contact_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          string full_name = i.full_name;
+          if (full_name != null && full_name == this._fullname_persona
+              && i.is_user == false)
+            {
+              this._found_not_user = true;
+            }
+          else if (i.is_user == true)
+            {
+              this._found_default_user = true;
+            }
+          else
+            {
+              this._found_unknown_user = true;
+            }
+        }
+
+        if (this._found_not_user &&
+            this._found_default_user)
+          this._main_loop.quit ();
+
+        assert (removed == null);
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new DefaultContactTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/email-details-interface.vala b/tests/tracker/email-details-interface.vala
new file mode 100644
index 0000000..a2f3821
--- /dev/null
+++ b/tests/tracker/email-details-interface.vala
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class EmailDetailsInterfaceTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private int _num_emails;
+  private bool _found_email_1;
+  private bool _found_email_2;
+
+  public EmailDetailsInterfaceTests ()
+    {
+      base ("EmailDetailsInterfaceTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+
+      this.add_test ("test email details interface",
+          this.test_email_details_interface);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_email_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, "persona #1");
+      c1.set (Trf.OntologyDefs.NCO_EMAIL_PROP,
+          "test1 example org,test2 example org");
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._num_emails = 0;
+      this._found_email_1 = false;
+      this._found_email_2 = false;
+
+      this._test_email_details_interface_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._num_emails == 2);
+      assert (this._found_email_1 == true);
+      assert (this._found_email_2 == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_email_details_interface_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          string full_name = i.full_name;
+          if (full_name != null)
+            {
+              foreach (var email in i.email_addresses)
+                {
+                  if (email.value == "test1 example org")
+                    {
+                      this._found_email_1 = true;
+                      this._num_emails++;
+                    }
+                  else if (email.value == "test2 example org")
+                    {
+                      this._found_email_2 = true;
+                      this._num_emails++;
+                    }
+                }
+            }
+        }
+
+        assert (removed == null);
+
+        if (this._found_email_1 &&
+            this._found_email_2 &&
+            this._num_emails == 2)
+          this._main_loop.quit ();
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new EmailDetailsInterfaceTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/emails-updates.vala b/tests/tracker/emails-updates.vala
new file mode 100644
index 0000000..683235c
--- /dev/null
+++ b/tests/tracker/emails-updates.vala
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class EmailsUpdatesTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private string _individual_id;
+  private bool _initial_email_found;
+  private string _initial_fullname_1;
+  private bool _updated_email_found;
+  private string _email_1;
+  private string _email_2;
+  private string _contact_urn_1;
+
+  public EmailsUpdatesTests ()
+    {
+      base ("EmailsUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("emails updates", this.test_emails_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_emails_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._initial_fullname_1 = "persona #1";
+      this._contact_urn_1 = "<urn:contact001>";
+      this._email_1 = "persona-addr-1 example org";
+      this._email_2 = "persona-addr-2 example org";
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn_1);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname_1);
+      c1.set (Trf.OntologyDefs.NCO_EMAIL_PROP, this._email_1);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._individual_id = "";
+      this._initial_email_found = false;
+      this._updated_email_found = false;
+
+      this._test_emails_updates_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_email_found == true);
+
+      bool initial_email_found_again = false;
+
+      var i = this._aggregator.individuals.lookup (this._individual_id);
+      if (i != null)
+        {
+          foreach (unowned FieldDetails fd in i.email_addresses)
+            {
+              var email = fd.value;
+              if (email == this._email_1)
+                {
+                  initial_email_found_again = true;
+                }
+            }
+        }
+
+      assert (initial_email_found_again == false);
+      assert (this._updated_email_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_emails_updates_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          if (i.full_name == this._initial_fullname_1)
+            {
+              this._individual_id = i.id;
+              i.notify["email-addresses"].connect (this._notify_email_cb);
+
+              foreach (unowned FieldDetails fd in i.email_addresses)
+                {
+                  var email = fd.value;
+                  if (email == this._email_1)
+                    {
+                      this._initial_email_found = true;
+
+                      var urn_email_1 = "<" + this._email_1 + ">";
+                      this._tracker_backend.remove_triplet (this._contact_urn_1,
+                          Trf.OntologyDefs.NCO_HAS_AFFILIATION, urn_email_1);
+
+                      var urn_email_2 = "<email:" + this._email_2 + ">";
+                      this._tracker_backend.insert_triplet (urn_email_2,
+                          "a", Trf.OntologyDefs.NCO_EMAIL,
+                          Trf.OntologyDefs.NCO_EMAIL_PROP,
+                          this._email_2);
+
+                      var affl_2 = "<" + this._email_2 + ">";
+                      this._tracker_backend.insert_triplet
+                          (affl_2,
+                          "a", Trf.OntologyDefs.NCO_AFFILIATION);
+
+                      this._tracker_backend.insert_triplet
+                          (affl_2,
+                          Trf.OntologyDefs.NCO_HAS_EMAIL, urn_email_2);
+
+                      this._tracker_backend.insert_triplet
+                          (this._contact_urn_1,
+                          Trf.OntologyDefs.NCO_HAS_AFFILIATION, affl_2);
+                    }
+                }
+            }
+        }
+
+      assert (removed == null);
+    }
+
+  private void _notify_email_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual individual = (Folks.Individual) individual_obj;
+
+      if (this._individual_id != individual.id)
+        return;
+
+      foreach (unowned FieldDetails fd in individual.email_addresses)
+        {
+          var email = fd.value;
+          if (email == this._email_2)
+            {
+              this._updated_email_found = true;
+              this._main_loop.quit ();
+            }
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new EmailsUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/family-name-updates.vala b/tests/tracker/family-name-updates.vala
new file mode 100644
index 0000000..dbcb7ee
--- /dev/null
+++ b/tests/tracker/family-name-updates.vala
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class FamilyNameUpdatesTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private bool _initial_family_name_found;
+  private bool _updated_family_name_found;
+  private string _updated_family_name;
+  private string _individual_id;
+  private string _initial_fullname;
+  private string _initial_family_name;
+  private string _contact_urn;
+
+
+  public FamilyNameUpdatesTests ()
+    {
+      base ("FamilyNameUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("family name updates", this.test_family_name_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_family_name_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._initial_fullname = "persona #1";
+      this._initial_family_name = "family name #1";
+      this._updated_family_name = "updated family #1";
+      this._contact_urn = "<urn:contact001>";
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname);
+      c1.set (Trf.OntologyDefs.NCO_FAMILY, this._initial_family_name);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._initial_family_name_found = false;
+      this._updated_family_name_found = false;
+      this._individual_id = "";
+
+      this._test_family_name_updates_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_family_name_found == true);
+      assert (this._updated_family_name_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_family_name_updates_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          i.structured_name.notify["family-name"].connect
+              (this._notify_family_name_cb);
+          var family_name = i.structured_name.family_name;
+          if (family_name == this._initial_family_name)
+            {
+              this._individual_id = i.id;
+              this._initial_family_name_found = true;
+              this._tracker_backend.update_contact (this._contact_urn,
+                  Trf.OntologyDefs.NCO_FAMILY, this._updated_family_name);
+            }
+        }
+
+        assert (removed == null);
+    }
+
+  private void _notify_family_name_cb ()
+    {
+      var i = this._aggregator.individuals.lookup (this._individual_id);
+      if (i == null)
+        return;
+
+      var family_name = i.structured_name.family_name;
+      if (family_name == this._updated_family_name)
+        {
+          this._updated_family_name_found = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new FamilyNameUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/favourite-details-interface.vala b/tests/tracker/favourite-details-interface.vala
new file mode 100644
index 0000000..3994f8e
--- /dev/null
+++ b/tests/tracker/favourite-details-interface.vala
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class FavouriteDetailsInterfaceTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private GLib.MainLoop _main_loop;
+  private string _fullname_p1;
+  private string _fullname_p2;
+  private string _fullname_p3;
+  private bool _found_p1;
+  private bool _found_p2;
+  private bool _found_p3;
+  private IndividualAggregator _aggregator;
+
+  public FavouriteDetailsInterfaceTests ()
+    {
+      base ("FavouriteDetailsInterfaceTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+
+      this.add_test ("test favourite details interface",
+          this.test_favourite_details_interface);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_favourite_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      Gee.HashMap<string, string> c2 = new Gee.HashMap<string, string> ();
+      Gee.HashMap<string, string> c3 = new Gee.HashMap<string, string> ();
+      this._fullname_p1 = "favourite persona #1";
+      this._fullname_p2 = "favourite persona #2";
+      this._fullname_p3 = "favourite persona #3";
+
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._fullname_p1);
+      c1.set (Trf.OntologyDefs.NAO_TAG, "");
+      this._tracker_backend.add_contact (c1);
+
+      c2.set (Trf.OntologyDefs.NCO_FULLNAME, this._fullname_p2);
+      c2.set (Trf.OntologyDefs.NAO_TAG, "");
+      this._tracker_backend.add_contact (c2);
+
+      c3.set (Trf.OntologyDefs.NCO_FULLNAME, this._fullname_p3);
+      this._tracker_backend.add_contact (c3);
+
+      this._tracker_backend.set_up ();
+
+      this._found_p1 = false;
+      this._found_p2 = false;
+      this._found_p3 = false;
+
+      this._test_favourite_details_interface_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          return false;
+        });
+
+      this._main_loop.run ();
+
+      assert (this._found_p1 == true);
+      assert (this._found_p2 == true);
+      assert (this._found_p3 == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_favourite_details_interface_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          string full_name = i.full_name;
+          if (full_name != null)
+            {
+              if (full_name == this._fullname_p1)
+                {
+                  assert (i.is_favourite == true);
+                  this._found_p1 = true;
+                }
+              else if (full_name == this._fullname_p2)
+                {
+                  assert (i.is_favourite == true);
+                  this._found_p2 = true;
+                }
+              else if (full_name == this._fullname_p3)
+                {
+                  assert (i.is_favourite == false);
+                  this._found_p3 = true;
+                }
+            }
+        }
+
+        assert (removed == null);
+
+        if (this._found_p1 &&
+            this._found_p2 &&
+            this._found_p3)
+          this._main_loop.quit ();
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new FavouriteDetailsInterfaceTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/favourite-updates.vala b/tests/tracker/favourite-updates.vala
new file mode 100644
index 0000000..32fd628
--- /dev/null
+++ b/tests/tracker/favourite-updates.vala
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class FavouriteUpdatesTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private bool _is_favourite_1;
+  private string _individual_id_1;
+  private bool _is_favourite_2;
+  private string _individual_id_2;
+  private string _initial_fullname_1;
+  private string _contact_urn_1;
+  private string _initial_fullname_2;
+  private string _contact_urn_2;
+
+  public FavouriteUpdatesTests ()
+    {
+      base ("FavouriteUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("favourite update", this.test_favourite_update);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_favourite_update ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      Gee.HashMap<string, string> c2 = new Gee.HashMap<string, string> ();
+      this._initial_fullname_1 = "persona #1";
+      this._contact_urn_1 = "<urn:contact001>";
+      this._initial_fullname_2 = "persona #2";
+      this._contact_urn_2 = "<urn:contact002>";
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn_1);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname_1);
+      this._tracker_backend.add_contact (c1);
+
+      c2.set (TrackerTest.Backend.URN, this._contact_urn_2);
+      c2.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname_2);
+      c2.set (Trf.OntologyDefs.NAO_TAG, "");
+      this._tracker_backend.add_contact (c2);
+
+      this._tracker_backend.set_up ();
+
+      this._is_favourite_1 = false;
+      this._individual_id_1 = "";
+      this._is_favourite_2 = true;
+      this._individual_id_2 = "";
+
+      this._test_favourite_update_async ();
+
+      // this timer is slightly higher than usual because sleep
+      // to ensure a (usually delayed) INSERT event has happened
+      Timeout.add_seconds (7, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._is_favourite_1 == true);
+      assert (this._is_favourite_2 == false);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_favourite_update_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          if (i.full_name == this._initial_fullname_1)
+            {
+              i.notify["is-favourite"].connect
+                  (this._notify_favourite_cb);
+              this._individual_id_1 = i.id;
+              this._tracker_backend.update_favourite (this._contact_urn_1,
+                  true);
+            }
+          else if (i.full_name == this._initial_fullname_2)
+            {
+              i.notify["is-favourite"].connect
+                  (this._notify_favourite_cb);
+              this._individual_id_2 = i.id;
+              // HACK: we need to make sure the INSERT event was delivered
+              Timeout.add_seconds (1, () =>
+                  {
+                    this._tracker_backend.update_favourite
+                        (this._contact_urn_2, false);
+                    return false;
+                  });
+            }
+        }
+
+        assert (removed == null);
+    }
+
+  private void _notify_favourite_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      if (i.id == this._individual_id_1)
+        {
+          this._is_favourite_1 = i.is_favourite;
+        }
+      else if (i.id == this._individual_id_2)
+        {
+          this._is_favourite_2 = i.is_favourite;
+        }
+
+      if (this._is_favourite_1 == true &&
+          this._is_favourite_2 == false)
+        this._main_loop.quit ();
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new FavouriteUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/fullname-updates.vala b/tests/tracker/fullname-updates.vala
new file mode 100644
index 0000000..30cfd63
--- /dev/null
+++ b/tests/tracker/fullname-updates.vala
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class FullnameUpdatesTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private bool _updated_name_found;
+  private bool _deleted_name_found;
+  private string _updated_fullname;
+  private string _individual_id;
+  private string _initial_fullname;
+  private string _contact_urn;
+  private bool _initial_name_found;
+
+  public FullnameUpdatesTests ()
+    {
+      base ("FullnameUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("fullname updates", this.test_fullname_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_fullname_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      this._initial_fullname = "persona #1";
+      this._updated_fullname = "updated persona #1";
+      this._contact_urn = "<urn:contact001>";
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._initial_name_found = false;
+      this._updated_name_found = false;
+      this._deleted_name_found = false;
+      this._individual_id = "";
+
+      this._test_fullname_updates_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_name_found == true);
+      assert (this._updated_name_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_fullname_updates_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          if (i.full_name == this._initial_fullname)
+            {
+              i.notify["full-name"].connect (this._notify_full_name_cb);
+              this._individual_id = i.id;
+              this._initial_name_found = true;
+              this._tracker_backend.update_contact (this._contact_urn,
+                  Trf.OntologyDefs.NCO_FULLNAME, this._updated_fullname);
+            }
+        }
+
+        assert (removed == null);
+    }
+
+  private void _notify_full_name_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+
+      if (i.full_name == this._updated_fullname)
+        {
+          this._updated_name_found = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new FullnameUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/gender-details-interface.vala b/tests/tracker/gender-details-interface.vala
new file mode 100644
index 0000000..5644277
--- /dev/null
+++ b/tests/tracker/gender-details-interface.vala
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class GenderDetailsInterfaceTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private TrackerTest.Backend _tracker_backend;
+  private bool _found_gender;
+  private string _gender;
+  private string _fullname;
+
+  public GenderDetailsInterfaceTests ()
+    {
+      base ("GenderDetailsInterfaceTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("test gender details interface",
+          this.test_gender_details_interface);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_gender_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._fullname = "persona #1";
+      this._gender = Trf.OntologyDefs.NCO_MALE;
+
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._fullname);
+      c1.set (Trf.OntologyDefs.NCO_GENDER, this._gender);
+      this._tracker_backend.add_contact (c1);
+      this._tracker_backend.set_up ();
+
+      this._found_gender = false;
+
+      this._test_gender_details_interface_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          return false;
+        });
+
+      this._main_loop.run ();
+
+      assert (this._found_gender == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_gender_details_interface_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          if (i.full_name == this._fullname)
+            {
+              i.notify["gender"].connect (this._notify_gender_cb);
+              if (i.gender == Gender.MALE)
+                {
+                  this._found_gender = true;
+                  this._main_loop.quit ();
+                }
+            }
+        }
+      assert (removed == null);
+    }
+
+  void _notify_gender_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual individual = (Folks.Individual) individual_obj;
+      if (individual.gender == Gender.MALE)
+        {
+          this._found_gender = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new GenderDetailsInterfaceTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/given-name-updates.vala b/tests/tracker/given-name-updates.vala
new file mode 100644
index 0000000..463fb0d
--- /dev/null
+++ b/tests/tracker/given-name-updates.vala
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class GivenNameUpdatesTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private bool _updated_given_name_found;
+  private string _updated_given_name;
+  private string _individual_id;
+  private string _initial_fullname;
+  private string _initial_given_name;
+  private string _contact_urn;
+  private bool _initial_given_name_found;
+
+  public GivenNameUpdatesTests ()
+    {
+      base ("GivenNameUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("given name updates", this.test_given_name_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_given_name_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._initial_fullname = "persona #1";
+      this._initial_given_name = "given name #1";
+      this._updated_given_name = "updated given #1";
+      this._contact_urn = "<urn:contact001>";
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname);
+      c1.set (Trf.OntologyDefs.NCO_GIVEN, this._initial_given_name);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._initial_given_name_found = false;
+      this._updated_given_name_found = false;
+      this._individual_id = "";
+
+      this._test_given_name_updates_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_given_name_found == true);
+      assert (this._updated_given_name_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_given_name_updates_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          i.structured_name.notify["given-name"].connect
+              (this._notify_given_name_cb);
+          var given_name = i.structured_name.given_name;
+          if (given_name == this._initial_given_name)
+            {
+              this._individual_id = i.id;
+              this._initial_given_name_found = true;
+              this._tracker_backend.update_contact (this._contact_urn,
+                  Trf.OntologyDefs.NCO_GIVEN, this._updated_given_name);
+            }
+        }
+
+      assert (removed == null);
+    }
+
+  private void _notify_given_name_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.StructuredName structured_name =
+          (Folks.StructuredName) individual_obj;
+
+      var given_name = structured_name.given_name;
+      if (given_name == this._updated_given_name)
+        {
+          this._updated_given_name_found = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new GivenNameUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/im-details-interface.vala b/tests/tracker/im-details-interface.vala
new file mode 100644
index 0000000..a1b6acd
--- /dev/null
+++ b/tests/tracker/im-details-interface.vala
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class ImDetailsInterfaceTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private int _num_addrs;
+  private bool _found_addr_1;
+  private bool _found_addr_2;
+  private string _fullname;
+
+  public ImDetailsInterfaceTests ()
+    {
+      base ("ImDetailsInterfaceTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+
+      this.add_test ("test im details interface",
+          this.test_im_details_interface);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_im_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._fullname = "persona #1";
+
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._fullname);
+      c1.set (Trf.OntologyDefs.NCO_IMADDRESS,
+          "jabber#test1 example org,aim#test2 example org");
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._num_addrs = 0;
+      this._found_addr_1 = false;
+      this._found_addr_2 = false;
+
+      this._test_im_details_interface_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._num_addrs == 2);
+      assert (this._found_addr_1 == true);
+      assert (this._found_addr_2 == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_im_details_interface_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          string full_name = i.full_name;
+          if (full_name == this._fullname)
+            {
+              foreach (var proto in i.im_addresses.get_keys ())
+                {
+                  var addrs = i.im_addresses.lookup (proto);
+
+                  if (proto == "jabber")
+                    {
+                      if (addrs.contains ("test1 example org"))
+                        {
+                          this._found_addr_1 = true;
+                          this._num_addrs++;
+                        }
+                    }
+                  else if (proto == "aim")
+                    {
+                      if (addrs.contains ("test2 example org"))
+                        {
+                          this._found_addr_2 = true;
+                          this._num_addrs++;
+                        }
+                    }
+                }
+            }
+        }
+
+      assert (removed == null);
+
+      if (this._num_addrs == 2 &&
+          this._found_addr_1 == true &&
+          this._found_addr_2 == true)
+        this._main_loop.quit ();
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new ImDetailsInterfaceTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/imaddresses-updates.vala b/tests/tracker/imaddresses-updates.vala
new file mode 100644
index 0000000..1eb29d1
--- /dev/null
+++ b/tests/tracker/imaddresses-updates.vala
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class IMAddressesUpdatesTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private string _initial_fullname_1;
+  private string _contact_urn_1;
+  private string _imaddress_proto_1;
+  private string _imaddress_1;
+  private string _imaddress_2;
+  private string _proto_2;
+  private string _individual_id;
+  private bool _initial_imaddress_found;
+  private bool _updated_imaddr_found;
+
+  public IMAddressesUpdatesTests ()
+    {
+      base ("IMAddressesUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("im addresses updates", this.test_imaddresses_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_imaddresses_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._initial_fullname_1 = "persona #1";
+      this._contact_urn_1 = "<urn:contact001>";
+      this._imaddress_proto_1 = "jabber#test1 example org";
+      this._imaddress_1 = "test1 example org";
+      this._imaddress_2 = "test2 example org";
+      this._proto_2 = "aim";
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn_1);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname_1);
+      c1.set (Trf.OntologyDefs.NCO_IMADDRESS, this._imaddress_proto_1);
+      this._tracker_backend.add_contact (c1);
+      this._tracker_backend.set_up ();
+
+      this._individual_id = "";
+      this._initial_imaddress_found = false;
+      this._updated_imaddr_found = false;
+
+      this._test_imaddresses_updates_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_imaddress_found == true);
+      assert (this._updated_imaddr_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_imaddresses_updates_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          if (i.full_name == this._initial_fullname_1)
+            {
+              this._individual_id = i.id;
+
+              foreach (unowned string proto in i.im_addresses.get_keys ())
+                {
+                  var addrs = i.im_addresses.lookup (proto);
+                  var im_address_iter = addrs.get (0);
+                  if (im_address_iter == this._imaddress_1)
+                    {
+                      i.notify["im-addresses"].connect (this._notify_im_cb);
+                      this._initial_imaddress_found = true;
+                      this._do_im_addr_update ();
+                    }
+                }
+            }
+        }
+
+      assert (removed == null);
+    }
+
+  private void _notify_im_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      foreach (unowned string proto in i.im_addresses.get_keys ())
+        {
+          var addrs = i.im_addresses.lookup (proto);
+          var im_address_iter = addrs.get (0);
+          if (im_address_iter == this._imaddress_2)
+            {
+              this._updated_imaddr_found = true;
+              this._main_loop.quit ();
+            }
+        }
+    }
+
+  private void _do_im_addr_update ()
+    {
+      var urn_affil_1 = "<" + this._imaddress_1 + "myaffiliation>";
+      this._tracker_backend.remove_triplet (this._contact_urn_1,
+          Trf.OntologyDefs.NCO_HAS_AFFILIATION, urn_affil_1);
+
+      var urn_imaddr_2 = "<" + this._imaddress_2 + ">";
+      this._tracker_backend.insert_triplet
+          (urn_imaddr_2,
+           "a", Trf.OntologyDefs.NCO_IMADDRESS,
+           Trf.OntologyDefs.NCO_IMPROTOCOL, this._proto_2,
+           Trf.OntologyDefs.NCO_IMID, this._imaddress_2);
+
+      var urn_affil_2 = "<" + this._imaddress_2;
+      urn_affil_2 += "myaffiliation>";
+      this._tracker_backend.insert_triplet
+          (urn_affil_2,
+          "a", Trf.OntologyDefs.NCO_AFFILIATION);
+
+     this._tracker_backend.insert_triplet
+         (urn_affil_2,
+         Trf.OntologyDefs.NCO_HAS_IMADDRESS, urn_imaddr_2);
+
+     this._tracker_backend.insert_triplet
+         (this._contact_urn_1,
+          Trf.OntologyDefs.NCO_HAS_AFFILIATION, urn_affil_2);
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new IMAddressesUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/individual-retrieval.vala b/tests/tracker/individual-retrieval.vala
new file mode 100644
index 0000000..41f80bf
--- /dev/null
+++ b/tests/tracker/individual-retrieval.vala
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class IndividualRetrievalTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private TrackerTest.Backend _tracker_backend;
+  private Gee.HashMap<string, string> _c1;
+  private Gee.HashMap<string, string> _c2;
+
+  public IndividualRetrievalTests ()
+    {
+      base ("IndividualRetrieval");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+
+      this.add_test ("singleton individuals", this.test_singleton_individuals);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_singleton_individuals ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      this._c1 = new Gee.HashMap<string, string> ();
+      this._c2 = new Gee.HashMap<string, string> ();
+
+      this._c1.set (Trf.OntologyDefs.NCO_FULLNAME, "persona #1");
+      this._tracker_backend.add_contact (this._c1);
+      this._c2.set (Trf.OntologyDefs.NCO_FULLNAME, "persona #2");
+      this._tracker_backend.add_contact (this._c2);
+      this._tracker_backend.set_up ();
+
+      this._test_singleton_individuals_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._c1.size == 0);
+      assert (this._c2.size == 0);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_singleton_individuals_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          string full_name = ((Folks.NameDetails) i).full_name;
+          if (full_name != null)
+            {
+              if (this._c1.get (Trf.OntologyDefs.NCO_FULLNAME) == full_name)
+                {
+                  this._c1.unset (Trf.OntologyDefs.NCO_FULLNAME);
+                }
+
+              if (this._c2.get (Trf.OntologyDefs.NCO_FULLNAME) == full_name)
+                {
+                  this._c2.unset (Trf.OntologyDefs.NCO_FULLNAME);
+                }
+            }
+        }
+
+        if (this._c1.size == 0 &&
+            this._c2.size == 0)
+          this._main_loop.quit ();
+
+        assert (removed == null);
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new IndividualRetrievalTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/name-details-interface.vala b/tests/tracker/name-details-interface.vala
new file mode 100644
index 0000000..0aa6eb5
--- /dev/null
+++ b/tests/tracker/name-details-interface.vala
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class NameDetailsInterfaceTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private Gee.HashMap<string, string> _c1;
+  private Gee.HashMap<string, string> _c2;
+
+  public NameDetailsInterfaceTests ()
+    {
+      base ("NameDetailsInterfaceTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+
+      this.add_test ("test name details interface",
+          this.test_name_details_interface);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_name_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      this._c1 = new Gee.HashMap<string, string> ();
+      this._c2 = new Gee.HashMap<string, string> ();
+
+      this._c1.set (Trf.OntologyDefs.NCO_FULLNAME, "persona #1");
+      this._c1.set (Trf.OntologyDefs.NCO_NICKNAME, "p #1");
+      this._c1.set (Trf.OntologyDefs.NCO_FAMILY, "p #1 Family");
+      this._c1.set (Trf.OntologyDefs.NCO_GIVEN, "p #1 Given");
+      this._c1.set (Trf.OntologyDefs.NCO_ADDITIONAL, "p #1 Additional");
+      this._c1.set (Trf.OntologyDefs.NCO_PREFIX, "Mr");
+      this._c1.set (Trf.OntologyDefs.NCO_SUFFIX, "Jr");
+      this._tracker_backend.add_contact (this._c1);
+
+      this._c2.set (Trf.OntologyDefs.NCO_FULLNAME, "persona #2");
+      this._c2.set (Trf.OntologyDefs.NCO_NICKNAME, "p #2");
+      this._tracker_backend.add_contact (this._c2);
+
+      this._tracker_backend.set_up ();
+
+      this._test_name_details_interface_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._c1.size == 0);
+      assert (this._c2.size == 0);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_name_details_interface_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          string full_name = ((Folks.NameDetails) i).full_name;
+          if (full_name != null)
+            {
+              StructuredName sname =
+                  ((Folks.NameDetails) i).structured_name;
+
+               if (full_name == "persona #1")
+                 {
+                   this._c1.unset (Trf.OntologyDefs.NCO_FULLNAME);
+
+                   string nickname = ((Folks.NameDetails) i).nickname;
+                   assert (this._c1.get (Trf.OntologyDefs.NCO_NICKNAME) ==
+                       nickname);
+                   this._c1.unset (Trf.OntologyDefs.NCO_NICKNAME);
+
+                   string family = sname.family_name ;
+                   assert (this._c1.get (Trf.OntologyDefs.NCO_FAMILY) ==
+                       family);
+                   this._c1.unset (Trf.OntologyDefs.NCO_FAMILY);
+
+                   string given = sname.given_name;
+                   assert (this._c1.get (Trf.OntologyDefs.NCO_GIVEN) == given);
+                   this._c1.unset (Trf.OntologyDefs.NCO_GIVEN);
+
+                   string additional = sname.additional_names;
+                   assert (this._c1.get (Trf.OntologyDefs.NCO_ADDITIONAL) ==
+                       additional);
+                   this._c1.unset (Trf.OntologyDefs.NCO_ADDITIONAL);
+
+                   string prefix = sname.prefixes;
+                   assert (this._c1.get (Trf.OntologyDefs.NCO_PREFIX) ==
+                       prefix);
+                   this._c1.unset (Trf.OntologyDefs.NCO_PREFIX);
+
+                   string suffix = sname.suffixes;
+                   assert (this._c1.get (Trf.OntologyDefs.NCO_SUFFIX) ==
+                       suffix);
+                   this._c1.unset (Trf.OntologyDefs.NCO_SUFFIX);
+
+                   assert (sname.is_empty () == false);
+                 }
+               else if (full_name == "persona #2")
+                 {
+                   this._c2.unset (Trf.OntologyDefs.NCO_FULLNAME);
+
+                   string nickname = ((Folks.NameDetails) i).nickname;
+                   assert (this._c2.get (Trf.OntologyDefs.NCO_NICKNAME) ==
+                       nickname);
+                   this._c2.unset (Trf.OntologyDefs.NCO_NICKNAME);
+
+                   assert (sname.is_empty () == true);
+                 }
+            }
+        }
+
+      assert (removed == null);
+
+      if (this._c1.size == 0 &&
+          this._c2.size == 0)
+        this._main_loop.quit ();
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new NameDetailsInterfaceTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/nickname-updates.vala b/tests/tracker/nickname-updates.vala
new file mode 100644
index 0000000..e3e39d4
--- /dev/null
+++ b/tests/tracker/nickname-updates.vala
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class NicknameUpdatesTests : Folks.TestCase
+{
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private bool _updated_nickname_found;
+  private bool _initial_nickname_found = false;
+  private string _updated_nickname;
+  private string _individual_id;
+  private GLib.MainLoop _main_loop;
+  private string _initial_fullname;
+  private string _initial_nickname;
+  private string _contact_urn;
+
+  public NicknameUpdatesTests ()
+    {
+      base ("NicknameUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("nickname updates", this.test_nickname_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_nickname_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._initial_fullname = "persona #1";
+      this._initial_nickname = "nickname #1";
+      this._updated_nickname = "updated nickname #1";
+      this._contact_urn = "<urn:contact001>";
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname);
+      c1.set (Trf.OntologyDefs.NCO_NICKNAME, this._initial_nickname);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._initial_nickname_found = false;
+      this._updated_nickname_found = false;
+      this._individual_id = "";
+
+      this._test_nickname_updates_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_nickname_found == true);
+      assert (this._updated_nickname_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_nickname_updates_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          if (i.nickname == this._initial_nickname)
+            {
+              i.notify["nickname"].connect (this._notify_nickname_cb);
+              this._individual_id = i.id;
+              this._initial_nickname_found = true;
+              this._tracker_backend.update_contact (this._contact_urn,
+                  Trf.OntologyDefs.NCO_NICKNAME, this._updated_nickname);
+            }
+        }
+
+      assert (removed == null);
+    }
+
+  private void _notify_nickname_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      if (i.nickname == this._updated_nickname)
+        {
+          this._updated_nickname_found = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new NicknameUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/note-details-interface.vala b/tests/tracker/note-details-interface.vala
new file mode 100644
index 0000000..da8ec55
--- /dev/null
+++ b/tests/tracker/note-details-interface.vala
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class NoteDetailsInterfaceTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private TrackerTest.Backend _tracker_backend;
+  private bool _found_note;
+  private string _note;
+  private string _fullname = "persona #1";
+
+  public NoteDetailsInterfaceTests ()
+    {
+      base ("NoteDetailsInterfaceTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("test note details interface",
+          this.test_note_details_interface);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_note_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._fullname = "persona #1";
+      this._note = "this is a note";
+
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._fullname);
+      c1.set (Trf.OntologyDefs.NCO_NOTE, this._note);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._found_note = false;
+
+      this._test_note_details_interface_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._found_note == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_note_details_interface_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          if (i.full_name == this._fullname)
+            {
+              i.notify["notes"].connect (this._notify_note_cb);
+              foreach (var n in i.notes)
+                {
+                  if (n.content == this._note)
+                    {
+                      this._found_note = true;
+                      this._main_loop.quit ();
+                    }
+                }
+            }
+        }
+
+      assert (removed == null);
+    }
+
+  void _notify_note_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual individual = (Folks.Individual) individual_obj;
+      foreach (var n in individual.notes)
+        {
+          if (n.content == this._note)
+            {
+              this._found_note = true;
+              this._main_loop.quit ();
+            }
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new NoteDetailsInterfaceTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/phone-details-interface.vala b/tests/tracker/phone-details-interface.vala
new file mode 100644
index 0000000..e37cc2c
--- /dev/null
+++ b/tests/tracker/phone-details-interface.vala
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class PhoneDetailsInterfaceTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private TrackerTest.Backend _tracker_backend;
+  private int _num_phones = 0;
+  private bool _found_phone_1 = false;
+  private bool _found_phone_2 = false;
+
+  public PhoneDetailsInterfaceTests ()
+    {
+      base ("PhoneDetailsInterfaceTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+
+      this.add_test ("test phone details interface",
+          this.test_phone_details_interface);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_phone_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, "persona #1");
+      c1.set (Trf.OntologyDefs.NCO_PHONE_PROP, "12345,54321");
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._num_phones = 0;
+      this._found_phone_1 = false;
+      this._found_phone_2 = false;
+
+      this._test_phone_details_interface_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._num_phones == 2);
+      assert (this._found_phone_1 == true);
+      assert (this._found_phone_2 == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_phone_details_interface_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          string full_name = i.full_name;
+          if (full_name != null)
+            {
+              foreach (var phone in i.phone_numbers)
+              {
+                if (phone.value == "12345")
+                  {
+                    this._found_phone_1 = true;
+                    this._num_phones++;
+                  }
+                else if (phone.value == "54321")
+                  {
+                    this._found_phone_2 = true;
+                    this._num_phones++;
+                  }
+              }
+            }
+        }
+
+      assert (removed == null);
+
+      if (this._num_phones == 2 &&
+          this._found_phone_1 == true &&
+          this._found_phone_2 == true)
+        this._main_loop.quit ();
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new PhoneDetailsInterfaceTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/phones-updates.vala b/tests/tracker/phones-updates.vala
new file mode 100644
index 0000000..c2113d6
--- /dev/null
+++ b/tests/tracker/phones-updates.vala
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class PhonesUpdatesTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private string _initial_fullname_1;
+  private string _contact_urn_1;
+  private string _phone_1;
+  private string _phone_2;
+  private string _individual_id;
+  private bool _initial_phone_found;
+  private bool _initial_phone_found_again;
+  private bool _updated_phone_found;
+
+  public PhonesUpdatesTests ()
+    {
+      base ("PhonesUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("phones updates", this.test_phones_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_phones_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._initial_fullname_1 = "persona #1";
+      this._contact_urn_1 = "<urn:contact001>";
+      this._phone_1 = "12345";
+      this._phone_2 = "54321";
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn_1);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname_1);
+      c1.set (Trf.OntologyDefs.NCO_PHONE_PROP, this._phone_1);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._individual_id = "";
+      this._initial_phone_found = false;
+      this._initial_phone_found_again = false;
+      this._updated_phone_found = false;
+
+      this._test_phones_updates_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_phone_found == true);
+      assert (this._initial_phone_found_again == false);
+      assert (this._updated_phone_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_phones_updates_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          if (i.full_name == this._initial_fullname_1)
+            {
+              this._individual_id = i.id;
+
+              i.notify["phone-numbers"].connect (this._notify_phones_cb);
+
+              foreach (unowned FieldDetails fd in i.phone_numbers)
+                {
+                  var phone = fd.value;
+                  if (phone == this._phone_1)
+                    {
+                      this._initial_phone_found = true;
+                      this._update_phone ();
+                    }
+                }
+            }
+        }
+      assert (removed == null);
+    }
+
+  private void _notify_phones_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      foreach (unowned FieldDetails fd in i.phone_numbers)
+        {
+          var phone = fd.value;
+          if (phone == this._phone_1)
+            {
+              this._initial_phone_found_again = true;
+            }
+          if (phone == this._phone_2)
+            {
+              this._updated_phone_found = true;
+              this._main_loop.quit ();
+            }
+        }
+    }
+
+  private void _update_phone ()
+    {
+      var urn_phone_1 = "<" + this._phone_1 + ">";
+      this._tracker_backend.remove_triplet (this._contact_urn_1,
+          Trf.OntologyDefs.NCO_HAS_AFFILIATION, urn_phone_1);
+
+      var urn_phone_2 = "<phone:" + this._phone_2 + ">";
+      this._tracker_backend.insert_triplet (urn_phone_2,
+          "a", Trf.OntologyDefs.NCO_PHONE,
+          Trf.OntologyDefs.NCO_PHONE_PROP,
+          this._phone_2);
+
+      var affl_2 = "<" + this._phone_2 + ">";
+      this._tracker_backend.insert_triplet
+          (affl_2,
+          "a", Trf.OntologyDefs.NCO_AFFILIATION);
+      this._tracker_backend.insert_triplet
+          (affl_2,
+          Trf.OntologyDefs.NCO_HAS_PHONE, urn_phone_2);
+
+      this._tracker_backend.insert_triplet
+          (this._contact_urn_1,
+          Trf.OntologyDefs.NCO_HAS_AFFILIATION, affl_2);
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new PhonesUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/postal-address-details-interface.vala b/tests/tracker/postal-address-details-interface.vala
new file mode 100644
index 0000000..8b9d023
--- /dev/null
+++ b/tests/tracker/postal-address-details-interface.vala
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class PostalAddressDetailsInterfaceTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private TrackerTest.Backend _tracker_backend;
+  private string _pobox = "12345";
+  private string _district = "example district";
+  private string _county = "example country";
+  private string _locality = "example locality";
+  private string _postalcode = "example postalcode";
+  private string _street = "example street";
+  private string _address = "example address";
+  private string _extended = "example extended address";
+  private string _country = "example country";
+  private string _region = "example region";
+  private PostalAddress _postal_address;
+  private bool _found_postal_address;
+  private string _fullname;
+
+  public PostalAddressDetailsInterfaceTests ()
+    {
+      base ("PostalAddressDetailsInterfaceTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+
+      this.add_test ("test postal address details interface",
+          this.test_postal_address_details_interface);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_postal_address_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._fullname = "persona #1";
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._fullname);
+
+      GLib.List<string> types = new GLib.List<string> ();
+      this._postal_address = new PostalAddress (
+           this._pobox,
+           this._extended,
+           this._street,
+           this._locality,
+           this._region,
+           this._postalcode,
+           this._country,
+           null, types, "tracker_id");
+
+      // nco:pobox, nco:district, nco:county, nco:locality, nco:postalcode,
+      // nco:streetAddress
+      // nco:addressLocation, nco:extendedAddress, nco:country, nco:region
+      string postal_info = this._pobox + ":";
+      postal_info += this._district + ":";
+      postal_info += this._county + ":";
+      postal_info += this._locality  + ":";
+      postal_info += this._postalcode  + ":";
+      postal_info += this._street  + ":";
+      postal_info += this._address  + ":";
+      postal_info += this._extended  + ":";
+      postal_info += this._country  + ":";
+      postal_info += this._region;
+
+      c1.set (Trf.OntologyDefs.NCO_POSTAL_ADDRESS, postal_info);
+      this._tracker_backend.add_contact (c1);
+      this._tracker_backend.set_up ();
+
+      this._found_postal_address = false;
+
+      this._test_postal_address_details_interface_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._found_postal_address == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_postal_address_details_interface_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          if (i.full_name == this._fullname)
+            {
+              foreach (var p in i.postal_addresses)
+              {
+                /* We copy the tracker_id - we don't know it.
+                 * Although we could get it from the 1st
+                 * personas iid; there is no real need.
+                 */
+                this._postal_address.uid = p.uid;
+
+                if (p.equal (this._postal_address))
+                  {
+                    this._found_postal_address = true;
+                    this._main_loop.quit ();
+                  }
+              }
+            }
+        }
+
+      assert (removed == null);
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new PostalAddressDetailsInterfaceTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/prefix-name-updates.vala b/tests/tracker/prefix-name-updates.vala
new file mode 100644
index 0000000..c547c94
--- /dev/null
+++ b/tests/tracker/prefix-name-updates.vala
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class PrefixNameUpdatesTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private bool _updated_prefix_name_found;
+  private bool _initial_prefix_name_found;
+  private string _updated_prefix_name;
+  private string _individual_id;
+  private string _initial_fullname;
+  private string _initial_prefix_name;
+  private string _contact_urn;
+
+  public PrefixNameUpdatesTests ()
+    {
+      base ("PrefixNameUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("prefix name updates", this.test_prefix_name_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_prefix_name_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._initial_fullname = "persona #1";
+      this._initial_prefix_name = "prefix name #1";
+      this._updated_prefix_name = "updated prefix #1";
+      this._contact_urn = "<urn:contact001>";
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname);
+      c1.set (Trf.OntologyDefs.NCO_PREFIX, this._initial_prefix_name);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._initial_prefix_name_found = false;
+      this._updated_prefix_name_found = false;
+      this._individual_id = "";
+
+      this._test_prefix_name_updates_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_prefix_name_found == true);
+      assert (this._updated_prefix_name_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_prefix_name_updates_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          var prefix_name = i.structured_name.prefixes;
+          if (prefix_name == this._initial_prefix_name)
+            {
+              i.structured_name.notify["prefixes"].connect
+                   (this._notify_prefix_name_cb);
+              this._individual_id = i.id;
+              this._initial_prefix_name_found = true;
+              this._tracker_backend.update_contact (this._contact_urn,
+                  Trf.OntologyDefs.NCO_PREFIX, this._updated_prefix_name);
+            }
+        }
+      assert (removed == null);
+    }
+
+  private void _notify_prefix_name_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.StructuredName sname = (Folks.StructuredName) individual_obj;
+      var prefix_name = sname.prefixes;
+      if (prefix_name == this._updated_prefix_name)
+        {
+          this._updated_prefix_name_found = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new PrefixNameUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/remove-contact.vala b/tests/tracker/remove-contact.vala
new file mode 100644
index 0000000..b94379c
--- /dev/null
+++ b/tests/tracker/remove-contact.vala
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class RemoveContactTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private bool _contact_added;
+  private bool _contact_removed;
+  private string _individual_id;
+  private string _persona_fullname;
+
+  public RemoveContactTests ()
+    {
+      base ("RemoveContactTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+
+      this.add_test ("test removing contacts ", this.test_remove_contact);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_remove_contact ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      this._persona_fullname = "persona #1";
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._persona_fullname);
+      this._tracker_backend.add_contact (c1);
+      this._tracker_backend.set_up ();
+
+      this._contact_added = false;
+      this._contact_removed = false;
+      this._individual_id = "";
+
+      this._test_remove_contact_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._contact_added == true);
+      assert (this._contact_removed == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_remove_contact_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          string full_name = i.full_name;
+          if (full_name == this._persona_fullname)
+            {
+              this._contact_added = true;
+              this._individual_id = i.id;
+              var contact_id = i.personas.nth_data (0).iid.split (":")[1];
+              this._tracker_backend.remove_contact (contact_id);
+            }
+        }
+
+      foreach (unowned Individual i in added)
+        {
+          if (i.id == this._individual_id)
+            {
+              this._contact_removed = true;
+              this._main_loop.quit ();
+            }
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new RemoveContactTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/role-details-interface.vala b/tests/tracker/role-details-interface.vala
new file mode 100644
index 0000000..ebe9c1a
--- /dev/null
+++ b/tests/tracker/role-details-interface.vala
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class RoleDetailsInterfaceTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private TrackerTest.Backend _tracker_backend;
+  private bool _found_role;
+  private string _fullname;
+  private string _affiliaton;
+
+  public RoleDetailsInterfaceTests ()
+    {
+      base ("RoleDetailsInterfaceTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("test role details interface",
+          this.test_role_details_interface);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_role_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._fullname = "persona #1";
+      this._affiliaton = "boss,Company";
+
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._fullname);
+      c1.set (Trf.OntologyDefs.NCO_HAS_AFFILIATION, this._affiliaton);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._found_role = false;
+
+      this._test_role_details_interface_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._found_role == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_role_details_interface_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          if (i.full_name == this._fullname)
+            {
+              foreach (var role in i.roles)
+                {
+                  if (role.title == "boss" &&
+                      role.organisation_name == "Company")
+                    {
+                      this._found_role = true;
+                      this._main_loop.quit ();
+                    }
+                }
+            }
+        }
+
+      assert (removed == null);
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new RoleDetailsInterfaceTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/suffix-name-updates.vala b/tests/tracker/suffix-name-updates.vala
new file mode 100644
index 0000000..0696567
--- /dev/null
+++ b/tests/tracker/suffix-name-updates.vala
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class SuffixNameUpdatesTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private bool _updated_suffix_name_found;
+  private bool _deleted_suffix_name_found;
+  private bool _initial_suffix_name_found;
+  private string _updated_suffix_name;
+  private string _individual_id;
+  private string _initial_fullname;
+  private string _initial_suffix_name;
+  private string _contact_urn;
+
+  public SuffixNameUpdatesTests ()
+    {
+      base ("SuffixNameUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("suffix name updates", this.test_suffix_name_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_suffix_name_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._initial_fullname = "persona #1";
+      this._initial_suffix_name = "suffix name #1";
+      this._updated_suffix_name = "updated suffix #1";
+      this._contact_urn = "<urn:contact001>";
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname);
+      c1.set (Trf.OntologyDefs.NCO_SUFFIX, this._initial_suffix_name);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._initial_suffix_name_found = false;
+      this._updated_suffix_name_found = false;
+      this._deleted_suffix_name_found = false;
+      this._individual_id = "";
+
+      this._test_suffix_name_updates_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_suffix_name_found == true);
+      assert (this._updated_suffix_name_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_suffix_name_updates_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+              var suffix_name = i.structured_name.suffixes;
+              if (suffix_name == this._initial_suffix_name)
+                {
+                  i.structured_name.notify["suffixes"].connect
+                      (this._notify_suffix_name_cb);
+                  this._individual_id = i.id;
+                  this._initial_suffix_name_found = true;
+                  this._tracker_backend.update_contact (this._contact_urn,
+                      Trf.OntologyDefs.NCO_SUFFIX, this._updated_suffix_name);
+                }
+        }
+
+      assert (removed == null);
+    }
+
+  private void _notify_suffix_name_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.StructuredName sname = (Folks.StructuredName) individual_obj;
+      var suffix_name = sname.suffixes;
+      if (suffix_name == this._updated_suffix_name)
+        {
+          this._updated_suffix_name_found = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new SuffixNameUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/url-details-interface.vala b/tests/tracker/url-details-interface.vala
new file mode 100644
index 0000000..0d17caf
--- /dev/null
+++ b/tests/tracker/url-details-interface.vala
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class UrlDetailsInterfaceTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private TrackerTest.Backend _tracker_backend;
+  private string _blog_url;
+  private string _website_url;
+  private string _urls;
+  private bool _found_blog;
+  private bool _found_website;
+
+  public UrlDetailsInterfaceTests ()
+    {
+      base ("UrlDetailsInterfaceTests");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("test url details interface",
+          this.test_url_details_interface);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_url_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._blog_url = "http://blog.example.org";;
+      this._website_url = "http://www.example.org";;
+      this._urls = "%s,%s".printf (this._blog_url, this._website_url);
+
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, "persona #1");
+      c1.set (TrackerTest.Backend.URLS, this._urls);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._found_blog = false;
+      this._found_website = false;
+
+      this._test_url_details_interface_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._found_blog == true);
+      assert (this._found_website == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_url_details_interface_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          string full_name = i.full_name;
+          if (full_name != null)
+            {
+              foreach (var url in i.urls)
+                {
+                  if (url.value == this._blog_url)
+                    {
+                      this._found_blog = true;
+                    }
+                  else if (url.value == this._website_url)
+                    {
+                      this._found_website = true;
+                    }
+                }
+            }
+        }
+
+      assert (removed == null);
+
+      if (this._found_blog &&
+          this._found_website)
+        this._main_loop.quit ();
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new UrlDetailsInterfaceTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/tracker/website-updates.vala b/tests/tracker/website-updates.vala
new file mode 100644
index 0000000..d7689d8
--- /dev/null
+++ b/tests/tracker/website-updates.vala
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ */
+
+using Tracker.Sparql;
+using TrackerTest;
+using Folks;
+using Gee;
+
+public class WebsiteUpdatesTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private TrackerTest.Backend _tracker_backend;
+  private IndividualAggregator _aggregator;
+  private bool _updated_website_found;
+  private bool _deleted_website_found;
+  private bool _initial_website_found;
+  private string _updated_website;
+  private string _individual_id;
+  private string _initial_fullname;
+  private string _initial_website;
+  private string _contact_urn;
+
+  public WebsiteUpdatesTests ()
+    {
+      base ("WebsiteUpdates");
+
+      this._tracker_backend = new TrackerTest.Backend ();
+      this._tracker_backend.debug = false;
+
+      this.add_test ("websites updates", this.test_website_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_website_updates ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      this._initial_fullname = "persona #1";
+      this._initial_website = "www.example1.org";
+      this._updated_website = "www.example2.org";
+      this._contact_urn = "<urn:contact001>";
+
+      c1.set (TrackerTest.Backend.URN, this._contact_urn);
+      c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname);
+      c1.set (TrackerTest.Backend.URLS, this._initial_website);
+      this._tracker_backend.add_contact (c1);
+
+      this._tracker_backend.set_up ();
+
+      this._initial_website_found = false;
+      this._updated_website_found = false;
+      this._deleted_website_found = false;
+      this._individual_id = "";
+
+      this._test_website_updates_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._initial_website_found == true);
+      assert (this._updated_website_found == true);
+
+      this._tracker_backend.tear_down ();
+    }
+
+  private async void _test_website_updates_async ()
+    {
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (GLib.List<Individual>? added,
+       GLib.List<Individual>? removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (unowned Individual i in added)
+        {
+          if (i.full_name == this._initial_fullname)
+            {
+              i.notify["urls"].connect
+                  (this._notify_website_cb);
+
+              this._individual_id = i.id;
+
+              foreach (unowned FieldDetails fd in i.urls)
+                {
+                  var website = fd.value;
+                  if (website == this._initial_website)
+                    {
+                      this._initial_website_found = true;
+                      string affl = "<affl:website>";
+                      this._tracker_backend.insert_triplet (affl,
+                          "a", Trf.OntologyDefs.NCO_AFFILIATION,
+                          Trf.OntologyDefs.NCO_WEBSITE, this._updated_website);
+                      this._tracker_backend.insert_triplet (this._contact_urn,
+                          Trf.OntologyDefs.NCO_HAS_AFFILIATION,
+                          affl);
+                    }
+                }
+            }
+        }
+
+      assert (removed == null);
+    }
+
+  private void _notify_website_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+
+      foreach (unowned FieldDetails fd in i.urls)
+        {
+          var website = fd.value;
+          if (website == this._updated_website)
+            {
+              this._updated_website_found = true;
+              this._main_loop.quit ();
+            }
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new WebsiteUpdatesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]