[folks] Add an EDS backend



commit 31f40df6da4766401b576635a8a632db84ee0a01
Author: Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
Date:   Tue Jul 12 19:00:00 2011 +0100

    Add an EDS backend
    
    Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=638281

 NEWS                                         |    1 +
 backends/Makefile.am                         |    5 +
 backends/eds/Makefile.am                     |   67 +++
 backends/eds/backend.mk                      |    1 +
 backends/eds/eds-backend-factory.vala        |   64 ++
 backends/eds/eds-backend.vala                |  178 ++++++
 backends/eds/lib/Makefile.am                 |  116 ++++
 backends/eds/lib/edsf-persona-store.vala     |  811 ++++++++++++++++++++++++++
 backends/eds/lib/edsf-persona.vala           |  806 +++++++++++++++++++++++++
 backends/eds/lib/folks-eds-uninstalled.pc.in |   12 +
 backends/eds/lib/folks-eds.deps              |    6 +
 backends/eds/lib/folks-eds.pc.in             |   15 +
 configure.ac                                 |   37 ++-
 tests/Makefile.am                            |    5 +
 tests/eds/Makefile.am                        |  211 +++++++
 tests/eds/add-contacts-stress-test.vala      |  200 +++++++
 tests/eds/add-persona.vala                   |  394 +++++++++++++
 tests/eds/avatar-details.vala                |  154 +++++
 tests/eds/data/backend-eds-only.ini          |    5 +
 tests/eds/email-details.vala                 |  210 +++++++
 tests/eds/im-details.vala                    |  169 ++++++
 tests/eds/individual-retrieval.vala          |  143 +++++
 tests/eds/link-personas.vala                 |  408 +++++++++++++
 tests/eds/name-details.vala                  |  220 +++++++
 tests/eds/persona-store-tests.vala           |  214 +++++++
 tests/eds/phone-details.vala                 |  214 +++++++
 tests/eds/postal-address-details.vala        |  177 ++++++
 tests/eds/remove-persona.vala                |  203 +++++++
 tests/eds/removing-contacts.vala             |  135 +++++
 tests/eds/set-avatar.vala                    |  167 ++++++
 tests/eds/set-emails.vala                    |  154 +++++
 tests/eds/set-full-name.vala                 |  149 +++++
 tests/eds/set-im-addresses.vala              |  156 +++++
 tests/eds/set-notes.vala                     |  155 +++++
 tests/eds/set-phones.vala                    |  154 +++++
 tests/eds/set-postal-addresses.vala          |  166 ++++++
 tests/eds/set-structured-name.vala           |  151 +++++
 tests/eds/updating-contacts.vala             |  156 +++++
 tests/lib/Makefile.am                        |    5 +
 tests/lib/eds/Makefile.am                    |   56 ++
 tests/lib/eds/backend.vala                   |  291 +++++++++
 tests/tools/eds.sh                           |   25 +
 tests/tools/with-session-bus-eds.sh          |   44 ++
 43 files changed, 7009 insertions(+), 1 deletions(-)
---
diff --git a/NEWS b/NEWS
index acf66b3..075bc18 100644
--- a/NEWS
+++ b/NEWS
@@ -14,6 +14,7 @@ Bugs fixed:
 * Bug 653325 â Build system does not complain if trying to build docs without
   vala-doc installed
 * Bug 653746 â mismatch between hash and equals in Note
+* Bug 638281 â Add an EDS backend
 
 API changes:
 * Swf.Persona retains and exposes its libsocialweb Contact
diff --git a/backends/Makefile.am b/backends/Makefile.am
index ba51ad5..0aa6709 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -11,11 +11,16 @@ if ENABLE_TRACKER
 SUBDIRS += tracker
 endif
 
+if ENABLE_EDS
+SUBDIRS += eds
+endif
+
 DIST_SUBDIRS = \
 	telepathy \
 	key-file \
 	tracker \
 	libsocialweb \
+	eds \
 	$(NULL)
 
 -include $(top_srcdir)/git.mk
diff --git a/backends/eds/Makefile.am b/backends/eds/Makefile.am
new file mode 100644
index 0000000..e49f898
--- /dev/null
+++ b/backends/eds/Makefile.am
@@ -0,0 +1,67 @@
+SUBDIRS = lib
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/folks \
+	-I$(top_srcdir)/backends/eds/lib \
+	-include $(CONFIG_HEADER) \
+	-DPACKAGE_DATADIR=\"$(pkgdatadir)\" \
+	-DBACKEND_NAME=\"$(BACKEND_NAME)\" \
+	-DG_LOG_DOMAIN=\"$(BACKEND_NAME)\" \
+	$(NULL)
+
+VALAFLAGS += \
+	--vapidir=. \
+	--vapidir=$(top_builddir)/backends/eds/lib \
+	--vapidir=$(top_srcdir)/folks \
+	$(addprefix --pkg ,$(folks_backend_eds_deps)) \
+	$(NULL)
+
+backenddir = $(BACKEND_DIR)/eds
+backend_LTLIBRARIES = libfolks-backend-eds.la
+
+libfolks_backend_eds_la_SOURCES = \
+	eds-backend.vala \
+	eds-backend-factory.vala \
+	$(NULL)
+
+folks_backend_eds_deps = \
+	folks \
+	folks-eds \
+	gee-1.0 \
+	gio-2.0 \
+	gobject-2.0 \
+	libebook-1.2 \
+	libedataserver-1.2 \
+	libxml-2.0 \
+	$(NULL)
+
+libfolks_backend_eds_la_CFLAGS = \
+	$(GIO_CFLAGS) \
+	$(GLIB_CFLAGS) \
+	$(GEE_CFLAGS) \
+	$(EBOOK_CFLAGS) \
+	$(EDATASERVER_CFLAGS) \
+	$(LIBXML_CFLAGS) \
+	$(NULL)
+
+libfolks_backend_eds_la_LIBADD = \
+	lib/libfolks-eds.la \
+	$(top_builddir)/folks/libfolks.la \
+	$(GIO_LIBS) \
+	$(GLIB_LIBS) \
+	$(GEE_LIBS) \
+	$(EBOOK_LIBS) \
+	$(EDATASERVER_LIBS) \
+	$(LIBXML_LIBS) \
+	$(NULL)
+
+libfolks_backend_eds_la_LDFLAGS = -shared -fPIC -module -avoid-version
+
+GITIGNOREFILES = \
+	folks-backend-eds.vapi \
+	$(libfolks_backend_eds_la_SOURCES:.vala=.c) \
+	libfolks_backend_eds_la_vala.stamp \
+	$(NULL)
+
+-include backend.mk
+-include $(top_srcdir)/git.mk
diff --git a/backends/eds/backend.mk b/backends/eds/backend.mk
new file mode 100644
index 0000000..cd1f182
--- /dev/null
+++ b/backends/eds/backend.mk
@@ -0,0 +1 @@
+BACKEND_NAME = eds
diff --git a/backends/eds/eds-backend-factory.vala b/backends/eds/eds-backend-factory.vala
new file mode 100644
index 0000000..d532aed
--- /dev/null
+++ b/backends/eds/eds-backend-factory.vala
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2009 Zeeshan Ali (Khattak) <zeeshanak gnome org>.
+ * Copyright (C) 2009 Nokia Corporation.
+ * 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: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *          Travis Reitter <travis reitter collabora co uk>
+ *          Marco Barisione <marco barisione collabora co uk>
+ *          Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ * This file was originally part of Rygel.
+ */
+
+using Folks;
+using Folks.Backends.Eds;
+
+private BackendFactory _backend_factory = null;
+
+/**
+ * The eds backend module entry point.
+ *
+ * @since 0.5.UNRELEASED
+ */
+public void module_init (BackendStore backend_store)
+{
+  _backend_factory = new BackendFactory (backend_store);
+}
+
+/**
+ * The eds backend module exit point.
+ *
+ * @since 0.5.UNRELEASED
+ */
+public void module_finalize (BackendStore backend_store)
+{
+  _backend_factory = null;
+}
+
+/**
+ * A backend factory to create a single { link Backend}.
+ */
+public class Folks.Backends.Eds.BackendFactory : Object
+{
+  /**
+   * { inheritDoc}
+   */
+  public BackendFactory (BackendStore backend_store)
+    {
+      backend_store.add_backend (new Backend ());
+    }
+}
diff --git a/backends/eds/eds-backend.vala b/backends/eds/eds-backend.vala
new file mode 100644
index 0000000..2eaf375
--- /dev/null
+++ b/backends/eds/eds-backend.vala
@@ -0,0 +1,178 @@
+/*
+ * 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 E;
+using Gee;
+using GLib;
+using Folks;
+using Folks.Backends.Eds;
+
+extern const string BACKEND_NAME;
+
+/**
+ * A backend which connects to EDS and creates a { link PersonaStore}
+ * for each service.
+ */
+public class Folks.Backends.Eds.Backend : Folks.Backend
+{
+  public static const string use_addressbooks =
+      "FOLKS_BACKEND_EDS_USE_ADDRESSBOOKS";
+  private bool _is_prepared = false;
+  private HashMap<string, PersonaStore> _persona_stores;
+  private Map<string, PersonaStore> _persona_stores_ro;
+  private E.SourceList _ab_sources;
+
+  /**
+   * { inheritDoc}
+   */
+  public override string name { get { return BACKEND_NAME; } }
+
+  /**
+   * { inheritDoc}
+   */
+  public override Map<string, PersonaStore> persona_stores
+    {
+      get { return this._persona_stores_ro; }
+    }
+
+  /**
+   * { inheritDoc}
+   */
+  public Backend ()
+    {
+      this._persona_stores = new HashMap<string, PersonaStore> ();
+      this._persona_stores_ro = this._persona_stores.read_only_view;
+    }
+
+  /**
+   * Whether this Backend has been prepared.
+   *
+   * See { link Folks.Backend.is_prepared}.
+   *
+   * @since 0.5.UNRELEASED
+   */
+  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)
+            {
+              string[] use_addressbooks = this._get_addressbooks_from_env ();
+              this._create_avatars_cache_dir ();
+
+              E.BookClient.get_sources (out this._ab_sources);
+              unowned GLib.SList<weak E.SourceGroup> groups =
+                  this._ab_sources.peek_groups ();
+
+              foreach (var g in groups)
+                {
+                  foreach (E.Source s in g.peek_sources ())
+                    {
+                      if (use_addressbooks.length > 0)
+                        {
+                          if (s.peek_name () in use_addressbooks)
+                            {
+                              this._add_addressbook (s);
+                            }
+                        }
+                      else
+                        {
+                          this._add_addressbook (s);
+                        }
+                    }
+                }
+
+              this._is_prepared = true;
+              this.notify_property ("is-prepared");
+            }
+        }
+    }
+
+  /**
+   * { inheritDoc}
+   */
+  public override async void unprepare () throws GLib.Error
+    {
+      foreach (var persona_store in this._persona_stores.values)
+        {
+          this.persona_store_removed (persona_store);
+        }
+
+      this._persona_stores.clear ();
+      this.notify_property ("persona-stores");
+
+      this._is_prepared = false;
+      this.notify_property ("is-prepared");
+    }
+
+  private void _create_avatars_cache_dir ()
+    {
+      string avatars_dir = GLib.Path.build_filename
+          (GLib.Environment.get_user_cache_dir (), "folks", "avatars");
+      DirUtils.create_with_parents (avatars_dir, 0700);
+    }
+
+  /**
+   * Add a new addressbook connected to a Persona Store.
+   */
+  private void _add_addressbook (E.Source s)
+    {
+      string relative_uri = s.peek_relative_uri ();
+
+      if (this._persona_stores.has_key (relative_uri))
+        return;
+
+      var store = new Edsf.PersonaStore (s);
+      this._persona_stores.set (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)
+    {
+      store.removed.disconnect (this._store_removed_cb);
+      this.persona_store_removed (store);
+      this.persona_stores.unset (store.id);
+    }
+
+  private string[] _get_addressbooks_from_env ()
+    {
+      string[] addressbooks = {};
+      string ab_list = Environment.get_variable (this.use_addressbooks);
+
+      if (ab_list != null && ab_list != "")
+        {
+          addressbooks = ab_list.split (":");
+        }
+
+      return addressbooks;
+    }
+}
diff --git a/backends/eds/lib/Makefile.am b/backends/eds/lib/Makefile.am
new file mode 100644
index 0000000..4a7242e
--- /dev/null
+++ b/backends/eds/lib/Makefile.am
@@ -0,0 +1,116 @@
+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 \
+	$(NULL)
+
+folks_edsdir = $(libdir)
+folks_eds_LTLIBRARIES = libfolks-eds.la
+
+CLEANFILES =
+
+##################################################################
+# Support library
+##################################################################
+
+pkgconfig_in = folks-eds.pc.in
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = $(pkgconfig_in:.in=)
+
+libfolks_eds_la_vala.stamp:
+
+folks_eds_valasources = \
+	edsf-persona.vala \
+	edsf-persona-store.vala \
+	$(NULL)
+
+libfolks_eds_la_SOURCES = \
+	$(folks_eds_valasources) \
+	$(NULL)
+
+VALAFLAGS = \
+	--vapidir=. \
+	--vapidir=$(top_srcdir)/folks \
+	$(addprefix --pkg ,$(folks_backend_eds_deps)) \
+	--vapi folks-eds.vapi \
+	-H folks-eds.h \
+	$(NULL)
+
+folks_backend_eds_deps = \
+	folks \
+	gee-1.0 \
+	gio-2.0 \
+	gobject-2.0 \
+	libebook-1.2 \
+	libedataserver-1.2 \
+	libxml-2.0 \
+	$(NULL)
+
+libfolks_eds_la_CFLAGS = \
+	$(GIO_CFLAGS) \
+	$(GLIB_CFLAGS) \
+	$(GEE_CFLAGS) \
+	$(EBOOK_CFLAGS) \
+	$(EDATASERVER_CFLAGS) \
+	$(LIBXML_CFLAGS) \
+	$(NULL)
+
+libfolks_eds_la_LIBADD = \
+	$(top_builddir)/folks/libfolks.la \
+	$(EBOOK_LIBS) \
+	$(EDATASERVER_LIBS) \
+	$(GIO_LIBS) \
+	$(GLIB_LIBS) \
+	$(GEE_LIBS) \
+	$(LIBXML_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_eds_la_LDFLAGS = \
+	-version-info "$(LT_CURRENT)":"$(LT_REVISION)":"$(LT_AGE)" \
+	-export-symbols-regex "^(EDSF|edsf)_.*|" \
+	$(NULL)
+
+folks_eds_includedir = $(includedir)/folks
+folks_eds_include_HEADERS = \
+	folks-eds.h \
+	$(NULL)
+
+vapidir = $(datadir)/vala/vapi
+dist_vapi_DATA = \
+	folks-eds.vapi \
+	folks-eds.deps \
+	$(NULL)
+
+##################################################################
+# General
+##################################################################
+
+CLEANFILES += \
+	$(pkgconfig_in:.in=) \
+	folks-eds-uninstalled.pc \
+	$(NULL)
+
+MAINTAINERCLEANFILES = \
+	$(folks_eds_valasources:.vala=.c) \
+	libfolks_eds_la_vala.stamp \
+	folks-eds.h \
+	folks-eds.vapi \
+	$(NULL)
+
+EXTRA_DIST = \
+	$(pkgconfig_in) \
+	$(MAINTAINERCLEANFILES) \
+	$(NULL)
+
+-include ../backend.mk
+-include $(top_srcdir)/git.mk
diff --git a/backends/eds/lib/edsf-persona-store.vala b/backends/eds/lib/edsf-persona-store.vala
new file mode 100644
index 0000000..e4ecdc5
--- /dev/null
+++ b/backends/eds/lib/edsf-persona-store.vala
@@ -0,0 +1,811 @@
+/*
+ * 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 E;
+using Folks;
+using Gee;
+using GLib;
+
+extern const string BACKEND_NAME;
+
+/**
+ * A persona store.
+ * It will create { link Persona}s for each contacts on the main addressbook.
+ */
+public class Edsf.PersonaStore : Folks.PersonaStore
+{
+  private HashMap<string, Persona> _personas;
+  private Map<string, Persona> _personas_ro;
+  private bool _is_prepared = false;
+  private E.BookClient _addressbook;
+  private E.BookClientView _ebookview;
+  private string _addressbook_uri = null;
+  private E.Source _source;
+  private string _query_str;
+
+  /**
+   * The type of persona store this is.
+   *
+   * See { link Folks.PersonaStore.type_id}.
+   *
+   * @since 0.5.UNRELEASED
+   */
+  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 0.5.UNRELEASED
+   */
+  public override MaybeBool can_add_personas
+    {
+      get { return MaybeBool.TRUE; }
+    }
+
+  /**
+   * Whether this PersonaStore can set the alias of { link Folks.Persona}s.
+   *
+   * See { link Folks.PersonaStore.can_alias_personas}.
+   *
+   * @since 0.5.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 0.5.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 0.5.UNRELEASED
+   */
+  public override MaybeBool can_remove_personas
+    {
+      get { return MaybeBool.TRUE; }
+    }
+
+  /**
+   * Whether this PersonaStore has been prepared.
+   *
+   * See { link Folks.PersonaStore.is_prepared}.
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public override bool is_prepared
+    {
+      get { return this._is_prepared; }
+    }
+
+  /**
+   * The { link Persona}s exposed by this PersonaStore.
+   *
+   * See { link Folks.PersonaStore.personas}.
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public override Map<string, Persona> personas
+    {
+      get { return this._personas_ro; }
+    }
+
+  /**
+   * Create a new PersonaStore.
+   *
+   * Create a new persona store to store the { link Persona}s for the contacts
+   *
+   * @param s the e-d-s source being represented by the persona store
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public PersonaStore (E.Source s)
+    {
+      string uri = s.peek_relative_uri ();
+      Object (id: uri, display_name: uri);
+      this._source = s;
+      this._addressbook_uri =  uri;
+      this._personas = new HashMap<string, Persona> ();
+      this._personas_ro = this._personas.read_only_view;
+      this._query_str = "(contains \"x-evolution-any-field\" \"\")";
+    }
+
+  ~PersonaStore ()
+    {
+      try
+        {
+          if (this._ebookview != null)
+            {
+              this._ebookview.objects_added.disconnect (
+                  this._contacts_added_cb);
+              this._ebookview.objects_removed.disconnect (
+                  this._contacts_removed_cb);
+              this._ebookview.objects_modified.disconnect (
+                  this._contacts_changed_cb);
+              this._ebookview.stop ();
+
+              this._ebookview = null;
+            }
+
+          this._addressbook = null;
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("~PersonaStore: %s\n", e.message);
+        }
+    }
+
+  /**
+   * Add a new { link Persona} to the PersonaStore.
+   *
+   * Accepted keys for `details` are:
+   * - PersonaStore.detail_key (PersonaDetail.AVATAR)
+   * - PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES)
+   * - PersonaStore.detail_key (PersonaDetail.FULL_NAME)
+   * - PersonaStore.detail_key (PersonaDetail.IM_ADDRESSES)
+   * - PersonaStore.detail_key (PersonaDetail.PHONE_NUMBERS)
+   * - PersonaStore.detail_key (PersonaDetail.POSTAL_ADDRESSES)
+   * - PersonaStore.detail_key (PersonaDetail.STRUCTURED_NAME)
+   * - PersonaStore.detail_key (PersonaDetail.LOCAL_IDS)
+   * - PersonaDetail.detail_key (PersonaDetail.WEB_SERVICE_ADDRESSES)
+   * - PersonaStore.detail_key (PersonaDetail.NOTES)
+   *
+   * See { link Folks.PersonaStore.add_persona_from_details}.
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public override async Folks.Persona? add_persona_from_details (
+      HashTable<string, Value?> details) throws Folks.PersonaStoreError
+    {
+      E.Contact contact = new E.Contact ();
+
+      foreach (var k in details.get_keys ())
+        {
+          Value? v = details.lookup (k);
+          if (k == Folks.PersonaStore.detail_key (
+                PersonaDetail.FULL_NAME))
+            {
+              contact.set (E.Contact.field_id ("full_name"),
+                  v.get_string ());
+            }
+          else if (k == Folks.PersonaStore.detail_key (
+                PersonaDetail.EMAIL_ADDRESSES))
+            {
+              Set<FieldDetails> email_addresses =
+                (Set<FieldDetails>) v.get_object ();
+              yield this._set_contact_attributes (contact, email_addresses,
+                  "EMAIL", E.ContactField.EMAIL);
+            }
+          else if (k == Folks.PersonaStore.detail_key (PersonaDetail.AVATAR))
+            {
+              var avatar = (File) v.get_object ();
+              yield this._set_contact_avatar (contact, avatar);
+            }
+          else if (k == Folks.PersonaStore.detail_key (
+                PersonaDetail.IM_ADDRESSES))
+            {
+              var im_addresses = (MultiMap<string, string>) v.get_object ();
+              yield this._set_contact_im_addrs (contact, im_addresses);
+            }
+          else if (k == Folks.PersonaStore.detail_key (
+                PersonaDetail.PHONE_NUMBERS))
+            {
+              Set<FieldDetails> phone_numbers =
+                (Set<FieldDetails>) v.get_object ();
+              yield this._set_contact_attributes (contact,
+                  phone_numbers, "TEL",
+                  E.ContactField.TEL);
+            }
+          else if (k == Folks.PersonaStore.detail_key (
+                PersonaDetail.POSTAL_ADDRESSES))
+            {
+              Set<PostalAddress> postal_addresses =
+                (Set<PostalAddress>) v.get_object ();
+                yield this._set_contact_postal_addresses (contact,
+                    postal_addresses);
+            }
+          else if (k == Folks.PersonaStore.detail_key (
+                PersonaDetail.STRUCTURED_NAME))
+            {
+              StructuredName sname = (StructuredName) v.get_object ();
+              yield this._set_contact_name (contact, sname);
+            }
+          else if (k == Folks.PersonaStore.detail_key (PersonaDetail.LOCAL_IDS))
+            {
+              Set<string> local_ids = (Set<string>) v.get_object ();
+              yield this._set_contact_local_ids (contact, local_ids);
+            }
+          else if (k == Folks.PersonaStore.detail_key
+              (PersonaDetail.WEB_SERVICE_ADDRESSES))
+            {
+              HashMultiMap<string, string> web_service_addresses =
+                (HashMultiMap<string, string>) v.get_object ();
+              yield this._set_contact_web_service_addresses (contact,
+                  web_service_addresses);
+            }
+          else if (k == Folks.PersonaStore.detail_key (PersonaDetail.NOTES))
+            {
+              var notes = (Gee.HashSet<Note>) v.get_object ();
+              yield this._set_contact_notes (contact, notes);
+            }
+        }
+
+      Edsf.Persona? persona = null;
+
+      try
+        {
+          string added_uid;
+          var result = yield this._addressbook.add_contact (contact,
+              out added_uid);
+
+          if (result)
+            {
+              debug ("Created contact with uid: %s\n", added_uid);
+              lock (this._personas)
+                {
+                  var iid = Edsf.Persona.build_iid (this.id, added_uid);
+                  persona = this._personas.get (iid);
+                  if (persona == null)
+                    {
+                      contact.set (E.Contact.field_id ("id"), added_uid);
+                      persona = new Persona (this, contact);
+                      this._personas.set (persona.iid, persona);
+                      var added_personas = new HashSet<Persona> ();
+                      added_personas.add (persona);
+                      this._emit_personas_changed (added_personas, null);
+                    }
+                }
+            }
+          else
+            {
+              throw new PersonaStoreError.CREATE_FAILED
+                ("BookClient.add_contact () failed.");
+            }
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("add_persona_from_details: %s\n",
+              e.message);
+        }
+
+      return persona;
+    }
+
+  /**
+   * Remove a { link Persona} from the PersonaStore.
+   *
+   * @param persona the that should be removed
+   *
+   * See { link Folks.PersonaStore.remove_persona}.
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public override async void remove_persona (Folks.Persona persona)
+      throws Folks.PersonaStoreError
+    {
+      try
+        {
+          yield this._addressbook.remove_contact (
+              ((Edsf.Persona) persona).contact);
+        }
+      catch (GLib.Error e)
+        {
+          /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=652425 */
+          throw new PersonaStoreError.INVALID_ARGUMENT (
+              "Can't remove contact: %s\n", e.message);
+        }
+    }
+
+  /**
+   * 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}.
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public override async void prepare ()
+    {
+      /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=652637 */
+      lock (this._is_prepared)
+        {
+          if (this._is_prepared)
+            {
+              return;
+            }
+
+          /* FIXME: we need better error codes */
+
+          try
+            {
+              this._addressbook = new E.BookClient (this._source);
+            }
+          catch (GLib.Error e1)
+            {
+              throw new PersonaStoreError.INVALID_ARGUMENT (
+                  "Couldn't get BookClient: %s\n", e1.message);
+            }
+
+          try
+            {
+              yield this._addressbook.open (true, null);
+            }
+          catch (GLib.Error e2)
+            {
+              throw new PersonaStoreError.INVALID_ARGUMENT (
+                  "Couldn't open addressbook: %s\n", e2.message);
+            }
+
+          if (this._addressbook.is_opened () == false)
+            {
+              throw new PersonaStoreError.INVALID_ARGUMENT (
+                  "Couldn't open addressbook\n");
+            }
+
+          bool got_view = false;
+          try
+            {
+              got_view = yield this._addressbook.get_view (this._query_str,
+                  out this._ebookview);
+            }
+          catch (GLib.Error e3)
+            {
+              throw new PersonaStoreError.INVALID_ARGUMENT (
+                  "Couldn't get book view: %s\n", e3.message);
+            }
+
+          if (got_view == false)
+            {
+              throw new PersonaStoreError.INVALID_ARGUMENT (
+                  "Couldn't get book view\n");
+            }
+
+          this._ebookview.objects_added.connect (this._contacts_added_cb);
+          this._ebookview.objects_removed.connect (this._contacts_removed_cb);
+          this._ebookview.objects_modified.connect (this._contacts_changed_cb);
+
+          try
+            {
+              this._ebookview.start ();
+            }
+          catch (GLib.Error e4)
+            {
+              throw new PersonaStoreError.INVALID_ARGUMENT (
+                  "Couldn't start bookview: %s\n", e4.message);
+            }
+
+          this._is_prepared = true;
+          this.notify_property ("is-prepared");
+        }
+    }
+
+  internal async void _set_avatar (Edsf.Persona persona, File avatar)
+    {
+      try
+        {
+          E.Contact contact = ((Edsf.Persona) persona).contact;
+          yield this._set_contact_avatar (contact, avatar);
+          yield this._addressbook.modify_contact (contact);
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Can't update avatar: %s\n", e.message);
+        }
+    }
+
+  internal async void _set_web_service_addresses (Edsf.Persona persona,
+      MultiMap<string, string> web_service_addresses)
+    {
+      try
+        {
+          E.Contact contact = ((Edsf.Persona) persona).contact;
+          yield this._set_contact_web_service_addresses (contact,
+            web_service_addresses);
+          yield this._addressbook.modify_contact (contact);
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Can't set local IDS: %s\n", e.message);
+        }
+    }
+
+  private async void _set_contact_web_service_addresses (E.Contact contact,
+      MultiMap<string, string> web_service_addresses)
+    {
+      var attr = contact.get_attribute ("X-FOLKS-WEB-SERVICES-IDS");
+      if (attr != null)
+        {
+          contact.remove_attribute (attr);
+        }
+
+      var attr_n = new VCardAttribute (null, "X-FOLKS-WEB-SERVICES-IDS");
+      foreach (var service in web_service_addresses.get_keys ())
+        {
+          var param = new E.VCardAttributeParam (service);
+          foreach (var id in web_service_addresses.get (service))
+            {
+              param.add_value (id);
+            }
+          attr_n.add_param (param);
+        }
+      contact.add_attribute (attr_n);
+    }
+
+  internal async void _set_local_ids (Edsf.Persona persona,
+      Set<string> local_ids)
+    {
+      try
+        {
+          E.Contact contact = ((Edsf.Persona) persona).contact;
+          yield this._set_contact_local_ids (contact, local_ids);
+          yield this._addressbook.modify_contact (contact);
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Can't set local IDS: %s\n", e.message);
+        }
+    }
+
+  private async void _set_contact_local_ids (E.Contact contact,
+      Set<string> local_ids)
+    {
+      var attr = contact.get_attribute ("X-FOLKS-CONTACTS-IDS");
+      if (attr != null)
+        {
+          contact.remove_attribute (attr);
+        }
+
+      attr = new VCardAttribute (null, "X-FOLKS-CONTACTS-IDS");
+      foreach (var local_id in local_ids)
+        {
+          attr.add_value (local_id);
+        }
+
+      contact.add_attribute (attr);
+    }
+
+  private async void _set_contact_avatar (E.Contact contact,
+      File avatar)
+    {
+      try
+        {
+          uint8[] photo_content;
+          yield avatar.load_contents_async (null, out photo_content);
+
+          var cp = new ContactPhoto ();
+          cp.type = ContactPhotoType.INLINED;
+          cp.set_inlined (photo_content);
+
+          contact.set (E.Contact.field_id ("photo"), cp);
+        }
+      catch (GLib.Error e_avatar)
+        {
+          GLib.warning ("Can't load avatar %s: %s\n\n", avatar.get_path (),
+              e_avatar.message);
+        }
+    }
+
+  internal async void _set_emails (Edsf.Persona persona,
+      Set<FieldDetails> emails)
+    {
+      try
+        {
+          E.Contact contact = ((Edsf.Persona) persona).contact;
+          yield this._set_contact_attributes (contact, emails, "EMAIL",
+              E.ContactField.EMAIL);
+          yield this._addressbook.modify_contact (contact);
+        }
+      catch (GLib.Error error)
+        {
+          GLib.warning ("Can't update email-addresses: %s\n",
+              error.message);
+        }
+    }
+
+  internal async void _set_phones (Edsf.Persona persona,
+      Set<FieldDetails> phones)
+    {
+      try
+        {
+          E.Contact contact = ((Edsf.Persona) persona).contact;
+          yield this._set_contact_attributes (contact, phones, "TEL",
+              E.ContactField.TEL);
+          yield this._addressbook.modify_contact (contact);
+        }
+      catch (GLib.Error error)
+        {
+          GLib.warning ("Can't update phones: %s\n", error.message);
+        }
+    }
+
+  internal async void _set_postal_addresses (Edsf.Persona persona,
+      Set<PostalAddress> addresses)
+    {
+      try
+        {
+          E.Contact contact = ((Edsf.Persona) persona).contact;
+          yield this._set_contact_postal_addresses (contact,
+              addresses);
+          yield this._addressbook.modify_contact (contact);
+        }
+      catch (GLib.Error error)
+        {
+          GLib.warning ("Can't update postal addresses: %s\n",
+              error.message);
+        }
+    }
+
+  private async void _set_contact_postal_addresses (E.Contact contact,
+      Set<PostalAddress> addresses)
+    {
+      foreach (var pa in addresses)
+        {
+          var address = new E.ContactAddress ();
+
+          address.po = pa.po_box;
+          address.ext = pa.extension;
+          address.street = pa.street;
+          address.locality = pa.locality;
+          address.region = pa.region;
+          address.code = pa.postal_code;
+          address.country = pa.country;
+          address.address_format = pa.address_format;
+
+          if (pa.types.size > 0)
+            {
+              var pa_type = pa.types.to_array ()[0];
+              contact.set (E.Contact.field_id (pa_type), address);
+            }
+          else
+            {
+              contact.set (E.ContactField.ADDRESS_OTHER, address);
+            }
+        }
+    }
+
+  private async void _set_contact_attributes (E.Contact contact,
+      Set<FieldDetails> new_attributes,
+      string attrib_name, E.ContactField field_id)
+    {
+      var attributes = new GLib.List <E.VCardAttribute>();
+
+      foreach (var e in new_attributes)
+        {
+          var attr = new E.VCardAttribute (null, attrib_name);
+          attr.add_value (e.value);
+          foreach (var param_name in e.parameters.get_keys ())
+            {
+              var param = new E.VCardAttributeParam (param_name.up ());
+              foreach (var param_val in e.parameters.get (param_name))
+                {
+                  param.add_value (param_val);
+                }
+              attr.add_param (param);
+            }
+          attributes.prepend (attr);
+        }
+
+      contact.set_attributes (field_id, attributes);
+    }
+
+  internal async void _set_full_name (Edsf.Persona persona,
+      string full_name)
+    {
+      try
+        {
+          E.Contact contact = ((Edsf.Persona) persona).contact;
+          contact.set (E.Contact.field_id ("full_name"), full_name);
+          yield this._addressbook.modify_contact (contact);
+        }
+      catch (GLib.Error error)
+        {
+          GLib.warning ("Can't update full name: %s\n", error.message);
+        }
+    }
+
+  internal async void _set_notes (Edsf.Persona persona,
+      Set<Note> notes)
+    {
+      try
+        {
+          E.Contact contact = ((Edsf.Persona) persona).contact;
+          yield this._set_contact_notes (contact, notes);
+          yield this._addressbook.modify_contact (contact);
+        }
+      catch (GLib.Error error)
+        {
+          GLib.warning ("Can't update notes: %s\n", error.message);
+        }
+    }
+
+  private async void _set_contact_notes (E.Contact contact, Set<Note> notes)
+    {
+      string note_str = "";
+      foreach (var note in notes)
+        {
+          if (note_str != "")
+            {
+              note_str += ". ";
+            }
+          note_str += note.content;
+        }
+
+      contact.set (E.Contact.field_id ("note"), note_str);
+    }
+
+  internal async void _set_structured_name (Edsf.Persona persona,
+      StructuredName sname)
+    {
+      try
+        {
+          E.Contact contact = ((Edsf.Persona) persona).contact;
+          yield this._set_contact_name (contact, sname);
+          yield this._addressbook.modify_contact (contact);
+        }
+      catch (GLib.Error error)
+        {
+          GLib.warning ("Can't update structured name: %s\n", error.message);
+        }
+    }
+
+  private async void _set_contact_name (E.Contact contact,
+      StructuredName sname)
+    {
+      E.ContactName contact_name = new E.ContactName ();
+
+      contact_name.family = sname.family_name;
+      contact_name.given = sname.given_name;
+      contact_name.additional = sname.additional_names;
+      contact_name.suffixes = sname.suffixes;
+      contact_name.prefixes = sname.prefixes;
+
+      contact.set (E.Contact.field_id ("name"), contact_name);
+    }
+
+  internal async void _set_im_addrs  (Edsf.Persona persona,
+      MultiMap<string, string> im_addrs)
+    {
+      try
+        {
+          E.Contact contact = ((Edsf.Persona) persona).contact;
+          yield this._set_contact_im_addrs (contact, im_addrs);
+          yield this._addressbook.modify_contact (contact);
+        }
+      catch (GLib.Error error)
+        {
+          GLib.warning ("Can't update IM addresses: %s\n", error.message);
+        }
+    }
+
+  /* TODO: this could be smarter & more efficient. */
+  private async void _set_contact_im_addrs (E.Contact contact,
+      MultiMap<string, string> im_addrs)
+    {
+      var im_eds_map = Edsf.Persona._get_im_eds_map ();
+
+      /* First let's remove everything */
+      foreach (var field_id in im_eds_map.get_values ())
+        {
+          var attrs = contact.get_attributes (field_id);
+          foreach (var attr in attrs)
+            {
+              contact.remove_attribute (attr);
+            }
+        }
+
+     foreach (var proto in im_addrs.get_keys ())
+       {
+         var attributes = new GLib.List <E.VCardAttribute>();
+         var attrib_name = ("X-" + proto).up ();
+         bool added = false;
+
+         foreach (var im in im_addrs.get (proto))
+           {
+             var attr_n = new E.VCardAttribute (null, attrib_name);
+             attr_n.add_value (im);
+             attributes.prepend (attr_n);
+             added = true;
+           }
+
+         if (added)
+           {
+             var field_id_t = im_eds_map.lookup (proto);
+             contact.set_attributes (field_id_t, attributes);
+           }
+       }
+    }
+
+  private void _contacts_added_cb (GLib.List<E.Contact> contacts)
+    {
+      var added_personas = new HashSet<Persona> ();
+      lock (this._personas)
+        {
+          foreach (E.Contact c in contacts)
+            {
+              var iid = Edsf.Persona.build_iid_from_contact (this.id, c);
+              var persona = this._personas.get (iid);
+              if (persona == null)
+                {
+                  persona = new Persona (this, c);
+                  this._personas.set (persona.iid, persona);
+                  added_personas.add (persona);
+                }
+            }
+        }
+
+      if (added_personas.size > 0)
+        {
+          this._emit_personas_changed (added_personas, null);
+        }
+    }
+
+  private void _contacts_changed_cb (GLib.List<E.Contact> contacts)
+    {
+      foreach (E.Contact c in contacts)
+        {
+          var iid = Edsf.Persona.build_iid_from_contact (this.id, c);
+          var persona = this._personas.get (iid);
+          if (persona != null)
+            {
+              persona._update (c);
+            }
+        }
+    }
+
+  private void _contacts_removed_cb (GLib.List<string> contacts_ids)
+    {
+      var removed_personas = new HashSet<Persona> ();
+
+      foreach (string contact_id in contacts_ids)
+        {
+          var iid = Edsf.Persona.build_iid (this.id, contact_id);
+          var persona = _personas.get (iid);
+          if (persona != null)
+            {
+              removed_personas.add (persona);
+              this._personas.unset (persona.iid);
+            }
+        }
+
+       if (removed_personas.size > 0)
+         {
+           this._emit_personas_changed (null, removed_personas);
+         }
+    }
+}
diff --git a/backends/eds/lib/edsf-persona.vala b/backends/eds/lib/edsf-persona.vala
new file mode 100644
index 0000000..8cf04f2
--- /dev/null
+++ b/backends/eds/lib/edsf-persona.vala
@@ -0,0 +1,806 @@
+/*
+ * 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 E;
+using Folks;
+using Gee;
+using GLib;
+using Xml;
+
+/**
+ * A persona subclass which represents a single EDS contact.
+ */
+public class Edsf.Persona : Folks.Persona,
+    AvatarDetails,
+    EmailDetails,
+    GenderDetails,
+    ImDetails,
+    LocalIdDetails,
+    NameDetails,
+    NoteDetails,
+    PhoneDetails,
+    UrlDetails,
+    PostalAddressDetails,
+    WebServiceDetails
+{
+  /* The following 4 definitions are used by the tests */
+  public static const string[] phone_fields = {
+    "assistant_phone", "business_phone", "business_phone_2", "callback_phone",
+    "car_phone", "company_phone", "home_phone", "home_phone_2", "isdn_phone",
+    "mobile_phone", "other_phone", "primary_phone"
+  };
+  public static const string[] address_fields = {
+    "address_home", "address_other", "address_work"
+  };
+  public static const string[] email_fields = {
+    "email_1", "email_2", "email_3", "email_4"
+  };
+  public static const string[] url_properties = {
+    "blog_url", "fburl", "homepage_url", "video_url"
+  };
+  private const string[] _linkable_properties = { "im-addresses",
+                                                  "local-ids",
+                                                  "web-service-addresses" };
+  private HashSet<FieldDetails> _phone_numbers;
+  private Set<FieldDetails> _phone_numbers_ro;
+  private HashSet<FieldDetails> _email_addresses;
+  private Set<FieldDetails> _email_addresses_ro;
+  private HashSet<Note> _notes;
+  private Set<Note> _notes_ro;
+  private static HashTable<string, E.ContactField> _im_eds_map = null;
+
+  private HashSet<PostalAddress> _postal_addresses;
+  private Set<PostalAddress> _postal_addresses_ro;
+
+  private HashSet<string> _local_ids;
+  private Set<string> _local_ids_ro;
+
+  private HashMultiMap<string, string> _web_service_addresses;
+
+  /**
+   * The e-d-s contact represented by this Persona
+   */
+  public E.Contact contact
+    {
+      get;
+      private set;
+    }
+
+  /**
+   * { inheritDoc}
+   */
+  public MultiMap<string, string> web_service_addresses
+    {
+      get { return this._web_service_addresses; }
+      set
+        {
+          var store = (Edsf.PersonaStore) this.store;
+          store._set_web_service_addresses (this, value);
+        }
+    }
+
+  /**
+   * IDs used to link { link Edsf.Persona}s.
+   */
+  public Set<string> local_ids
+    {
+      get
+        {
+          if (this._local_ids.contains (this.contact_id) == false)
+            {
+              this._local_ids.add (this.contact_id);
+            }
+          return this._local_ids_ro;
+        }
+      set
+        {
+          ((Edsf.PersonaStore) this.store)._set_local_ids (this, value);
+        }
+    }
+
+  /**
+   * The postal addresses of the contact.
+   *
+   * A list of postal addresses associated to the contact.
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public Set<PostalAddress> postal_addresses
+    {
+      get { return this._postal_addresses_ro; }
+      private set
+        {
+          ((Edsf.PersonaStore) this.store)._set_postal_addresses (this, value);
+        }
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public Set<FieldDetails> phone_numbers
+    {
+      get { return this._phone_numbers_ro; }
+      private set
+        {
+          ((Edsf.PersonaStore) this.store)._set_phones (this, value);
+        }
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public Set<FieldDetails> email_addresses
+    {
+      get { return this._email_addresses_ro; }
+      private set
+        {
+          ((Edsf.PersonaStore) this.store)._set_emails (this, value);
+        }
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public Set<Note> notes
+    {
+      get { return this._notes_ro; }
+      private set
+        {
+          ((Edsf.PersonaStore) this.store)._set_notes (this, value);
+        }
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public override string[] linkable_properties
+    {
+      get { return this._linkable_properties; }
+    }
+
+  private File _avatar;
+  /**
+   * An avatar for the Persona.
+   *
+   * See { link Folks.Avatar.avatar}.
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public File avatar
+    {
+      get { return this._avatar; }
+      set
+        {
+          if (this._avatar == null ||
+              !this._avatar.equal (value))
+            {
+              ((Edsf.PersonaStore) this.store)._set_avatar (this, value);
+            }
+        }
+    }
+
+  private StructuredName _structured_name;
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public StructuredName structured_name
+    {
+      get { return this._structured_name; }
+      set
+        {
+          ((Edsf.PersonaStore) this.store)._set_structured_name (this, value);
+        }
+    }
+
+  /**
+   * The e-d-s contact uid
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public string contact_id { get; private set; }
+
+  private string _full_name;
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public string full_name
+    {
+      get { return this._full_name; }
+      set
+        {
+          ((Edsf.PersonaStore) this.store)._set_full_name (this, value);
+        }
+    }
+
+  private string _nickname;
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public string nickname { get { return this._nickname; } }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public Gender gender { get; private set; }
+
+  private HashSet<FieldDetails> _urls;
+  private Set<FieldDetails> _urls_ro;
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public Set<FieldDetails> urls
+    {
+      get { return this._urls_ro; }
+      set
+        {
+          GLib.warning ("Urls setting not supported yet\n");
+        }
+    }
+
+  private HashMultiMap<string, string> _im_addresses =
+      new HashMultiMap<string, string> ();
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public MultiMap<string, string> im_addresses
+    {
+      get { return this._im_addresses; }
+      set
+        {
+          ((Edsf.PersonaStore) this.store)._set_im_addrs (this, value);
+        }
+    }
+
+  /**
+   * Build a IID.
+   *
+   * @param store_id the { link PersonaStore.id}
+   * @param contact the Contact
+   * @return a valid IID
+   *
+   * @since 0.5.UNRELEASED
+   */
+  internal static string build_iid_from_contact (string store_id,
+      E.Contact contact)
+    {
+      var contact_id =
+          (string) Edsf.Persona._get_property_from_contact (contact, "id");
+      return Edsf.Persona.build_iid (store_id, contact_id);
+    }
+
+  /**
+   * Build a IID.
+   *
+   * @param store_id the { link PersonaStore.id}
+   * @param contact_id the id belonging to the Contact
+   * @return a valid IID
+   *
+   * @since 0.5.UNRELEASED
+   */
+  internal static string build_iid (string store_id, string contact_id)
+    {
+      return "%s:%s".printf (store_id, contact_id);
+    }
+
+
+  /**
+   * Create a new persona.
+   *
+   * Create a new persona for the { link PersonaStore} `store`, representing
+   * the EDS contact given by `contact`.
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public Persona (PersonaStore store, E.Contact contact)
+    {
+      var contact_id =
+        (string) Edsf.Persona._get_property_from_contact (contact, "id");
+      var uid = this.build_uid (BACKEND_NAME, store.id, contact_id);
+      var iid = Edsf.Persona.build_iid (store.id, contact_id);
+      var is_user = BookClient.is_self (contact);
+      var full_name =
+          (string) Edsf.Persona._get_property_from_contact (contact,
+              "full_name");
+
+      debug ("Creating new Edsf.Persona with IID '%s'", iid);
+
+      Object (display_id: full_name,
+              uid: uid,
+              iid: iid,
+              store: store,
+              gender: Gender.UNSPECIFIED,
+              is_user: is_user);
+
+      this.contact_id = contact_id;
+      this._phone_numbers = new HashSet<FieldDetails> ();
+      this._phone_numbers_ro = this._phone_numbers.read_only_view;
+      this._email_addresses = new HashSet<FieldDetails> ();
+      this._email_addresses_ro = this._email_addresses.read_only_view;
+      this._notes = new HashSet<Note> ();
+      this._notes_ro = this._notes.read_only_view;
+      this._urls = new HashSet<FieldDetails> ();
+      this._urls_ro = this._urls.read_only_view;
+      this._postal_addresses = new HashSet<PostalAddress> ();
+      this._postal_addresses_ro = this._postal_addresses.read_only_view;
+      this._local_ids = new HashSet<string> ();
+      this._local_ids_ro = this._local_ids.read_only_view;
+      this._web_service_addresses = new HashMultiMap<string, string> ();
+
+      this._update (contact);
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public override void linkable_property_to_links (string prop_name,
+      Folks.Persona.LinkablePropertyCallback callback)
+    {
+      if (prop_name == "im-addresses")
+        {
+          foreach (var protocol in this._im_addresses.get_keys ())
+            {
+              var im_addresses = this._im_addresses.get (protocol);
+
+              foreach (string address in im_addresses)
+                  callback (protocol + ":" + address);
+            }
+        }
+      else if (prop_name == "local-ids")
+        {
+          foreach (var id in this._local_ids)
+            {
+              callback (id);
+            }
+        }
+      else if (prop_name == "web-service-addresses")
+        {
+          foreach (var web_service in this.web_service_addresses.get_keys ())
+            {
+              var web_service_addresses =
+                  this._web_service_addresses.get (web_service);
+
+              foreach (string address in web_service_addresses)
+                  callback (web_service + ":" + address);
+            }
+        }
+      else
+        {
+          /* Chain up */
+          base.linkable_property_to_links (prop_name, callback);
+        }
+    }
+
+  ~Persona ()
+    {
+      debug ("Destroying Edsf.Persona '%s': %p", this.uid, this);
+    }
+
+  /**
+   * Update attribs of the persona.
+   */
+  internal void _update (E.Contact contact)
+    {
+      this.contact = contact;
+
+      this._update_names ();
+      this._update_avatar ();
+      this._update_urls ();
+      this._update_phones ();
+      this._update_addresses ();
+      this._update_emails ();
+      this._update_im_addresses ();
+      this._update_notes ();
+      this._update_local_ids ();
+      this._update_web_services_addresses ();
+    }
+
+  private void _update_web_services_addresses ()
+    {
+      this._web_service_addresses.clear ();
+
+      var services = this.contact.get_attribute ("X-FOLKS-WEB-SERVICES-IDS");
+      if (services != null)
+        {
+          foreach (var service in services.get_params ())
+            {
+              var service_name = service.get_name ().down ();
+              foreach (var service_id in service.get_values ())
+                {
+                  this._web_service_addresses.set (service_name, service_id);
+                }
+            }
+        }
+
+      this.notify_property ("web-service-addresses");
+    }
+
+  private void _update_emails ()
+    {
+      this._email_addresses.clear ();
+
+      var attrs = _contact.get_attributes (E.ContactField.EMAIL);
+      foreach (var attr in attrs)
+        {
+          var fd = new FieldDetails (attr.get_value ());
+          foreach (var param in attr.get_params ())
+            {
+              string param_name = param.get_name ().down ();
+              foreach (var param_value in param.get_values ())
+                {
+                  fd.add_parameter (param_name, param_value);
+                }
+            }
+          this._email_addresses.add (fd);
+        }
+
+      this.notify_property ("email-addresses");
+    }
+
+  private void _update_notes ()
+    {
+      this._notes.clear ();
+
+      string n = (string) this._get_property ("note");
+      if (n != null && n != "")
+        {
+          var note = new Note (n);
+          this._notes.add (note);
+        }
+
+      this.notify_property ("notes");
+    }
+
+  private void _update_names ()
+    {
+      string full_name = (string) this._get_property ("full_name");
+      if (this._full_name != full_name)
+        {
+          this._full_name = full_name;
+          this.notify_property ("full-name");
+        }
+
+      string nickname = (string) this._get_property ("nickname");
+      if (this.nickname != nickname)
+        {
+          this._nickname = nickname;
+          this.notify_property ("nickname");
+        }
+
+      StructuredName? structured_name = null;
+      E.ContactName? cn = (E.ContactName) this._get_property ("name");
+      if (cn != null)
+        {
+          string family_name = cn.family;
+          string given_name  = cn.given;
+          string additional_names = cn.additional;
+          string prefixes = cn.prefixes;
+          string suffixes = cn.suffixes;
+          structured_name = new StructuredName (family_name, given_name,
+                                                additional_names, prefixes,
+                                                suffixes);
+        }
+
+      if (structured_name != null && !structured_name.is_empty ())
+        {
+          this._structured_name = structured_name;
+          this.notify_property ("structured-name");
+        }
+      else if (this._structured_name != null)
+        {
+          this._structured_name = null;
+          this.notify_property ("structured-name");
+        }
+    }
+
+  private void _update_avatar ()
+    {
+      string filename = this.uid.delimit (Path.DIR_SEPARATOR.to_string (), '-');
+      string cached_avatar_path = GLib.Path.build_filename (
+          GLib.Environment.get_user_cache_dir (), "folks",
+          "avatars", filename);
+      E.ContactPhoto? p = (E.ContactPhoto) this._get_property ("photo");
+
+      this._avatar = File.new_for_path (cached_avatar_path);
+
+      if (p != null)
+        {
+          var content_old = this.get_avatar_content ();
+          var content_new = this._get_avatar_content_from_contact (p);
+
+          if (content_old != content_new)
+            {
+              try
+                {
+                  this._avatar.replace_contents (content_new,
+                      content_new.length,
+                      null, false, FileCreateFlags.REPLACE_DESTINATION,
+                      null);
+                  this.notify_property ("avatar");
+                }
+              catch (GLib.Error e)
+                {
+                  GLib.warning ("Can't write avatar: %s\n", e.message);
+                }
+            }
+        }
+      else
+        {
+          try
+            {
+              this._avatar.delete ();
+            }
+          catch (GLib.Error e) {}
+          finally
+            {
+              this._avatar = null;
+              this.notify_property ("avatar");
+            }
+        }
+    }
+
+  private void _update_urls ()
+    {
+      this._urls.clear ();
+
+      foreach (string url_property in this.url_properties)
+        {
+          string u = (string) this._get_property (url_property);
+          if (u != null && u != "")
+            {
+              this._urls.add (new FieldDetails (u));
+            }
+        }
+
+      this.notify_property ("urls");
+    }
+
+  private void _update_im_addresses ()
+    {
+      var im_eds_map = this._get_im_eds_map ();
+      this._im_addresses.clear ();
+
+      foreach (var proto in im_eds_map.get_keys ())
+        {
+          var addresses = this.contact.get_attributes (
+              im_eds_map.lookup (proto));
+          foreach (var attr in addresses)
+            {
+              try
+                {
+                  var addr = attr.get_value ();
+                  string address = (owned) ImDetails.normalise_im_address (addr,
+                      proto);
+                  this._im_addresses.set (proto, address);
+                }
+              catch (Folks.ImDetailsError e)
+                {
+                  GLib.warning (
+                      "Problem when trying to normalise address: %s\n",
+                      e.message);
+                }
+            }
+        }
+
+      this.notify_property ("im-addresses");
+    }
+
+  /**
+   * Get the avatars content
+   *
+   * @since 0.5.UNRELEASED
+   */
+  public string get_avatar_content ()
+    {
+      string content = "";
+
+      if (this._avatar != null &&
+          this._avatar.query_exists ())
+        {
+          try
+            {
+              uint8[] content_temp;
+              this._avatar.load_contents (null, out content_temp);
+              content = (string) content_temp;
+            }
+          catch (GLib.Error e)
+            {
+              GLib.warning ("Can't compare avatars: %s\n", e.message);
+            }
+        }
+
+      return content;
+    }
+
+  private string _get_avatar_content_from_contact (E.ContactPhoto p)
+    {
+      string content = "";
+
+      if (p.type == ContactPhotoType.INLINED)
+        {
+          content = (string) p.get_inlined ();
+        }
+      else if (p.type == ContactPhotoType.URI)
+        {
+          try
+            {
+              uint8[] temp_content;
+              var file = File.new_for_uri (p.get_uri ());
+              file.load_contents (null, out temp_content);
+              content = (string) temp_content;
+            }
+          catch (GLib.Error e)
+            {
+              GLib.warning ("Couldn't load content for avatar: %s\n",
+                  p.get_uri ());
+            }
+        }
+
+      return content;
+    }
+
+  /**
+   * build a table of im protocols / im protocol aliases
+   */
+  internal static HashTable<string, E.ContactField> _get_im_eds_map ()
+    {
+      lock (Edsf.Persona._im_eds_map)
+        {
+          if (Edsf.Persona._im_eds_map == null)
+            {
+              Edsf.Persona._im_eds_map =
+                new HashTable<string, E.ContactField> (str_hash, str_equal);
+              Edsf.Persona._im_eds_map.insert ("aim", ContactField.IM_AIM);
+              Edsf.Persona._im_eds_map.insert ("yahoo", ContactField.IM_YAHOO);
+              Edsf.Persona._im_eds_map.insert ("groupwise",
+                  ContactField.IM_GROUPWISE);
+              Edsf.Persona._im_eds_map.insert ("jabber",
+                  ContactField.IM_JABBER);
+              Edsf.Persona._im_eds_map.insert ("msn",
+                  ContactField.IM_MSN);
+              Edsf.Persona._im_eds_map.insert ("icq",
+                  ContactField.IM_ICQ);
+              Edsf.Persona._im_eds_map.insert ("gadugadu",
+                  ContactField.IM_GADUGADU);
+              Edsf.Persona._im_eds_map.insert ("skype",
+                  ContactField.IM_SKYPE);
+            }
+        }
+
+      return Edsf.Persona._im_eds_map;
+    }
+
+  private void _update_phones ()
+    {
+      this._phone_numbers.clear ();
+
+      var attrs = _contact.get_attributes (E.ContactField.TEL);
+      foreach (var attr in attrs)
+        {
+          var fd = new FieldDetails (attr.get_value ());
+          foreach (var param in attr.get_params ())
+            {
+              string param_name = param.get_name ().down ();
+              foreach (var param_value in param.get_values ())
+                {
+                  fd.add_parameter (param_name, param_value);
+                }
+            }
+          this._phone_numbers.add (fd);
+        }
+
+     this.notify_property ("phone-numbers");
+   }
+
+  /*
+   * TODO: we should check if addresses corresponding to different types
+   *       are the same and if so instantiate only one PostalAddress
+   *       (with the given types).
+   */
+  private void _update_addresses ()
+    {
+      this._postal_addresses.clear ();
+
+      foreach (string afield in this.address_fields)
+        {
+          E.ContactAddress a =
+              (E.ContactAddress) this._get_property (afield);
+          if (a != null)
+            {
+              /* FIXME: might be my broken setup, but it looks like
+               * e-d-s is ignoring the address_format param */
+              var address_format = a.address_format;
+              var postal_code = a.code;
+              var country = a.country;
+              var extension = a.ext;
+              var locality = a.locality;
+              var po_box = a.po;
+              var region = a.region;
+              var street = a.street;
+              var types = new HashSet<string> ();
+              types.add (afield);
+
+              PostalAddress pa = new PostalAddress (po_box, extension, street,
+                  locality, region, postal_code, country,
+                  address_format, types, null);
+              this._postal_addresses.add (pa);
+            }
+        }
+
+      this.notify_property ("postal-addresses");
+    }
+
+  private void _update_local_ids ()
+    {
+      this._local_ids.clear ();
+
+      var ids = this.contact.get_attribute ("X-FOLKS-CONTACTS-IDS");
+      if (ids != null)
+        {
+          unowned GLib.List<string> ids_v = ids.get_values ();
+
+          foreach (var local_id in ids_v)
+            {
+              this._local_ids.add (local_id);
+            }
+        }
+
+      this.notify_property ("local-ids");
+    }
+
+  internal static void * _get_property_from_contact (E.Contact contact,
+      string prop_name)
+    {
+      void *prop_value = null;
+      prop_value = contact.get (E.Contact.field_id (prop_name));
+      return prop_value;
+    }
+
+  private void * _get_property (string prop_name)
+    {
+      return Edsf.Persona._get_property_from_contact (this.contact,
+          prop_name);
+    }
+}
diff --git a/backends/eds/lib/folks-eds-uninstalled.pc.in b/backends/eds/lib/folks-eds-uninstalled.pc.in
new file mode 100644
index 0000000..d1f76e7
--- /dev/null
+++ b/backends/eds/lib/folks-eds-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 e-d-s support library (uninstalled copy)
+Description: Evolution Data Server support library for the Folks meta-contacts library
+Version: @VERSION@
+Requires: folks glib-2.0 gobject-2.0 gee-1.0 libebook-1.2 libedataserver-1.2
+Libs: ${abs_top_builddir}/backends/telepathy/libfolks-eds.la
+Cflags: -I${abs_top_srcdir} -I${abs_top_srcdir}/backends/eds -I${abs_top_builddir}
diff --git a/backends/eds/lib/folks-eds.deps b/backends/eds/lib/folks-eds.deps
new file mode 100644
index 0000000..9cb8d84
--- /dev/null
+++ b/backends/eds/lib/folks-eds.deps
@@ -0,0 +1,6 @@
+glib-2.0
+gobject-2.0
+folks
+libebook-1.2
+libedataserver-1.2
+gee-1.0
diff --git a/backends/eds/lib/folks-eds.pc.in b/backends/eds/lib/folks-eds.pc.in
new file mode 100644
index 0000000..4e01ba4
--- /dev/null
+++ b/backends/eds/lib/folks-eds.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 e-d-s support library
+Description: Evolution Data Server support library for the Folks meta-contacts library
+Version: @VERSION@
+Requires: folks glib-2.0 gobject-2.0 gee-1.0 libebook-1.2 libedataserver-1.2
+Libs: -L${libdir} -lfolks-eds
+Cflags: -I${includedir} -I${includedir}/folks
diff --git a/configure.ac b/configure.ac
index 3b93742..79752a3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -77,6 +77,14 @@ AC_ARG_ENABLE(tracker-backend,
 
 AM_CONDITIONAL([ENABLE_TRACKER], [test "x$enable_tracker_backend" = "xyes"])
 
+AC_ARG_ENABLE(eds-backend,
+        AC_HELP_STRING([--enable-eds-backend],
+                       [ build the E-D-S backend]),
+        enable_eds_backend=$enableval,
+        enable_eds_backend=no )
+
+AM_CONDITIONAL([ENABLE_EDS], [test "x$enable_eds_backend" = "xyes"])
+
 # Automatically check the dependencies for the libsocialweb backend
 SW_CLIENT_REQUIRED=0.25.15
 AC_ARG_ENABLE(libsocialweb-backend,
@@ -124,6 +132,8 @@ VALA_REQUIRED=0.12.0
 VALADOC_REQUIRED=0.2.1
 TRACKER_SPARQL_REQUIRED=0.10
 GCONF2_REQUIRED=2.31
+EBOOK_REQUIRED=3.0.1
+EDATASERVER_REQUIRED=3.0.1
 
 PKG_CHECK_MODULES([GLIB],
                   [glib-2.0 >= $GLIB_REQUIRED
@@ -142,6 +152,11 @@ if test x$enable_tracker_backend = xyes; then
                           [tracker-sparql-0.10 >= $TRACKER_SPARQL_REQUIRED])
 fi
 
+if test x$enable_eds_backend = xyes; then
+        PKG_CHECK_MODULES([EBOOK], [libebook-1.2 >= $EBOOK_REQUIRED])
+        PKG_CHECK_MODULES([EDATASERVER], [libedataserver-1.2 >= $EDATASERVER_REQUIRED])
+fi
+
 #
 # Vala building options -- allows tarball builds without installing Vala
 #
@@ -218,6 +233,9 @@ if test "x$enable_vala" = "xyes" ; then
           VALA_CHECK_PACKAGES([tracker-sparql-0.10])
         fi
 
+        if test x$enable_eds_backend = xyes; then
+          VALA_CHECK_PACKAGES([libxml-2.0])
+        fi
 fi
 
 # this will set HAVE_INTROSPECTION
@@ -244,6 +262,8 @@ BACKEND_KF='$(top_builddir)/backends/key-file/.libs/libfolks-backend-key-file.so
 AC_SUBST([BACKEND_KF])
 BACKEND_TP='$(top_builddir)/backends/telepathy/.libs/libfolks-backend-telepathy.so'
 AC_SUBST([BACKEND_TP])
+BACKEND_EDS='$(top_builddir)/backends/eds/.libs/libfolks-backend-eds.so'
+AC_SUBST([BACKEND_EDS])
 
 if test x$have_libsocialweb_backend = xyes; then
   BACKEND_LIBSOCIALWEB='$(top_builddir)/backends/libsocialweb/.libs/libfolks-backend-libsocialweb.so'
@@ -255,6 +275,11 @@ if test x$enable_tracker_backend = xyes; then
   AC_SUBST([BACKEND_TRACKER])
 fi
 
+if test x$enable_eds_backend = xyes; then
+  BACKEND_EDS='$(top_builddir)/backends/eds/.libs/libfolks-backend-eds.so'
+  AC_SUBST([BACKEND_EDS])
+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$have_libsocialweb_backend = xyes; then
@@ -265,6 +290,10 @@ 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
+if test x$enable_eds_backend = xyes; then
+  EDS_BACKEND_UNINST_PATH='$(BACKEND_EDS)'
+  BACKEND_UNINST_PATH="$BACKEND_UNINST_PATH:$EDS_BACKEND_UNINST_PATH"
+fi
 AC_SUBST([BACKEND_UNINST_PATH])
 
 # -----------------------------------------------------------
@@ -404,6 +433,8 @@ AC_CONFIG_FILES([
     backends/telepathy/lib/folks-telepathy-uninstalled.pc
     backends/tracker/lib/folks-tracker.pc
     backends/tracker/lib/folks-tracker-uninstalled.pc
+    backends/eds/lib/folks-eds.pc
+    backends/eds/lib/folks-eds-uninstalled.pc
     folks/folks.pc
     folks/folks-uninstalled.pc
     Makefile
@@ -417,6 +448,8 @@ AC_CONFIG_FILES([
     backends/telepathy/lib/Makefile
     backends/tracker/Makefile
     backends/tracker/lib/Makefile
+    backends/eds/Makefile
+    backends/eds/lib/Makefile
     folks/Makefile
     docs/Makefile
     po/Makefile.in
@@ -433,6 +466,8 @@ AC_CONFIG_FILES([
     tests/lib/key-file/Makefile
     tests/lib/libsocialweb/Makefile
     tests/lib/libsocialweb/session.conf
+    tests/eds/Makefile
+    tests/lib/eds/Makefile
     tests/lib/telepathy/Makefile
     tests/lib/tracker/Makefile
     tests/lib/telepathy/contactlist/Makefile
@@ -457,6 +492,6 @@ Configure summary:
         Inspector tool..............:  ${with_inspect_tool}
         Tracker backend.............:  ${enable_tracker_backend}
         Libsocialweb backend........:  ${have_libsocialweb_backend}
-
+        E-D-S backend...............:  ${enable_eds_backend}
 
 "
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0c29f1e..00e2f40 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -15,6 +15,10 @@ if ENABLE_TRACKER
 SUBDIRS += tracker
 endif
 
+if ENABLE_EDS
+SUBDIRS += eds
+endif
+
 DIST_SUBDIRS = \
     data \
     lib \
@@ -24,6 +28,7 @@ DIST_SUBDIRS = \
     telepathy \
     libsocialweb \
     tracker \
+    eds \
     $(NULL)
 
 TESTS_ENVIRONMENT = \
diff --git a/tests/eds/Makefile.am b/tests/eds/Makefile.am
new file mode 100644
index 0000000..6fc1645
--- /dev/null
+++ b/tests/eds/Makefile.am
@@ -0,0 +1,211 @@
+AM_CPPFLAGS = \
+	$(GLIB_CFLAGS) \
+	$(GEE_CFLAGS) \
+	$(EBOOK_CFLAGS) \
+	$(EDATASERVER_CFLAGS) \
+	$(GCONF2_CFLAGS) \
+	-I$(top_srcdir)/folks \
+	-I$(top_srcdir)/backends/eds/lib \
+	-I$(top_srcdir)/tests/lib \
+	-I$(top_srcdir)/tests/lib/eds \
+	-include $(CONFIG_HEADER) \
+	$(NULL)
+
+LDADD = \
+	$(top_builddir)/tests/lib/eds/libeds-test.la \
+	$(top_builddir)/tests/lib/libfolks-test.la \
+	$(top_builddir)/backends/eds/lib/libfolks-eds.la
+	$(top_builddir)/folks/libfolks.la \
+	$(GLIB_LIBS) \
+	$(GEE_LIBS) \
+	$(GCONF2_LIBS) \
+	-L$(top_srcdir)/backends/eds/lib \
+	$(NULL)
+
+AM_VALAFLAGS = \
+	--vapidir=. \
+	--vapidir=$(top_srcdir)/folks \
+	--vapidir=$(top_srcdir)/backends/eds/lib \
+	--vapidir=$(top_srcdir)/tests/lib \
+	--vapidir=$(top_srcdir)/tests/lib/eds \
+	--pkg gobject-2.0 \
+	--pkg gio-2.0 \
+	--pkg gee-1.0 \
+	--pkg folks \
+	--pkg folks-test \
+	--pkg libebook-1.2 \
+	--pkg libedataserver-1.2 \
+	--pkg libxml-2.0 \
+	--pkg folks-eds \
+	--pkg eds-test \
+	--pkg gconf-2.0 \
+	$(NULL)
+
+# in order from least to most complex
+noinst_PROGRAMS = \
+	persona-store-tests \
+	individual-retrieval \
+	phone-details \
+	email-details \
+	name-details \
+	removing-contacts \
+	updating-contacts \
+	avatar-details \
+	add-persona \
+	im-details \
+	postal-address-details \
+	remove-persona \
+	set-avatar \
+	set-emails \
+	set-im-addresses \
+	set-full-name \
+	set-structured-name \
+	set-phones \
+	set-postal-addresses \
+	link-personas \
+	set-notes \
+	add-contacts-stress-test \
+	$(NULL)
+
+RUN_WITH_PRIVATE_BUS = $(top_srcdir)/tests/tools/with-session-bus-eds.sh
+
+backend_store_key_file=$(srcdir)/data/backend-eds-only.ini
+avatar_file= abs_top_srcdir@/tests/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)
+
+persona_store_tests_SOURCES = \
+	persona-store-tests.vala \
+	$(NULL)
+
+individual_retrieval_SOURCES = \
+	individual-retrieval.vala \
+	$(NULL)
+
+removing_contacts_SOURCES = \
+	removing-contacts.vala \
+	$(NULL)
+
+updating_contacts_SOURCES = \
+	updating-contacts.vala \
+	$(NULL)
+
+phone_details_SOURCES = \
+	phone-details.vala \
+	$(NULL)
+
+email_details_SOURCES = \
+	email-details.vala \
+	$(NULL)
+
+name_details_SOURCES = \
+	name-details.vala \
+	$(NULL)
+
+avatar_details_SOURCES = \
+	avatar-details.vala \
+	$(NULL)
+
+add_persona_SOURCES = \
+	add-persona.vala \
+	$(NULL)
+
+im_details_SOURCES = \
+	im-details.vala \
+	$(NULL)
+
+postal_address_details_SOURCES = \
+	postal-address-details.vala \
+	$(NULL)
+
+remove_persona_SOURCES = \
+	remove-persona.vala \
+	$(NULL)
+
+set_avatar_SOURCES = \
+	set-avatar.vala \
+	$(NULL)
+
+set_emails_SOURCES = \
+	set-emails.vala \
+	$(NULL)
+
+set_im_addresses_SOURCES = \
+	set-im-addresses.vala \
+	$(NULL)
+
+set_full_name_SOURCES = \
+	set-full-name.vala \
+	$(NULL)
+
+set_structured_name_SOURCES = \
+	set-structured-name.vala \
+	$(NULL)
+
+set_phones_SOURCES = \
+	set-phones.vala \
+	$(NULL)
+
+set_postal_addresses_SOURCES = \
+	set-postal-addresses.vala \
+	$(NULL)
+
+link_personas_SOURCES = \
+	link-personas.vala \
+	$(NULL)
+
+set_notes_SOURCES = \
+	set-notes.vala \
+	$(NULL)
+
+add_contacts_stress_test_SOURCES = \
+	add-contacts-stress-test.vala \
+	$(NULL)
+
+CLEANFILES = \
+        *.pid \
+        *.address \
+        $(TESTS) \
+        $(NULL)
+
+MAINTAINERCLEANFILES = \
+        $(addsuffix .c,$(noinst_PROGRAMS)) \
+        persona_store_tests_vala.stamp \
+        individual_retrieval_vala.stamp \
+        removing_contacts_vala.stamp \
+        updating_contacts_vala.stamp \
+        phone_details_vala.stamp \
+        name_details_vala.stamp \
+        email_details_vala.stamp \
+        avatar_details_vala.stamp \
+        add_persona_vala.stamp \
+        im_details_vala.stamp \
+        postal_address_details_vala.stamp \
+        remove_persona_vala.stamp \
+        set_avatar_vala.stamp \
+        set_emails_vala.stamp \
+        set_im_addresses_vala.stamp \
+        set_full_name_vala.stamp \
+        set_structured_name_vala.stamp \
+        set_phones_vala.stamp \
+        set_postal_addresses_vala.stamp \
+        link_personas_vala.stamp \
+        set_notes_vala.stamp \
+	add_contacts_stress_test_vala.stamp \
+        $(NULL)
+
+EXTRA_DIST = \
+	$(MAINTAINERCLEANFILES) \
+	$(backend_store_key_file) \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
+-include $(top_srcdir)/check.mk
\ No newline at end of file
diff --git a/tests/eds/add-contacts-stress-test.vala b/tests/eds/add-contacts-stress-test.vala
new file mode 100644
index 0000000..b850ef3
--- /dev/null
+++ b/tests/eds/add-contacts-stress-test.vala
@@ -0,0 +1,200 @@
+/*
+ * 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 Folks;
+using Gee;
+
+public class AddContactsStressTestTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private Edsf.PersonaStore _pstore;
+  private bool _added_contacts = false;
+  private HashTable<string, bool> _contacts_found;
+  private int _contacts_cnt = 150;
+
+  public AddContactsStressTestTests ()
+    {
+      base ("AddContactsStressTestTests");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      var test_desc = "stress testing adding (%d) contacts to e-d-s ".printf (
+          this._contacts_cnt);
+      this.add_test (test_desc, this.test_add_contacts);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  public void test_add_contacts ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+
+     this._contacts_found = new HashTable<string, bool> (str_hash, str_equal);
+     for (var i=0; i<this._contacts_cnt; i++)
+       {
+         var persona_name = "full_name -%d".printf (i);
+         this._contacts_found.insert (persona_name, false);
+       }
+
+      this._test_add_persona_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      int found = 0;
+      foreach (var k in this._contacts_found.get_values ())
+        {
+          if (k)
+            found++;
+        }
+
+      assert (found == this._contacts_cnt);
+    }
+
+  private async void _test_add_persona_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 ();
+
+          this._pstore = null;
+          foreach (var backend in store.enabled_backends.values)
+            {
+              this._pstore =
+                (Edsf.PersonaStore) backend.persona_stores.get ("local://test");
+              if (this._pstore != null)
+                break;
+            }
+          assert (this._pstore != null);
+          this._pstore.notify["is-prepared"].connect (this._notify_pstore_cb);
+          this._try_to_add ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private async void _add_contacts ()
+    {
+     for (var i=0; i<this._contacts_cnt; i++)
+       {
+         var persona_name = "full_name -%d".printf (i);
+         HashTable<string, Value?> details = new HashTable<string, Value?>
+           (str_hash, str_equal);
+
+         Value? v1 = Value (typeof (string));
+         v1.set_string (persona_name);
+         details.insert (Folks.PersonaStore.detail_key (
+                 PersonaDetail.FULL_NAME),
+             (owned) v1);
+
+         try
+           {
+             yield this._aggregator.add_persona_from_details (null,
+                 this._pstore, details);
+           }
+         catch (Folks.IndividualAggregatorError e)
+           {
+             GLib.warning ("[AddContactsStressTest]: %d: %s\n", i,
+                 e.message);
+           }
+       }
+    }
+
+  private void _individuals_changed_cb
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (var i in added)
+        {
+          assert (this._contacts_found.lookup (i.full_name) == false);
+          this._contacts_found.replace (i.full_name, true);
+        }
+
+      assert (removed.size == 0);
+
+      this._exit_if_all_contacts_found ();
+    }
+
+  private void _notify_pstore_cb (Object _pstore, ParamSpec ps)
+    {
+      this._try_to_add ();
+    }
+
+  private void _try_to_add ()
+    {
+      lock (this._added_contacts)
+        {
+          if (this._pstore.is_prepared &&
+              this._added_contacts == false)
+            {
+              this._added_contacts = true;
+              this._add_contacts ();
+            }
+        }
+    }
+
+  private void _exit_if_all_contacts_found ()
+    {
+      foreach (var k in this._contacts_found.get_keys ())
+        {
+          var v = this._contacts_found.lookup (k);
+          if (v == false)
+            return;
+        }
+      this._main_loop.quit ();
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new AddContactsStressTestTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/add-persona.vala b/tests/eds/add-persona.vala
new file mode 100644
index 0000000..acc0660
--- /dev/null
+++ b/tests/eds/add-persona.vala
@@ -0,0 +1,394 @@
+/*
+ * 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 Folks;
+using Gee;
+
+public class AddPersonaTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private string _persona_fullname;
+  private string _email_1;
+  private Edsf.PersonaStore _pstore;
+  private bool _added_persona = false;
+  private HashTable<string, bool> _properties_found;
+  private string _avatar_path;
+  private string _im_addr_1;
+  private string _im_addr_2;
+  private string _phone_1;
+  private string _phone_2;
+  private PostalAddress _address;
+  private string _po_box = "12345";
+  private string _locality = "locality";
+  private string _postal_code = "code";
+  private string _street = "some street";
+  private string _extension = "some extension";
+  private string _country = "some country";
+  private string _region = "some region";
+  private string _family_name;
+  private string _given_name;
+  private string _note = "This is a note.";
+
+  public AddPersonaTests ()
+    {
+      base ("AddPersonaTests");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("test adding a persona to e-d-s ", this.test_add_persona);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  public void test_add_persona ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      this._persona_fullname = "persona #1";
+      this._email_1 = "someone-1 example org";
+      this._avatar_path = Environment.get_variable ("AVATAR_FILE_PATH");
+      this._im_addr_1 = "someone-1 jabber example org";
+      this._im_addr_2 = "someone-2 jabber example org";
+      this._phone_1 = "12345";
+      this._phone_2 = "54321";
+      this._family_name = "family";
+      this._given_name = "given";
+
+      var types =  new HashSet<string> ();
+      types.add (Edsf.Persona.address_fields[0]);
+      this._address = new PostalAddress (this._po_box,
+          this._extension, this._street, this._locality, this._region,
+          this._postal_code, this._country, null, types, null);
+
+      this._properties_found = new HashTable<string, bool>
+          (str_hash, str_equal);
+      this._properties_found.insert ("full_name", false);
+      this._properties_found.insert ("email-1", false);
+      this._properties_found.insert ("avatar", false);
+      this._properties_found.insert ("im-addr-1", false);
+      this._properties_found.insert ("im-addr-2", false);
+      this._properties_found.insert ("phone-1", false);
+      this._properties_found.insert ("phone-2", false);
+      this._properties_found.insert ("postal-address-1", false);
+      this._properties_found.insert ("structured_name", false);
+      this._properties_found.insert ("note", false);
+
+      this._test_add_persona_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      foreach (var k in this._properties_found.get_values ())
+        {
+          assert (k);
+        }
+    }
+
+  private async void _test_add_persona_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 ();
+
+          this._pstore = null;
+          foreach (var backend in store.enabled_backends.values)
+            {
+              this._pstore =
+                (Edsf.PersonaStore) backend.persona_stores.get ("local://test");
+              if (this._pstore != null)
+                break;
+            }
+          assert (this._pstore != null);
+          this._pstore.notify["is-prepared"].connect (this._notify_pstore_cb);
+          this._try_to_add ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private async void _add_persona ()
+    {
+      HashTable<string, Value?> details = new HashTable<string, Value?>
+          (str_hash, str_equal);
+
+      Value? v1 = Value (typeof (string));
+      v1.set_string (this._persona_fullname);
+      details.insert (Folks.PersonaStore.detail_key (PersonaDetail.FULL_NAME),
+          (owned) v1);
+
+      Value? v2 = Value (typeof (Set<FieldDetails>));
+      var emails = new HashSet<FieldDetails> ();
+      var email_1 = new FieldDetails (this._email_1);
+      email_1.set_parameter ("type", Edsf.Persona.email_fields[0]);
+      emails.add (email_1);
+      v2.set_object (emails);
+      details.insert (
+          Folks.PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES),
+          (owned) v2);
+
+      Value? v3 = Value (typeof (File));
+      File avatar = File.new_for_path (this._avatar_path);
+      v3.set_object (avatar);
+      details.insert (Folks.PersonaStore.detail_key (PersonaDetail.AVATAR),
+          (owned) v3);
+
+      Value? v4 = Value (typeof (MultiMap<string, string>));
+      var im_addrs = new HashMultiMap<string, string> ();
+      im_addrs.set ("jabber", this._im_addr_1);
+      im_addrs.set ("yahoo", this._im_addr_2);
+      v4.set_object (im_addrs);
+      details.insert (
+         Folks.PersonaStore.detail_key (PersonaDetail.IM_ADDRESSES), v4);
+
+      Value? v5 = Value (typeof (Set<FieldDetails>));
+      var phones = new HashSet<FieldDetails> ();
+      var phone_1 = new FieldDetails (this._phone_1);
+      phone_1.set_parameter ("type", Edsf.Persona.phone_fields[0]);
+      phones.add (phone_1);
+      var phone_2 = new FieldDetails (this._phone_2);
+      phone_2.set_parameter ("type", Edsf.Persona.phone_fields[1]);
+      phones.add (phone_2);
+      v5.set_object (phones);
+      details.insert (
+          Folks.PersonaStore.detail_key (PersonaDetail.PHONE_NUMBERS),
+          (owned) v5);
+
+      Value? v6 = Value (typeof (Set<PostalAddress>));
+      var postal_addresses = new HashSet<PostalAddress> ();
+
+      var types =  new HashSet<string> ();
+      types.add (Edsf.Persona.address_fields[0]);
+      PostalAddress postal_a = new PostalAddress (this._po_box,
+          this._extension, this._street, this._locality, this._region,
+          this._postal_code, this._country, null, types, null);
+      postal_addresses.add (postal_a);
+      v6.set_object (postal_addresses);
+      details.insert (
+          Folks.PersonaStore.detail_key (PersonaDetail.POSTAL_ADDRESSES),
+          (owned) v6);
+
+      Value? v7 = Value (typeof (StructuredName));
+      StructuredName sname = new StructuredName (this._family_name,
+          this._given_name, null, null, null);
+      v7.set_object (sname);
+      details.insert (
+          Folks.PersonaStore.detail_key (PersonaDetail.STRUCTURED_NAME),
+          (owned) v7);
+
+      Value? v8 = Value (typeof (Set<Note>));
+      var notes = new HashSet<Note> ();
+      var note = new Note(this._note);
+      notes.add (note);
+      v8.set_object (notes);
+      details.insert (
+          Folks.PersonaStore.detail_key (PersonaDetail.NOTES),
+          (owned) v8);
+
+      try
+        {
+          yield this._aggregator.add_persona_from_details (null,
+              this._pstore, details);
+        }
+      catch (Folks.IndividualAggregatorError e)
+        {
+          GLib.warning ("[AddPersonaError] add_persona_from_details: %s\n",
+              e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (var i in added)
+        {
+          if (i.is_user == false)
+            {
+              i.notify["full-name"].connect (this._notify_cb);
+              i.notify["email-addresses"].connect (this._notify_cb);
+              i.notify["avatar"].connect (this._notify_cb);
+              i.notify["im-addresses"].connect (this._notify_cb);
+              i.notify["phone-numbers"].connect (this._notify_cb);
+              i.notify["postal-addresses"].connect (this._notify_cb);
+              i.notify["structured-name"].connect (this._notify_cb);
+              i.notify["notes"].connect (this._notify_cb);
+
+              this._check_properties (i);
+            }
+        }
+
+      assert (removed.size == 0);
+    }
+
+  private void _notify_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      this._check_properties (i);
+    }
+
+  private void _notify_pstore_cb (Object _pstore, ParamSpec ps)
+    {
+      this._try_to_add ();
+    }
+
+  private void _try_to_add ()
+    {
+      lock (this._added_persona)
+        {
+          if (this._pstore.is_prepared &&
+              this._added_persona == false)
+            {
+              this._added_persona = true;
+              this._add_persona ();
+            }
+        }
+    }
+
+  private void _check_properties (Individual i)
+    {
+      if (i.full_name == this._persona_fullname)
+        this._properties_found.replace ("full_name", true);
+
+      foreach (var e in i.email_addresses)
+        {
+          if (e.value == this._email_1)
+            {
+              this._properties_found.replace ("email-1", true);
+            }
+        }
+
+      if (i.avatar != null)
+        {
+          uint8[] content_a;
+          uint8[] content_b;
+          var b = File.new_for_path (this._avatar_path);
+
+          try
+            {
+              i.avatar.load_contents (null, out content_a);
+              b.load_contents (null, out content_b);
+
+              if (((string) content_a) == ((string) content_b))
+                {
+                  this._properties_found.replace ("avatar", true);
+                }
+            }
+          catch (GLib.Error e)
+            {
+              GLib.warning ("Couldn't load avatars: %s",
+                  e.message);
+            }
+        }
+
+      foreach (var proto in i.im_addresses.get_keys ())
+        {
+          var addrs = i.im_addresses.get (proto);
+          foreach (var a in addrs)
+            {
+              if (a == this._im_addr_1)
+                this._properties_found.replace ("im-addr-1", true);
+              else if (a == this._im_addr_2)
+                this._properties_found.replace ("im-addr-2", true);
+            }
+        }
+
+      foreach (var e in i.phone_numbers)
+        {
+          if (e.value == this._phone_1)
+            {
+              this._properties_found.replace ("phone-1", true);
+            }
+          else if (e.value == this._phone_2)
+            {
+              this._properties_found.replace ("phone-2", true);
+            }
+        }
+
+      foreach (var pa in i.postal_addresses)
+        {
+          this._address.uid = pa.uid;
+          if (pa.equal (this._address))
+            this._properties_found.replace ("postal-address-1", true);
+        }
+
+      if (i.structured_name != null &&
+          i.structured_name.family_name == this._family_name &&
+          i.structured_name.given_name == this._given_name)
+        this._properties_found.replace ("structured_name", true);
+
+      foreach (var note in i.notes)
+        {
+          if (note.content == this._note)
+            {
+              this._properties_found.replace ("note", true);
+              break;
+            }
+        }
+
+      this._exit_if_all_properties_found ();
+    }
+
+  private void _exit_if_all_properties_found ()
+    {
+      foreach (var k in this._properties_found.get_keys ())
+        {
+          var v = this._properties_found.lookup (k);
+          if (v == false)
+            return;
+        }
+      this._main_loop.quit ();
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new AddPersonaTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/avatar-details.vala b/tests/eds/avatar-details.vala
new file mode 100644
index 0000000..63332ef
--- /dev/null
+++ b/tests/eds/avatar-details.vala
@@ -0,0 +1,154 @@
+/*
+ * 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 EdsTest;
+using Folks;
+using Gee;
+
+public class AvatarDetailsTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private Gee.HashMap<string, Value?> _c1;
+  private bool _avatars_are_equal;
+  private string _avatar_path;
+
+  public AvatarDetailsTests ()
+    {
+      base ("AvatarDetails");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("avatar details interface", this.test_avatar);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  public void test_avatar ()
+    {
+      this._c1 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      this._avatar_path = Environment.get_variable ("AVATAR_FILE_PATH");
+      this._avatars_are_equal = false;
+      Value? v;
+
+      this._eds_backend.reset();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      this._c1.set ("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string (this._avatar_path);
+      this._c1.set ("avatar",(owned) v);
+      this._eds_backend.add_contact (this._c1);
+
+      this._test_avatar_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          return false;
+        });
+
+      this._main_loop.run ();
+
+      assert (this._avatars_are_equal);
+   }
+
+  private async void _test_avatar_async ()
+    {
+
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+
+      assert (removed.size == 0);
+
+      foreach (Individual i in added)
+        {
+          assert (i.personas.size == 1);
+
+          if (i.full_name == "bernie h. innocenti")
+            {
+              uint8[] content_a;
+              uint8[] content_b;
+              var b = File.new_for_path (this._avatar_path);
+
+              try
+                {
+                  i.avatar.load_contents (null, out content_a);
+                  b.load_contents (null, out content_b);
+
+                  if (((string) content_a) == ((string) content_b))
+                    {
+                      this._avatars_are_equal = true;
+                      this._main_loop.quit ();
+                    }
+                }
+              catch (GLib.Error e)
+                {
+                  GLib.warning ("couldn't load file a");
+                }
+            }
+        }
+   }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new AvatarDetailsTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/data/backend-eds-only.ini b/tests/eds/data/backend-eds-only.ini
new file mode 100644
index 0000000..e8a66e1
--- /dev/null
+++ b/tests/eds/data/backend-eds-only.ini
@@ -0,0 +1,5 @@
+[eds]
+enabled=true
+
+[all-others]
+enabled=false
diff --git a/tests/eds/email-details.vala b/tests/eds/email-details.vala
new file mode 100644
index 0000000..2bcdbe0
--- /dev/null
+++ b/tests/eds/email-details.vala
@@ -0,0 +1,210 @@
+/*
+ * 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 EdsTest;
+using Folks;
+using Gee;
+
+public class EmailDetailsTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private int _email_count;
+  private HashSet<string> _email_types;
+  private Gee.HashMap<string, Value?> _c1;
+  private Gee.HashMap<string, Value?> _c2;
+  private Gee.HashMap<string, Value?> _c3;
+
+  public EmailDetailsTests ()
+    {
+      base ("EmailDetails");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("email details interface", this.test_email_details);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  public void test_email_details ()
+    {
+      this._email_count = 0;
+      this._email_types = new HashSet<string> (str_hash,
+          str_equal);
+      this._c1 = new Gee.HashMap<string, Value?> ();
+      this._c2 = new Gee.HashMap<string, Value?> ();
+      this._c3 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+
+      this._eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      this._c1.set ("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("bernie example org");
+      this._c1.set (Edsf.Persona.email_fields[0], (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("bernie innocenti example org");
+      this._c1.set (Edsf.Persona.email_fields[1], (owned) v);
+      this._eds_backend.add_contact (this._c1);
+
+      v = Value (typeof (string));
+      v.set_string ("richard m. stallman");
+      this._c2.set ("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("rms example org");
+      this._c2.set (Edsf.Persona.email_fields[0], (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("rms 1 example org");
+      this._c2.set (Edsf.Persona.email_fields[1], (owned) v);
+      this._eds_backend.add_contact (this._c2);
+
+      v = Value (typeof (string));
+      v.set_string ("foo bar");
+      this._c3.set ("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("foo example org");
+      this._c3.set (Edsf.Persona.email_fields[0], (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("foo bar example org");
+      this._c3.set (Edsf.Persona.email_fields[1], (owned) v);
+      this._eds_backend.add_contact (this._c3);
+
+      this._test_email_details_async ();
+
+      Timeout.add_seconds (5, () =>
+          {
+            this._main_loop.quit ();
+            return false;
+          });
+
+      this._main_loop.run ();
+
+      assert (this._email_count == 6);
+      assert (this._email_types.size == 1);
+      assert (this._c1.size == 0);
+      assert (this._c2.size == 0);
+      assert (this._c3.size == 0);
+
+      foreach (var pt in this._email_types)
+        {
+          assert (pt == "OTHER");
+        }
+    }
+
+  private async void _test_email_details_async ()
+    {
+
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          unowned Gee.HashMap<string, Value?> contact = null;
+          assert (i.personas.size == 1);
+
+          if (i.full_name == "bernie h. innocenti")
+            {
+              contact = this._c1;
+            }
+          else if (i.full_name == "richard m. stallman")
+            {
+              contact = this._c2;
+            }
+          else if (i.full_name == "foo bar")
+            {
+              contact = this._c3;
+            }
+
+          if (contact == null)
+            {
+              continue;
+            }
+
+          contact.unset ("full_name");
+          var email_owner = (Folks.EmailDetails) i;
+          foreach (var e in email_owner.email_addresses)
+            {
+              this._email_count++;
+
+              bool found = false;
+              for (int j = 0; j < 2; j++) {
+                  var v = Edsf.Persona.email_fields[j];
+                  if (contact.get (v) != null &&
+                      contact.get (v).get_string () == e.value) {
+                      found = true;
+                      contact.unset (v);
+                  }
+              }
+              assert (found);
+              foreach (var v in e.get_parameter_values("type"))
+                {
+                  this._email_types.add (v);
+                }
+            }
+        }
+
+      assert (removed.size == 0);
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new EmailDetailsTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/im-details.vala b/tests/eds/im-details.vala
new file mode 100644
index 0000000..565c717
--- /dev/null
+++ b/tests/eds/im-details.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 Folks;
+using Gee;
+
+public class ImDetailsTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private int _num_addrs;
+  private bool _found_addr_1;
+  private bool _found_addr_2;
+  private string _fullname;
+  private string _im_addrs;
+
+  public ImDetailsTests ()
+    {
+      base ("ImDetailsTests");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("test im details interface",
+          this.test_im_details_interface);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  public void test_im_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      this._fullname = "persona #1";
+      this._im_addrs = "im_jabber_home_1#test1 example org";
+      this._im_addrs += ",im_yahoo_home_1#test2 example org";
+      Value? v;
+
+      this._eds_backend.reset();
+
+      v = Value (typeof (string));
+      v.set_string (this._fullname);
+      c1.set ("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string (this._im_addrs);
+      c1.set ("im_addresses", (owned) v);
+
+      this._eds_backend.add_contact (c1);
+
+      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);
+    }
+
+  private async void _test_im_details_interface_async ()
+    {
+
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (var 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.get (proto);
+
+                 if (proto == "jabber")
+                    {
+                      if (addrs.contains ("test1 example org"))
+                        {
+                          this._found_addr_1 = true;
+                          this._num_addrs++;
+                        }
+                    }
+                  else if (proto == "yahoo")
+                    {
+                      if (addrs.contains ("test2 example org"))
+                        {
+                          this._found_addr_2 = true;
+                          this._num_addrs++;
+                        }
+                    }
+                }
+            }
+        }
+
+      assert (removed.size == 0);
+
+      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 ImDetailsTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/individual-retrieval.vala b/tests/eds/individual-retrieval.vala
new file mode 100644
index 0000000..e0f8737
--- /dev/null
+++ b/tests/eds/individual-retrieval.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 EdsTest;
+using Folks;
+using Gee;
+
+public class IndividualRetrievalTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private HashSet<string> _found_individuals;
+
+  public IndividualRetrievalTests ()
+    {
+      base ("IndividualRetrieval");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("singleton individuals", this.test_singleton_individuals);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  public void test_singleton_individuals ()
+    {
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      Gee.HashMap<string, Value?> c2 = new Gee.HashMap<string, Value?> ();
+      this._found_individuals = new HashSet<string> (str_hash,
+          str_equal);
+      this._main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+
+      this._eds_backend.reset();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      c1.set("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("bernie example org");
+      c1.set(Edsf.Persona.email_fields[0], (owned) v);
+      this._eds_backend.add_contact (c1);
+
+      v = Value (typeof (string));
+      v.set_string ("richard m. stallman");
+      c2.set("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("rms example org");
+      c2.set(Edsf.Persona.email_fields[0], (owned) v);
+      this._eds_backend.add_contact (c2);
+
+      this._test_singleton_individuals_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          return false;
+        });
+
+      this._main_loop.run ();
+
+      assert (this._found_individuals.size == 2);
+    }
+
+  private async void _test_singleton_individuals_async ()
+    {
+
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          assert (i.personas.size == 1);
+          this._found_individuals.add (i.id);
+        }
+
+      if (this._found_individuals.size == 2)
+        {
+          this._main_loop.quit ();
+        }
+
+      assert (removed.size == 0);
+    }
+}
+
+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/eds/link-personas.vala b/tests/eds/link-personas.vala
new file mode 100644
index 0000000..44c66a7
--- /dev/null
+++ b/tests/eds/link-personas.vala
@@ -0,0 +1,408 @@
+/*
+ * 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 Folks;
+using Gee;
+
+enum LinkingMethod
+{
+  IM_ADDRESSES,
+  LOCAL_IDS,
+  WEB_SERVICE_ADDRESSES
+}
+
+
+public class LinkPersonasTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private string _persona_fullname_1;
+  private string _persona_fullname_2;
+  private string _im_address_1 = "someone-1 jabber example org";
+  private string _im_address_2 = "someone-2 jabber example org";
+  private bool _linking_fired;
+  private bool _persona_found_1;
+  private bool _persona_found_2;
+  private string _persona_iid_1;
+  private string _persona_iid_2;
+  private HashSet<Persona> _personas;
+  private int _removed_individuals;
+  private string _folks_config_key = "/system/folks/backends/primary_store";
+  private unowned GConf.Client _gconf_client;
+  private Gee.HashMap<string, string> _linking_props;
+  private LinkingMethod _linking_method;
+
+  public LinkPersonasTests ()
+    {
+      base ("LinkPersonasTests");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("test linking personas via IM addresses",
+          this.test_linking_personas_via_im_addresses);
+      this.add_test ("test linking personas via local IDs",
+          this.test_linking_personas_via_local_ids);
+      this.add_test ("test linking personas via web service addresses",
+          this.test_linking_personas_via_web_service_addresses);
+    }
+
+  public override void set_up ()
+    {
+      /* We configure eds as the primary (writeable) store */
+      this._gconf_client = GConf.Client.get_default ();
+      try
+        {
+          GConf.Value val = new GConf.Value (GConf.ValueType.STRING);
+          val.set_string ("eds");
+          this._gconf_client.set (this._folks_config_key, val);
+        }
+      catch (GLib.Error e)
+        {
+          warning ("Couldn't set primary store: %s\n", e.message);
+        }
+
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+
+      try
+        {
+          this._gconf_client.unset (this._folks_config_key);
+        }
+      catch (GLib.Error e)
+        {
+          warning ("Couldn't unset primary store: %s\n", e.message);
+        }
+    }
+
+  public void test_linking_personas_via_im_addresses ()
+    {
+      this._linking_method = LinkingMethod.IM_ADDRESSES;
+      this._test_linking_personas ();
+    }
+
+  public void test_linking_personas_via_local_ids ()
+    {
+      this._linking_method = LinkingMethod.LOCAL_IDS;
+      this._test_linking_personas ();
+    }
+
+  public void test_linking_personas_via_web_service_addresses ()
+    {
+      this._linking_method = LinkingMethod.WEB_SERVICE_ADDRESSES;
+      this._test_linking_personas ();
+    }
+
+  private void _test_linking_personas ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      this._persona_fullname_1 = "persona #1";
+      this._persona_fullname_2 = "persona #2";
+      this._personas = new HashSet<Persona> ();
+      this._removed_individuals = 0;
+      this._persona_found_1 = false;
+      this._persona_found_2 = false;
+      this._linking_fired = false;
+      this._persona_iid_1 = "";
+      this._persona_iid_2 = "";
+
+      this._linking_props = new Gee.HashMap<string, string> ();
+      if (this._linking_method == LinkingMethod.IM_ADDRESSES ||
+          this._linking_method == LinkingMethod.WEB_SERVICE_ADDRESSES)
+        {
+          this._linking_props.set ("prop1", this._im_address_1);
+          this._linking_props.set ("prop2", this._im_address_2);
+        }
+
+      this._test_linking_personas_async ();
+
+      var timer_id = Timeout.add_seconds (8, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      /* Check we get the new individual (containing the linked
+       * personas) and that the previous ones were removed. */
+      assert (this._linking_props.size == 0);
+      assert (this._removed_individuals == 2);
+
+      GLib.Source.remove (timer_id);
+      this._aggregator = null;
+      this._main_loop = null;
+    }
+
+  private async void _test_linking_personas_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 ();
+
+          PersonaStore pstore = null;
+          foreach (var backend in store.enabled_backends.values)
+            {
+              pstore = backend.persona_stores.get ("local://test");
+              if (pstore != null)
+                break;
+            }
+          assert (pstore != null);
+
+          yield _add_personas (pstore);
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  /* Here is how this test is expected to work:
+   * - we start by adding 2 personas
+   * - this should trigger individuals-changed with 2 new individuals
+   * - we ask the IndividualAggregator to link the 2 personas coming
+   *   from those individuals
+   * - we wait for a new Individual which contains the linkable
+   *   attributes of these 2 personas
+   */
+  private async void _add_personas (PersonaStore pstore)
+    {
+      HashTable<string, Value?> details1 = new HashTable<string, Value?>
+          (str_hash, str_equal);
+      Value? v1;
+      var wsk =
+        Folks.PersonaStore.detail_key (PersonaDetail.WEB_SERVICE_ADDRESSES);
+
+      if (this._linking_method == LinkingMethod.IM_ADDRESSES)
+        {
+          v1 = Value (typeof (MultiMap<string, string>));
+          var im_addrs1 = new HashMultiMap<string, string> ();
+          im_addrs1.set ("jabber", this._im_address_1);
+          v1.set_object (im_addrs1);
+          details1.insert ("im-addresses", (owned) v1);
+        }
+      else if (this._linking_method == LinkingMethod.WEB_SERVICE_ADDRESSES)
+        {
+          v1 = Value (typeof (MultiMap<string, string>));
+          var wsa1 = new HashMultiMap<string, string> ();
+          wsa1.set ("twitter", this._im_address_1);
+          v1.set_object (wsa1);
+          details1.insert (wsk, (owned) v1);
+        }
+
+      Value? v2 = Value (typeof (string));
+      v2.set_string (this._persona_fullname_1);
+      details1.insert ("full-name", (owned) v2);
+
+      HashTable<string, Value?> details2 = new HashTable<string, Value?>
+          (str_hash, str_equal);
+
+      Value? v3;
+      if (this._linking_method == LinkingMethod.IM_ADDRESSES)
+        {
+          v3 = Value (typeof (MultiMap<string, string>));
+          var im_addrs2 = new HashMultiMap<string, string> ();
+          im_addrs2.set ("yahoo", this._im_address_2);
+          v3.set_object (im_addrs2);
+          details2.insert ("im-addresses", (owned) v3);
+        }
+      else if (this._linking_method == LinkingMethod.WEB_SERVICE_ADDRESSES)
+        {
+          v3 = Value (typeof (MultiMap<string, string>));
+          var wsa2 = new HashMultiMap<string, string> ();
+          wsa2.set ("lastfm", this._im_address_2);
+          v3.set_object (wsa2);
+          details2.insert (wsk, (owned) v3);
+        }
+
+      Value? v4 = Value (typeof (string));
+      v4.set_string (this._persona_fullname_2);
+      details2.insert ("full-name", (owned)v4);
+
+      try
+        {
+          yield this._aggregator.add_persona_from_details (null,
+              pstore, details1);
+
+          yield this._aggregator.add_persona_from_details (null,
+              pstore, details2);
+        }
+      catch (Folks.IndividualAggregatorError e)
+        {
+          GLib.warning ("[AddPersonaError] add_persona_from_details: %s\n",
+              e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (var i in added)
+        {
+          this._check_personas (i);
+        }
+
+      if (removed.size > 0)
+        this._removed_individuals += (int) removed.size;
+    }
+
+  /* As mentioned in _add_personas here we actually check
+   * for the following events
+   *
+   * - spot the 2 individuals corresponding to the 2 personas we've added
+   * - when we've spotted these 2, we pack them in a list and feed that to
+   *   IndividualAggregator#link_personas
+   * - this should fire a new individuals-changed event with a new individual
+   *   which should be the linked individual if it contains the linking
+   *   properties of the 2 linked personas.
+   */
+  private async void _check_personas (Individual i)
+    {
+      Persona first_persona = null;
+      foreach (var p in i.personas)
+        {
+          first_persona = p;
+          break;
+        }
+
+      if (i.full_name == this._persona_fullname_1 &&
+          this._persona_iid_1 == "")
+        {
+          this._persona_iid_1 = first_persona.iid;
+          this._personas.add (first_persona);
+          if (this._linking_method == LinkingMethod.LOCAL_IDS)
+            {
+              var contact_id1 = ((Edsf.Persona) first_persona).contact_id;
+              this._linking_props.set ("prop1", contact_id1);
+            }
+        }
+      else if (i.full_name == this._persona_fullname_2 &&
+          this._persona_iid_2 == "")
+        {
+          this._persona_iid_2 = first_persona.iid;
+          this._personas.add (first_persona);
+          if (this._linking_method == LinkingMethod.LOCAL_IDS)
+            {
+              var contact_id2 = ((Edsf.Persona) first_persona).contact_id;
+              this._linking_props.set ("prop2", contact_id2);
+            }
+        }
+      else if (i.personas.size > 1)
+        {
+          /* Lets check if it contains all the linking properties */
+          if (this._linking_method == LinkingMethod.IM_ADDRESSES)
+            {
+              foreach (var proto in i.im_addresses.get_keys ())
+                {
+                  var addrs = i.im_addresses.get (proto);
+                  foreach (var a in addrs)
+                    {
+                      if (a == this._linking_props.get ("prop1"))
+                        {
+                          this._linking_props.unset ("prop1");
+                        }
+                      else if (a == this._linking_props.get ("prop2"))
+                        {
+                          this._linking_props.unset ("prop2");
+                        }
+                    }
+                }
+            }
+          else if (this._linking_method == LinkingMethod.LOCAL_IDS)
+            {
+              foreach (var local_id in i.local_ids)
+                {
+                  if (local_id == this._linking_props.get ("prop1"))
+                    {
+                      this._linking_props.unset ("prop1");
+                    }
+                  else if (local_id == this._linking_props.get ("prop2"))
+                    {
+                      this._linking_props.unset ("prop2");
+                    }
+                }
+            }
+          else if (this._linking_method == LinkingMethod.WEB_SERVICE_ADDRESSES)
+            {
+              foreach (var service in i.web_service_addresses.get_keys ())
+                {
+                  var ws_ids = i.web_service_addresses.get (service);
+                  foreach (var ws_id in ws_ids)
+                    {
+                      if (ws_id == this._linking_props.get ("prop1"))
+                        {
+                          this._linking_props.unset ("prop1");
+                        }
+                      else if (ws_id == this._linking_props.get ("prop2"))
+                        {
+                          this._linking_props.unset ("prop2");
+                        }
+                    }
+                }
+            }
+
+          if (this._linking_props.size == 0)
+            {
+              this._main_loop.quit ();
+            }
+        }
+
+      /* We can try linking the personas only once we've got the
+       * 2 initially created personas. */
+      if (this._personas.size == 2 &&
+          this._linking_fired == false)
+        {
+          this._linking_fired = true;
+          try
+            {
+              yield this._aggregator.link_personas (this._personas);
+            }
+          catch (GLib.Error e)
+            {
+              GLib.warning ("link_personas: %s\n", e.message);
+            }
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new LinkPersonasTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/name-details.vala b/tests/eds/name-details.vala
new file mode 100644
index 0000000..a47e2f8
--- /dev/null
+++ b/tests/eds/name-details.vala
@@ -0,0 +1,220 @@
+/*
+ * 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 EdsTest;
+using Folks;
+using Gee;
+
+public class NameDetailsTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private int _names_count;
+  private Gee.HashMap<string, Value?> _c1;
+  private Gee.HashMap<string, Value?> _c2;
+
+  public NameDetailsTests ()
+    {
+      base ("NameDetails");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("name details interface", this.test_names);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  public void test_names ()
+    {
+      this._c1 = new Gee.HashMap<string, Value?> ();
+      this._c2 = new Gee.HashMap<string, Value?> ();
+      this._names_count = 0;
+      this._main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+
+      this._eds_backend.reset ();
+
+      /* FIXME: passing the EContactName would be better */
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      this._c1.set ("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("bernie");
+      this._c1.set ("nickname", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("Innocenti");
+      this._c1.set ("contact_name_family", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("Bernardo");
+      this._c1.set ("contact_name_given", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("H.");
+      this._c1.set ("contact_name_additional", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("Mr.");
+      this._c1.set ("contact_name_prefixes", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("(sysadmin FSF)");
+      this._c1.set ("contact_name_suffixes", (owned) v);
+      this._eds_backend.add_contact (this._c1);
+
+      v = Value (typeof (string));
+      v.set_string ("richard m. stallman");
+      this._c2.set ("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("Stallman");
+      this._c2.set ("contact_name_family", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("Richard M.");
+      this._c2.set ("contact_name_given", (owned) v);
+      this._eds_backend.add_contact (this._c2);
+
+      this._test_names_async ();
+
+      Timeout.add_seconds (5, () =>
+          {
+            this._main_loop.quit ();
+            return false;
+          });
+
+      this._main_loop.run ();
+
+      assert (this._names_count == 2);
+      assert (this._c1.size == 0);
+      assert (this._c2.size == 0);
+    }
+
+  private async void _test_names_async ()
+    {
+
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          string s;
+
+          assert (i.personas.size == 1);
+
+          var name = (Folks.NameDetails) i;
+
+          if (i.full_name == "bernie h. innocenti")
+            {
+              assert (name.structured_name.is_empty () == false);
+
+              s = this._c1.get ("full_name").get_string ();
+              assert (name.full_name == s);
+              this._c1.unset ("full_name");
+
+              s = this._c1.get ("nickname").get_string ();
+              assert (name.nickname == s);
+              this._c1.unset ("nickname");
+
+              s = this._c1.get ("contact_name_family").get_string ();
+              assert (name.structured_name.family_name == s);
+              this._c1.unset ("contact_name_family");
+
+              s = this._c1.get ("contact_name_given").get_string ();
+              assert (name.structured_name.given_name == s);
+              this._c1.unset ("contact_name_given");
+
+              s = this._c1.get ("contact_name_additional").get_string ();
+              assert (name.structured_name.additional_names == s);
+              this._c1.unset ("contact_name_additional");
+
+              s = this._c1.get ("contact_name_prefixes").get_string ();
+              assert (name.structured_name.prefixes == s);
+              this._c1.unset ("contact_name_prefixes");
+
+              s = this._c1.get ("contact_name_suffixes").get_string ();
+              assert (name.structured_name.suffixes == s);
+              this._c1.unset ("contact_name_suffixes");
+            }
+          else if (i.full_name == "richard m. stallman")
+            {
+              assert (name.structured_name.is_empty () == false);
+
+              s = this._c2.get ("full_name").get_string ();
+              assert (name.full_name == s);
+              this._c2.unset ("full_name");
+
+              s = this._c2.get ("contact_name_family").get_string ();
+              assert (name.structured_name.family_name == s);
+              this._c2.unset ("contact_name_family");
+
+              s = this._c2.get ("contact_name_given").get_string ();
+              assert (name.structured_name.given_name == s);
+              this._c2.unset ("contact_name_given");
+            }
+
+          this._names_count++;
+        }
+
+        assert (removed.size == 0);
+
+        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 NameDetailsTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/persona-store-tests.vala b/tests/eds/persona-store-tests.vala
new file mode 100644
index 0000000..3385158
--- /dev/null
+++ b/tests/eds/persona-store-tests.vala
@@ -0,0 +1,214 @@
+/*
+ * 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 EdsTest;
+using Folks;
+using Gee;
+
+public class PersonaStoreTests : Folks.TestCase
+{
+  private EdsTest.Backend eds_backend;
+  private HashSet<string> group_flags_received;
+
+  public PersonaStoreTests ()
+    {
+      base ("PersonaStoreTests");
+      this.eds_backend = new EdsTest.Backend ();
+      this.add_test ("persona store tests", this.test_persona_store);
+    }
+
+  public override void set_up ()
+    {
+      this.group_flags_received = new HashSet<string> (str_hash, str_equal);
+      this.eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_persona_store ()
+    {
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      Gee.HashMap<string, Value?> c2 = new Gee.HashMap<string, Value?> ();
+      var main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+
+      this.eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      c1.set ("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("bernie example org");
+      c1.set (Edsf.Persona.email_fields[0], (owned) v);
+      this.eds_backend.add_contact (c1);
+
+      v = Value (typeof (string));
+      v.set_string ("richard m. stallman");
+      c2.set ("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("rms example org");
+      c2.set (Edsf.Persona.email_fields[0], (owned) v);
+      this.eds_backend.add_contact (c2);
+
+      var backend_store = BackendStore.dup ();
+      backend_store.prepare.begin ((o, r) =>
+        {
+          backend_store.prepare.end (r);
+        });
+      backend_store.backend_available.connect ((b) =>
+          {
+            if (b.name == "eds")
+              {
+                b.persona_store_added.connect ((ps) =>
+                    {
+                      this.set_up_persona_store (ps);
+                    });
+
+                foreach (var store in b.persona_stores.values)
+                  {
+                    this.set_up_persona_store (store);
+                  }
+
+              }
+          });
+
+      backend_store.load_backends ();
+
+      Timeout.add_seconds (3, () =>
+        {
+          main_loop.quit ();
+          return false;
+        });
+
+      main_loop.run ();
+
+      this.eds_backend.tear_down ();
+    }
+
+  private void set_up_persona_store (Folks.PersonaStore store)
+    {
+      store.prepare.begin ((obj, result) =>
+        {
+          try
+            {
+              store.prepare.end (result);
+
+              if (store.can_add_personas != MaybeBool.UNSET)
+                can_add_personas_cb (store, null);
+              else
+                store.notify["can-add-personas"].connect (
+                    this.can_add_personas_cb);
+
+              if (store.can_remove_personas != MaybeBool.UNSET)
+                can_remove_personas_cb (store, null);
+              else
+                store.notify["can-remove-personas"].connect (
+                    this.can_remove_personas_cb);
+
+              if (store.can_alias_personas != MaybeBool.UNSET)
+                can_alias_personas_cb (store, null);
+              else
+                store.notify["can-alias-personas"].connect (
+                    this.can_alias_personas_cb);
+
+              if (store.can_group_personas != MaybeBool.UNSET)
+                can_group_personas_cb (store, null);
+              else
+                store.notify["can-group-personas"].connect (
+                    this.can_group_personas_cb);
+            }
+          catch (GLib.Error e)
+            {
+              warning ("Error preparing PersonaStore type: %s, id: %s: " +
+                "'%s'", store.type_id, store.id, e.message);
+            }
+        });
+    }
+
+  private void can_add_personas_cb (GLib.Object s, ParamSpec? p)
+    {
+      assert (s is Edsf.PersonaStore);
+      var store = (Edsf.PersonaStore) s;
+
+      if (store.can_add_personas != MaybeBool.UNSET)
+        {
+          assert (store.can_add_personas == MaybeBool.TRUE);
+
+          store.notify["can-add-personas"].disconnect (
+              this.can_add_personas_cb);
+        }
+    }
+
+  private void can_remove_personas_cb (GLib.Object s, ParamSpec? p)
+    {
+      assert (s is Edsf.PersonaStore);
+      var store = (Edsf.PersonaStore) s;
+
+      if (store.can_remove_personas != MaybeBool.UNSET)
+        {
+          assert (store.can_remove_personas == MaybeBool.TRUE);
+
+          store.notify["can-remove-personas"].disconnect (
+              this.can_remove_personas_cb);
+        }
+    }
+
+  private void can_alias_personas_cb (GLib.Object s, ParamSpec? p)
+    {
+      assert (s is Edsf.PersonaStore);
+      var store = (Edsf.PersonaStore) s;
+
+      if (store.can_alias_personas != MaybeBool.UNSET)
+        {
+          assert (store.can_alias_personas == MaybeBool.FALSE);
+
+          store.notify["can-alias-personas"].disconnect (
+              this.can_alias_personas_cb);
+        }
+    }
+
+  private void can_group_personas_cb (GLib.Object s, ParamSpec? p)
+    {
+      assert (s is Edsf.PersonaStore);
+      var store = (Edsf.PersonaStore) s;
+
+      if (store.can_group_personas != MaybeBool.UNSET)
+        {
+          assert (store.can_group_personas == MaybeBool.FALSE);
+
+          store.notify["can-group-personas"].disconnect (
+              this.can_group_personas_cb);
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new PersonaStoreTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/phone-details.vala b/tests/eds/phone-details.vala
new file mode 100644
index 0000000..dffcc13
--- /dev/null
+++ b/tests/eds/phone-details.vala
@@ -0,0 +1,214 @@
+/*
+ * 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 EdsTest;
+using Folks;
+using Gee;
+
+public class PhoneDetailsTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private int _phones_count;
+  private HashSet<string> _phone_types;
+  private Gee.HashMap<string, Value?> _c1;
+  private Gee.HashMap<string, Value?> _c2;
+
+  public PhoneDetailsTests ()
+    {
+      base ("PhoneDetails");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("phone details interface", this.test_phone_numbers);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  public void test_phone_numbers ()
+    {
+      this._phones_count = 0;
+      this._phone_types = new HashSet<string> (str_hash,
+          str_equal);
+      this._c1 = new Gee.HashMap<string, Value?> ();
+      this._c2 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+
+      this._eds_backend.reset();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      this._c1.set ("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("123");
+      this._c1.set ("car_phone", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("1234");
+      this._c1.set ("company_phone", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("12345");
+      this._c1.set ("home_phone", (owned) v);
+      this._eds_backend.add_contact (this._c1);
+
+      v = Value (typeof (string));
+      v.set_string ("richard m. stallman");
+      this._c2.set ("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("54321");
+      this._c2.set ("car_phone", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("4321");
+      this._c2.set ("company_phone", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("321");
+      this._c2.set ("home_phone", (owned) v);
+      this._eds_backend.add_contact (this._c2);
+
+      this._test_phone_numbers_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          return false;
+        });
+
+      this._main_loop.run ();
+
+      assert (this._phones_count == 6);
+      assert (this._phone_types.size == 3);
+      assert (this._c1.size == 0);
+      assert (this._c2.size == 0);
+
+      foreach (var pt in this._phone_types)
+        {
+          assert(pt in Edsf.Persona.phone_fields);
+        }
+   }
+
+  private async void _test_phone_numbers_async ()
+    {
+
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          unowned Gee.HashMap<string, Value?> contact = null;
+          assert (i.personas.size == 1);
+
+          if (i.full_name == "bernie h. innocenti")
+            {
+              contact = this._c1;
+            }
+          else if (i.full_name == "richard m. stallman")
+            {
+              contact = this._c2;
+            }
+
+          if (contact == null)
+            {
+              continue;
+            }
+
+          contact.unset ("full_name");
+          var phone_numbers = (Folks.PhoneDetails) i;
+          foreach (var p in phone_numbers.phone_numbers)
+            {
+              this._phones_count++;
+              foreach (var t in p.get_parameter_values ("type"))
+                {
+                  string? v = null;
+
+                  if (t == "CAR")
+                    {
+                      v = "car_phone";
+                    }
+                  else if (t == "HOME")
+                    {
+                      v = "home_phone";
+                    }
+                  else if (t == "X-EVOLUTION-COMPANY")
+                    {
+                      v = "company_phone";
+                    }
+
+                  if (v == null)
+                    {
+                      continue;
+                    }
+
+                  this._phone_types.add (v);
+                  assert (contact.get (v).get_string () == p.value);
+                  contact.unset (v);
+                }
+            }
+        }
+
+        if (this._phones_count == 6)
+          {
+            this._main_loop.quit ();
+          }
+
+        assert (removed.size == 0);
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new PhoneDetailsTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/postal-address-details.vala b/tests/eds/postal-address-details.vala
new file mode 100644
index 0000000..b322425
--- /dev/null
+++ b/tests/eds/postal-address-details.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 Folks;
+using Gee;
+
+public class PostalAddressDetailsTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private EdsTest.Backend _eds_backend;
+  private string _pobox = "12345";
+  private string _locality = "example locality";
+  private string _postalcode = "example postalcode";
+  private string _street = "example street";
+  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 PostalAddressDetailsTests ()
+    {
+      base ("PostalAddressDetailsTests");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("test postal address details interface",
+          this.test_postal_address_details_interface);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  public void test_postal_address_details_interface ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      this._fullname = "persona #1";
+      Value? v;
+
+      var types = new HashSet<string> ();
+      types.add (Edsf.Persona.address_fields[0]);
+      this._postal_address = new PostalAddress (
+           this._pobox,
+           this._extended,
+           this._street,
+           this._locality,
+           this._region,
+           this._postalcode,
+           this._country,
+           null, types, "eds_id");
+
+      v = Value (typeof (string));
+      v.set_string (this._fullname);
+      c1.set ("full_name", (owned) v);
+      var types1 = new HashSet<string> ();
+      types1.add (Edsf.Persona.address_fields[0]);
+      var postal_address_copy = new PostalAddress (
+           this._pobox,
+           this._extended,
+           this._street,
+           this._locality,
+           this._region,
+           this._postalcode,
+           this._country,
+           null, types1, "eds_id");
+      v = Value (typeof (PostalAddress));
+      v.set_object (postal_address_copy);
+      c1.set (Edsf.Persona.address_fields[0], (owned) v);
+
+      this._eds_backend.add_contact (c1);
+
+      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);
+    }
+
+  private async void _test_postal_address_details_interface_async ()
+    {
+
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (var i in added)
+        {
+          if (i.full_name == this._fullname)
+            {
+              foreach (var p in i.postal_addresses)
+              {
+                /* We copy the uid - 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.size == 0);
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new PostalAddressDetailsTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/remove-persona.vala b/tests/eds/remove-persona.vala
new file mode 100644
index 0000000..476e3af
--- /dev/null
+++ b/tests/eds/remove-persona.vala
@@ -0,0 +1,203 @@
+/*
+ * 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 Folks;
+using Gee;
+
+public class RemovePersonaTests : Folks.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private string _persona_fullname;
+  private bool _persona_removed;
+  private bool _individual_removed;
+  private string _individual_id;
+  private PersonaStore _pstore;
+  private string _persona_id;
+  private Individual _individual;
+  private bool _added_persona = false;
+
+  public RemovePersonaTests ()
+    {
+      base ("RemovePersonaTests");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("test removing personas from e-d-s ",
+          this.test_remove_persona);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  public void test_remove_persona ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      this._persona_fullname = "persona #1";
+
+      this._persona_removed = false;
+      this._individual_removed = false;
+
+      this._test_remove_persona_async ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          this._main_loop.quit ();
+          assert_not_reached ();
+        });
+
+      this._main_loop.run ();
+
+      assert (this._persona_removed == true);
+      assert (this._individual_removed == true);
+    }
+
+  private async void _test_remove_persona_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 ();
+
+          this._pstore = null;
+          foreach (var backend in store.enabled_backends.values)
+            {
+              this._pstore = backend.persona_stores.get ("local://test");
+              if (this._pstore != null)
+                break;
+            }
+          assert (this._pstore != null);
+
+          this._pstore.notify["is-prepared"].connect (this._notify_pstore_cb);
+          this._try_to_add ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _notify_pstore_cb (Object _pstore, ParamSpec ps)
+    {
+      this._try_to_add ();
+    }
+
+  private void _try_to_add ()
+    {
+      if (this._pstore.is_prepared &&
+          this._added_persona == false)
+        {
+          this._added_persona = true;
+          this._add_persona ();
+        }
+    }
+
+  private async void _add_persona ()
+    {
+      HashTable<string, Value?> details = new HashTable<string, Value?>
+          (str_hash, str_equal);
+      Value? v1 = Value (typeof (string));
+      v1.set_string (this._persona_fullname);
+      details.insert (Folks.PersonaStore.detail_key (PersonaDetail.FULL_NAME),
+          (owned) v1);
+
+      try
+        {
+          yield this._aggregator.add_persona_from_details
+              (null, this._pstore, details);
+        }
+      catch (Folks.IndividualAggregatorError e)
+        {
+          GLib.warning ("[RemovePersonaError] add_persona_from_details: %s\n",
+              e.message);
+        }
+    }
+
+  private void _individuals_changed_cb
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (var i in added)
+        {
+          if (i.full_name == this._persona_fullname)
+            {
+              this._individual_id = i.id;
+
+              /* Only examine the first persona */
+              foreach (var p in i.personas)
+                {
+                  this._persona_id = p.iid;
+                  break;
+                }
+
+              this._individual = i;
+              if (this._pstore.personas.has_key (this._persona_id) == true)
+                {
+                  this._pstore.personas_changed.connect (this._personas_cb);
+                  this._aggregator.remove_individual (this._individual);
+                }
+            }
+        }
+
+      foreach (var i in removed)
+        {
+          if (i.id == this._individual_id)
+            {
+              this._individual_removed = true;
+            }
+        }
+    }
+
+  private void _personas_cb ()
+    {
+      if (this._pstore.personas.has_key (this._persona_id) == false)
+        {
+          this._persona_removed = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new RemovePersonaTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/removing-contacts.vala b/tests/eds/removing-contacts.vala
new file mode 100644
index 0000000..50009f9
--- /dev/null
+++ b/tests/eds/removing-contacts.vala
@@ -0,0 +1,135 @@
+/*
+ * 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 EdsTest;
+using Folks;
+using Gee;
+
+public class RemovingContactsTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private bool _removed;
+  private bool _added;
+
+  public RemovingContactsTests ()
+    {
+      base ("IndividualRetrieval");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("removing contact", this.test_removal);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+   }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  void test_removal ()
+    {
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+
+      this._added = false;
+      this._removed = false;
+
+      this._eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      c1.set ("full_name", (owned) v);
+      this._eds_backend.add_contact (c1);
+
+      this._test_removal_async ();
+
+      Timeout.add_seconds (5, () => {
+            this._main_loop.quit ();
+            return false;
+          });
+
+      this._main_loop.run ();
+
+      assert (this._added == true);
+      assert (this._removed == true);
+    }
+
+  private async void _test_removal_async ()
+    {
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          var name = (Folks.NameDetails) i;
+          this._added = true;
+          assert (name.full_name == "bernie h. innocenti");
+          this._eds_backend.remove_contact (0);
+        }
+
+      foreach (Individual i in removed)
+        {
+          var name = (Folks.NameDetails) i;
+          assert (name.full_name == "bernie h. innocenti");
+          this._removed = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new RemovingContactsTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/set-avatar.vala b/tests/eds/set-avatar.vala
new file mode 100644
index 0000000..d12eaa7
--- /dev/null
+++ b/tests/eds/set-avatar.vala
@@ -0,0 +1,167 @@
+/*
+ * 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 EdsTest;
+using Folks;
+using Gee;
+
+public class SetAvatarTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private bool _found_before_update;
+  private bool _found_after_update;
+  private File _avatar;
+
+  public SetAvatarTests ()
+    {
+      base ("SetAvatar");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("setting avatar on e-d-s persona", this.test_set_avatar);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  void test_set_avatar ()
+    {
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      var avatar_path = Environment.get_variable ("AVATAR_FILE_PATH");
+      this._avatar = File.new_for_path (avatar_path);
+      Value? v;
+
+      this._found_before_update = false;
+      this._found_after_update = false;
+
+      this._eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      c1.set ("full_name", (owned) v);
+      this._eds_backend.add_contact (c1);
+
+      this._test_set_avatar_async ();
+
+      Timeout.add_seconds (5, () => {
+            this._main_loop.quit ();
+            assert_not_reached ();
+          });
+
+      this._main_loop.run ();
+
+      assert (this._found_before_update);
+      assert (this._found_after_update);
+    }
+
+  private async void _test_set_avatar_async ()
+    {
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          var name = (Folks.NameDetails) i;
+
+          if (name.full_name == "bernie h. innocenti")
+            {
+              i.notify["avatar"].connect (this._notify_avatar_cb);
+              this._found_before_update = true;
+
+              foreach (var p in i.personas)
+                {
+                  ((AvatarDetails) p).avatar = this._avatar;
+                }
+            }
+        }
+
+      assert (removed.size == 0);
+    }
+
+  private void _notify_avatar_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      var name = (Folks.NameDetails) i;
+      if (name.full_name == "bernie h. innocenti")
+        {
+          uint8[] content_a;
+          uint8[] content_b;
+
+          try
+            {
+              i.avatar.load_contents (null, out content_a);
+              this._avatar.load_contents (null, out content_b);
+
+             if (((string) content_a) == ((string) content_b))
+                {
+                  this._found_after_update = true;
+                  this._main_loop.quit ();
+                }
+            }
+          catch (GLib.Error e)
+            {
+              GLib.warning ("Couldn't compare avatars: %s\n", e.message);
+            }
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new SetAvatarTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/set-emails.vala b/tests/eds/set-emails.vala
new file mode 100644
index 0000000..204a9d4
--- /dev/null
+++ b/tests/eds/set-emails.vala
@@ -0,0 +1,154 @@
+/*
+ * 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 EdsTest;
+using Folks;
+using Gee;
+
+public class SetEmailsTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private bool _found_before_update;
+  private bool _found_after_update;
+
+  public SetEmailsTests ()
+    {
+      base ("SetEmails");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("setting emails on e-d-s persona", this.test_set_emails);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  void test_set_emails ()
+    {
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+
+      this._found_before_update = false;
+      this._found_after_update = false;
+
+      this._eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      c1.set ("full_name", (owned) v);
+      this._eds_backend.add_contact (c1);
+
+      this._test_set_emails_async ();
+
+      Timeout.add_seconds (5, () => {
+            this._main_loop.quit ();
+            assert_not_reached ();
+          });
+
+      this._main_loop.run ();
+
+      assert (this._found_before_update);
+      assert (this._found_after_update);
+    }
+
+  private async void _test_set_emails_async ()
+    {
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          var name = (Folks.NameDetails) i;
+
+          if (name.full_name == "bernie h. innocenti")
+            {
+              i.notify["email-addresses"].connect (this._notify_emails_cb);
+              this._found_before_update = true;
+
+              foreach (var p in i.personas)
+                {
+                  var emails = new HashSet<FieldDetails> ();
+                  var email_1 = new FieldDetails ("bernie example org");
+                  email_1.set_parameter ("type", "OTHER");
+                  emails.add (email_1);
+                  ((EmailDetails) p).email_addresses = emails;
+                }
+            }
+        }
+
+      assert (removed.size == 0);
+    }
+
+  private void _notify_emails_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      foreach (var e in i.email_addresses)
+        {
+          if (e.value == "bernie example org")
+            {
+              this._found_after_update = true;
+              this._main_loop.quit ();
+            }
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new SetEmailsTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/set-full-name.vala b/tests/eds/set-full-name.vala
new file mode 100644
index 0000000..8578bc7
--- /dev/null
+++ b/tests/eds/set-full-name.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 EdsTest;
+using Folks;
+using Gee;
+
+public class SetFullNameTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private bool _found_before_update;
+  private bool _found_after_update;
+
+  public SetFullNameTests ()
+    {
+      base ("SetFullName");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("setting full name on e-d-s persona",
+          this.test_set_full_name);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  void test_set_full_name ()
+    {
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+
+      this._found_before_update = false;
+      this._found_after_update = false;
+
+      this._eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      c1.set ("full_name", (owned) v);
+      this._eds_backend.add_contact (c1);
+
+      this._test_set_full_name_async ();
+
+      Timeout.add_seconds (5, () => {
+            this._main_loop.quit ();
+            assert_not_reached ();
+          });
+
+      this._main_loop.run ();
+
+      assert (this._found_before_update);
+      assert (this._found_after_update);
+    }
+
+  private async void _test_set_full_name_async ()
+    {
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          var name = (Folks.NameDetails) i;
+
+          if (name.full_name == "bernie h. innocenti")
+            {
+              i.notify["full-name"].connect (this._notify_full_name_cb);
+              this._found_before_update = true;
+
+              foreach (var p in i.personas)
+                {
+                  ((NameDetails) p).full_name = "bernie";
+                }
+            }
+        }
+
+      assert (removed.size == 0);
+    }
+
+  private void _notify_full_name_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      var name = (Folks.NameDetails) i;
+      if (name.full_name == "bernie")
+        {
+          this._found_after_update = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new SetFullNameTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/set-im-addresses.vala b/tests/eds/set-im-addresses.vala
new file mode 100644
index 0000000..4fc44f7
--- /dev/null
+++ b/tests/eds/set-im-addresses.vala
@@ -0,0 +1,156 @@
+/*
+ * 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 EdsTest;
+using Folks;
+using Gee;
+
+public class SetIMAddressesTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private bool _found_before_update;
+  private bool _found_after_update;
+
+  public SetIMAddressesTests ()
+    {
+      base ("SetIMAddresses");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("setting im addresses on e-d-s persona",
+          this.test_set_im_addresses);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  void test_set_im_addresses ()
+    {
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+
+      this._found_before_update = false;
+      this._found_after_update = false;
+
+      this._eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      c1.set ("full_name", (owned) v);
+      this._eds_backend.add_contact (c1);
+
+      this._test_set_im_addresses_async ();
+
+      Timeout.add_seconds (5, () => {
+            this._main_loop.quit ();
+            assert_not_reached ();
+          });
+
+      this._main_loop.run ();
+
+      assert (this._found_before_update);
+      assert (this._found_after_update);
+    }
+
+  private async void _test_set_im_addresses_async ()
+    {
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          var name = (Folks.NameDetails) i;
+
+          if (name.full_name == "bernie h. innocenti")
+            {
+              i.notify["im-addresses"].connect (this._notify_im_addresses_cb);
+              this._found_before_update = true;
+
+              foreach (var p in i.personas)
+                {
+                  var im_addrs = new HashMultiMap<string, string> ();
+                  im_addrs.set ("jabber", "bernie example org");
+                  ((ImDetails) p).im_addresses = im_addrs;
+                }
+            }
+        }
+
+      assert (removed.size == 0);
+    }
+
+  private void _notify_im_addresses_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      foreach (var proto in i.im_addresses.get_keys ())
+        {
+          foreach (var im in i.im_addresses.get (proto))
+            {
+              if (im == "bernie example org")
+                {
+                  this._found_after_update = true;
+                  this._main_loop.quit ();
+                }
+            }
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new SetIMAddressesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/set-notes.vala b/tests/eds/set-notes.vala
new file mode 100644
index 0000000..43cad5c
--- /dev/null
+++ b/tests/eds/set-notes.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 EdsTest;
+using Folks;
+using Gee;
+
+public class SetNotesTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private bool _found_before_update;
+  private bool _found_after_update;
+  private string _note = "This is a note.";
+
+  public SetNotesTests ()
+    {
+      base ("SetNotes");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("setting notes name on e-d-s persona",
+          this.test_set_notes);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  void test_set_notes ()
+    {
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+
+      this._found_before_update = false;
+      this._found_after_update = false;
+
+      this._eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      c1.set ("full_name", (owned) v);
+      this._eds_backend.add_contact (c1);
+
+      this._test_set_full_name_async ();
+
+      Timeout.add_seconds (5, () => {
+            this._main_loop.quit ();
+            assert_not_reached ();
+          });
+
+      this._main_loop.run ();
+
+      assert (this._found_before_update);
+      assert (this._found_after_update);
+    }
+
+  private async void _test_set_full_name_async ()
+    {
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          var name = (Folks.NameDetails) i;
+
+          if (name.full_name == "bernie h. innocenti")
+            {
+              i.notify["notes"].connect (this._notify_notes_cb);
+              this._found_before_update = true;
+
+              foreach (var p in i.personas)
+                {
+                  var notes = new HashSet<Note> ();
+                  var note = new Note(this._note);
+                  notes.add (note);
+                  ((NoteDetails) p).notes = notes;
+                }
+            }
+        }
+
+      assert (removed.size == 0);
+    }
+
+  private void _notify_notes_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      foreach (var note in i.notes)
+        {
+          if (note.content == this._note)
+            {
+              this._found_after_update = true;
+              this._main_loop.quit ();
+            }
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new SetNotesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/set-phones.vala b/tests/eds/set-phones.vala
new file mode 100644
index 0000000..df4aa6f
--- /dev/null
+++ b/tests/eds/set-phones.vala
@@ -0,0 +1,154 @@
+/*
+ * 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 EdsTest;
+using Folks;
+using Gee;
+
+public class SetPhonesTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private bool _found_before_update;
+  private bool _found_after_update;
+
+  public SetPhonesTests ()
+    {
+      base ("SetPhones");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("setting phones on e-d-s persona", this.test_set_phones);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  void test_set_phones ()
+    {
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+
+      this._found_before_update = false;
+      this._found_after_update = false;
+
+      this._eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      c1.set ("full_name", (owned) v);
+      this._eds_backend.add_contact (c1);
+
+      this._test_set_phones_async ();
+
+      Timeout.add_seconds (5, () => {
+            this._main_loop.quit ();
+            assert_not_reached ();
+          });
+
+      this._main_loop.run ();
+
+      assert (this._found_before_update);
+      assert (this._found_after_update);
+    }
+
+  private async void _test_set_phones_async ()
+    {
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          var name = (Folks.NameDetails) i;
+
+          if (name.full_name == "bernie h. innocenti")
+            {
+              i.notify["phone-numbers"].connect (this._notify_phones_cb);
+              this._found_before_update = true;
+
+              foreach (var p in i.personas)
+                {
+                  var phones = new HashSet<FieldDetails> ();
+                  var phone_1 = new FieldDetails ("1234");
+                  phone_1.set_parameter ("type", "HOME");
+                  phones.add (phone_1);
+                  ((PhoneDetails) p).phone_numbers = phones;
+                }
+            }
+        }
+
+      assert (removed.size == 0);
+    }
+
+  private void _notify_phones_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      foreach (var p in i.phone_numbers)
+        {
+          if (p.value == "1234")
+            {
+              this._found_after_update = true;
+              this._main_loop.quit ();
+            }
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new SetPhonesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/set-postal-addresses.vala b/tests/eds/set-postal-addresses.vala
new file mode 100644
index 0000000..dc9356c
--- /dev/null
+++ b/tests/eds/set-postal-addresses.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 EdsTest;
+using Folks;
+using Gee;
+
+public class SetPostalAddressesTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private bool _found_before_update;
+  private bool _found_after_update;
+  private PostalAddress _postal;
+
+  public SetPostalAddressesTests ()
+    {
+      base ("SetPostalAddresses");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("setting postal addresss on e-d-s persona",
+          this.test_set_postal_addresses);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  void test_set_postal_addresses ()
+    {
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+      var types = new HashSet<string> ();
+      types.add ("address_other");
+      this._postal = new PostalAddress ("123", "extension", "street",
+          "locality", "region", "postal code", "country", "",
+          types, "123");
+
+      this._found_before_update = false;
+      this._found_after_update = false;
+
+      this._eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      c1.set ("full_name", (owned) v);
+      this._eds_backend.add_contact (c1);
+
+      this._test_set_postal_addresses_async ();
+
+      Timeout.add_seconds (5, () => {
+            this._main_loop.quit ();
+            assert_not_reached ();
+          });
+
+      this._main_loop.run ();
+
+      assert (this._found_before_update);
+      assert (this._found_after_update);
+    }
+
+  private async void _test_set_postal_addresses_async ()
+    {
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          var name = (Folks.NameDetails) i;
+
+          if (name.full_name == "bernie h. innocenti")
+            {
+              i.notify["postal-addresses"].connect (
+                  this._notify_postal_addresses_cb);
+              this._found_before_update = true;
+
+              foreach (var p in i.personas)
+                {
+                  var addresses = new HashSet<PostalAddress> ();
+                  var types = new HashSet<string> ();
+                  types.add ("address_other");
+                  var addr1 = new PostalAddress ("123", "extension", "street",
+                      "locality", "region", "postal code", "country", "format",
+                      types, "123");
+                  addresses.add (addr1);
+                  ((PostalAddressDetails) p).postal_addresses = addresses;
+                }
+            }
+        }
+
+      assert (removed.size == 0);
+    }
+
+  private void _notify_postal_addresses_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      foreach (var p in i.postal_addresses)
+        {
+          p.uid = this._postal.uid;
+          if (p.equal (this._postal))
+            {
+              this._found_after_update = true;
+              this._main_loop.quit ();
+            }
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new SetPostalAddressesTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/set-structured-name.vala b/tests/eds/set-structured-name.vala
new file mode 100644
index 0000000..31f7331
--- /dev/null
+++ b/tests/eds/set-structured-name.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 EdsTest;
+using Folks;
+using Gee;
+
+public class SetStructuredNameTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private bool _found_before_update;
+  private bool _found_after_update;
+  private StructuredName _sname;
+
+  public SetStructuredNameTests ()
+    {
+      base ("SetStructuredName");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("setting structured name on e-d-s persona",
+          this.test_set_structured_name);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  void test_set_structured_name ()
+    {
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      this._sname = new StructuredName.simple ("Innocenti", "Bernie");
+      Value? v;
+
+      this._found_before_update = false;
+      this._found_after_update = false;
+
+      this._eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      c1.set ("full_name", (owned) v);
+      this._eds_backend.add_contact (c1);
+
+      this._test_set_structured_name_async ();
+
+      Timeout.add_seconds (5, () => {
+            this._main_loop.quit ();
+            assert_not_reached ();
+          });
+
+      this._main_loop.run ();
+
+      assert (this._found_before_update);
+      assert (this._found_after_update);
+    }
+
+  private async void _test_set_structured_name_async ()
+    {
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          var name = (Folks.NameDetails) i;
+
+          if (name.full_name == "bernie h. innocenti")
+            {
+              i.notify["structured-name"].connect (
+                  this._notify_structured_name_cb);
+              this._found_before_update = true;
+
+              foreach (var p in i.personas)
+                {
+                  ((NameDetails) p).structured_name = this._sname;
+                }
+            }
+        }
+
+      assert (removed.size == 0);
+    }
+
+  private void _notify_structured_name_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      if (i.structured_name.equal (this._sname))
+        {
+          this._found_after_update = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new SetStructuredNameTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/eds/updating-contacts.vala b/tests/eds/updating-contacts.vala
new file mode 100644
index 0000000..b03c5c4
--- /dev/null
+++ b/tests/eds/updating-contacts.vala
@@ -0,0 +1,156 @@
+/*
+ * 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 EdsTest;
+using Folks;
+using Gee;
+
+public class UpdatingContactsTests : Folks.TestCase
+{
+  private EdsTest.Backend _eds_backend;
+  private IndividualAggregator _aggregator;
+  private GLib.MainLoop _main_loop;
+  private bool _found_before_update;
+  private bool _found_after_update;
+
+  public UpdatingContactsTests ()
+    {
+      base ("UpdatingContacts");
+
+      this._eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("updating contact", this.test_updates);
+    }
+
+  public override void set_up ()
+    {
+      this._eds_backend.set_up ();
+    }
+
+  public override void tear_down ()
+    {
+      this._eds_backend.tear_down ();
+    }
+
+  void test_updates ()
+    {
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      this._main_loop = new GLib.MainLoop (null, false);
+      Value? v;
+
+      this._found_before_update = false;
+      this._found_after_update = false;
+
+      this._eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string ("bernie h. innocenti");
+      c1.set ("full_name", (owned) v);
+      this._eds_backend.add_contact (c1);
+
+      this._test_updates_async ();
+
+      Timeout.add_seconds (5, () => {
+            this._main_loop.quit ();
+            assert_not_reached ();
+          });
+
+      this._main_loop.run ();
+
+      assert (this._found_before_update);
+      assert (this._found_after_update);
+    }
+
+  private async void _test_updates_async ()
+    {
+      yield this._eds_backend.commit_contacts_to_addressbook ();
+
+      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
+      (Set<Individual> added,
+       Set<Individual> removed,
+       string? message,
+       Persona? actor,
+       GroupDetails.ChangeReason reason)
+    {
+      foreach (Individual i in added)
+        {
+          var name = (Folks.NameDetails) i;
+
+          if (name.full_name == "bernie h. innocenti")
+            {
+              i.notify["full-name"].connect (this._notify_full_name_cb);
+              this._found_before_update = true;
+              this._update_contact ();
+            }
+        }
+
+      assert (removed.size == 0);
+    }
+
+  private async void _update_contact ()
+    {
+      Gee.HashMap<string, Value?> updated_data =
+        new Gee.HashMap<string, Value?> ();
+      Value? v;
+
+      v = Value (typeof (string));
+      v.set_string ("bernie innocenti");
+      updated_data.set("full_name", (owned) v);
+      yield this._eds_backend.update_contact (0, updated_data);
+    }
+
+  private void _notify_full_name_cb (Object individual_obj, ParamSpec ps)
+    {
+      Folks.Individual i = (Folks.Individual) individual_obj;
+      var name = (Folks.NameDetails) i;
+      if (name.full_name == "bernie innocenti")
+        {
+          this._found_after_update = true;
+          this._main_loop.quit ();
+        }
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new UpdatingContactsTests ().get_suite ());
+
+  Test.run ();
+
+  return 0;
+}
diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am
index 1e1c595..5f7a4dd 100644
--- a/tests/lib/Makefile.am
+++ b/tests/lib/Makefile.am
@@ -11,11 +11,16 @@ if ENABLE_TRACKER
 SUBDIRS += tracker
 endif
 
+if ENABLE_EDS
+SUBDIRS += eds
+endif
+
 DIST_SUBDIRS = \
 	key-file \
 	telepathy \
 	libsocialweb \
 	tracker \
+	eds \
 	$(NULL)
 
 noinst_LTLIBRARIES = libfolks-test.la
diff --git a/tests/lib/eds/Makefile.am b/tests/lib/eds/Makefile.am
new file mode 100644
index 0000000..1c8346c
--- /dev/null
+++ b/tests/lib/eds/Makefile.am
@@ -0,0 +1,56 @@
+VALAFLAGS += \
+	--library=eds-test \
+	--header=eds-test.h \
+	--vapidir=$(top_srcdir)/folks \
+	--vapidir=$(top_srcdir)/backends/eds/lib \
+	--vapidir=$(top_srcdir)/backends/eds/vapi \
+	--vapidir=. \
+	--pkg gobject-2.0 \
+	--pkg gio-2.0 \
+	--pkg gee-1.0 \
+	--pkg folks \
+	--pkg libebook-1.2 \
+	--pkg libedataserver-1.2 \
+	--pkg libxml-2.0 \
+	--pkg folks-eds \
+	$(NULL)
+
+noinst_LTLIBRARIES = libeds-test.la
+
+libeds_test_la_SOURCES = \
+	backend.vala \
+	$(NULL)
+
+libeds_test_la_CFLAGS = \
+	$(GLIB_CFLAGS) \
+	$(GEE_CFLAGS) \
+	$(EBOOK_CFLAGS) \
+	$(EDATASERVER_CFLAGS) \
+	-I$(top_srcdir)/backends/eds/lib \
+	$(NULL)
+
+libeds_test_la_LIBADD = \
+	$(top_builddir)/folks/libfolks.la \
+	$(top_builddir)/backends/eds/libfolks-backend-eds.la \
+	$(EBOOK_LIBS) \
+	$(EDATASERVER_LIBS) \
+	$(GLIB_LIBS) \
+	$(GEE_LIBS) \
+	$(NULL)
+
+MAINTAINERCLEANFILES = \
+	eds-test.vapi \
+	eds-test.h \
+	$(NULL)
+
+EXTRA_DIST = \
+	eds-test.vapi \
+	eds-test.h \
+	$(NULL)
+
+GITIGNOREFILES = \
+	$(libeds_test_la_SOURCES:.vala=.c) \
+	libeds_test_la_vala.stamp \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/tests/lib/eds/backend.vala b/tests/lib/eds/backend.vala
new file mode 100644
index 0000000..bf32db0
--- /dev/null
+++ b/tests/lib/eds/backend.vala
@@ -0,0 +1,291 @@
+/*
+ * 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 E;
+using Folks;
+using Random;
+
+errordomain EdsTest.BackendSetupError
+{
+  FETCH_SOURCE_GROUP_FAILED,
+  OPENING_FAILED,
+  ADD_CONTACT_FAILED,
+  ADD_TO_SOURCE_GROUP_FAILED,
+}
+
+public class EdsTest.Backend
+{
+  private string _addressbook_name;
+  private E.BookClient _addressbook;
+  private GLib.List<string> _e_contacts;
+  private GLib.List<Gee.HashMap<string, Value?>> _contacts;
+  E.SourceGroup _source_group;
+  E.Source _source;
+
+  public Backend ()
+    {
+      this._contacts = new GLib.List<Gee.HashMap<string, Value?>> ();
+      this._e_contacts = new GLib.List<string> ();
+   }
+
+  public void add_contact (owned Gee.HashMap<string, Value?> c)
+    {
+      this._contacts.prepend (c);
+    }
+
+  public async void update_contact (int contact_pos,
+      owned Gee.HashMap<string, Value?> updated_data)
+    {
+      var uid = this._e_contacts.nth_data (contact_pos);
+      E.Contact contact;
+      try
+        {
+          yield this._addressbook.get_contact (uid, out contact);
+          this._set_contact_fields (contact, updated_data);
+          yield this._addressbook.modify_contact (contact);
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Couldn't update contact\n");
+        }
+    }
+
+  public async void remove_contact (int contact_pos)
+    {
+      var uid = this._e_contacts.nth_data (contact_pos);
+      E.Contact contact;
+      try
+        {
+          yield this._addressbook.get_contact (uid, out contact);
+          yield this._addressbook.remove_contact (contact);
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Couldn't remove contact\n");
+        }
+    }
+
+  public void reset ()
+    {
+      this._contacts = new GLib.List<Gee.HashMap<string, Value?>> ();
+      this._e_contacts = new GLib.List<string> ();
+    }
+
+  /* Create a temporary addressbook */
+  public void set_up ()
+    {
+      try
+        {
+          this._prepare_source ();
+          this._addressbook = new BookClient (this._source);
+          this._addressbook.open_sync (false, null);
+          this._addressbook_name =
+            this._addressbook.get_source ().peek_name ();
+          Environment.set_variable ("FOLKS_BACKEND_EDS_USE_ADDRESSBOOKS",
+                                    this._addressbook_name, true);
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Unable to create test data: %s\n", e.message);
+        }
+    }
+
+  private void _prepare_source ()
+    {
+      var base_uri = "local:";
+      this._source_group = new E.SourceGroup ("Test", base_uri);
+
+      this._source = new E.Source ("Test", "local://test");
+      if (this._source_group.add_source (this._source, -1))
+        {
+          try
+            {
+              SourceList sl;
+              BookClient.get_sources (out sl);
+              sl.add_group (this._source_group, 0);
+              sl.sync ();
+            }
+          catch (GLib.Error e)
+            {
+              // XXX
+            }
+        }
+    }
+
+  public async void commit_contacts_to_addressbook ()
+    {
+      this._contacts.reverse ();
+      foreach (var c in this._contacts)
+        {
+          E.Contact contact = new E.Contact ();
+
+          this._set_contact_fields (contact, c);
+
+          try
+            {
+              string added_uid;
+              yield this._addressbook.add_contact (contact,
+                  out added_uid);
+              this._e_contacts.prepend ((owned) added_uid);
+            }
+          catch (GLib.Error e)
+            {
+              GLib.warning ("Couldn't add contact: %s\n",
+                  e.message);
+            }
+        }
+        this._e_contacts.reverse ();
+    }
+
+  private void _set_contact_fields (E.Contact contact,
+                                    Gee.HashMap<string, Value?> c)
+    {
+      bool added_contact_name = false;
+      E.ContactName contact_name = new E.ContactName ();
+      string contact_field_name = "contact_name";
+      int min_len = contact_field_name.length;
+
+      foreach (var k in c.keys)
+        {
+          if (k.length > min_len && k.slice(0, min_len) == contact_field_name)
+            {
+              var v = c.get (k).get_string ();
+              if (k.index_of ("family") >= 0)
+                {
+                  contact_name.family = v;
+                }
+              else if (k.index_of ("given") >= 0)
+                {
+                  contact_name.given = v;
+                }
+              else if (k.index_of ("additional") >= 0)
+                {
+                  contact_name.additional = v;
+                }
+              else if (k.index_of ("prefixes") >= 0)
+                {
+                  contact_name.prefixes = v;
+                }
+              else if (k.index_of ("suffixes") >= 0)
+                {
+                  contact_name.suffixes = v;
+                }
+
+              added_contact_name = true;
+            }
+          else if (k == "avatar")
+            {
+              var v = c.get (k).get_string ();
+              uint8[] photo_content;
+              var file = File.new_for_path (v);
+
+              try
+                {
+                  file.load_contents (null, out photo_content);
+
+                  var cp = new ContactPhoto ();
+                  cp.type = ContactPhotoType.INLINED;
+                  cp.set_inlined (photo_content);
+
+                  contact.set (E.Contact.field_id ("photo"), cp);
+                }
+              catch (GLib.Error e)
+                {
+                  GLib.warning ("\n\nCan't load avatar %s: %s\n\n", v,
+                      e.message);
+                }
+            }
+          else if (k == "im_addresses")
+            {
+              var v = c.get (k).get_string ();
+              var addresses = this._parse_addrs (v);
+              foreach (var addr in addresses.keys)
+                {
+                  var proto = addresses.get (addr);
+                  contact.set (E.Contact.field_id (proto), addr);
+                }
+            }
+          else if (k == Edsf.Persona.address_fields[0])
+            {
+              var pa = (PostalAddress) c.get (k).get_object ();
+              var address = new E.ContactAddress ();
+              address.po = pa.po_box;
+              address.ext = pa.extension;
+              address.street = pa.street;
+              address.locality = pa.locality;
+              address.region = pa.region;
+              address.code = pa.postal_code;
+              address.country = pa.country;
+              address.address_format = pa.address_format;
+
+              contact.set (E.Contact.field_id (k), address);
+           }
+          else
+            {
+              var v = c.get (k).get_string ();
+              contact.set (E.Contact.field_id (k), v);
+            }
+        }
+      if (added_contact_name)
+        {
+          contact.set (E.Contact.field_id ("name"), contact_name);
+        }
+    }
+
+  public void tear_down ()
+    {
+      Environment.set_variable ("FOLKS_BACKEND_EDS_USE_ADDRESSBOOKS",
+          "", true);
+
+      try
+        {
+          var ret = this._addressbook.remove_sync (null);
+          if (ret == false)
+            {
+              GLib.warning ("remove() addressbook returned false on %s\n",
+                                  this._addressbook_name);
+            }
+
+          this._addressbook = null;
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Unable to remove addressbook %s because: %s\n",
+                              this._addressbook_name, e.message);
+        }
+    }
+
+  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;
+    }
+}
\ No newline at end of file
diff --git a/tests/tools/eds.sh b/tests/tools/eds.sh
new file mode 100644
index 0000000..852ba2b
--- /dev/null
+++ b/tests/tools/eds.sh
@@ -0,0 +1,25 @@
+#
+# Helper functions to start your own e-d-s 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.
+
+eds_tmpdir=$(mktemp -d)
+
+eds_init_settings () {
+    export XDG_DATA_HOME=$eds_tmpdir/.local
+    export XDG_CACHE_HOME=$eds_tmpdir/.cache
+    export XDG_CONFIG_HOME=$eds_tmpdir/.config
+}
+
+# This should be called on INT TERM and EXIT
+eds_stop () {
+    rm -rf $eds_tmpdir
+    rm -rf $eds_tmpdir
+}
+
diff --git a/tests/tools/with-session-bus-eds.sh b/tests/tools/with-session-bus-eds.sh
new file mode 100755
index 0000000..b5cab54
--- /dev/null
+++ b/tests/tools/with-session-bus-eds.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+# with-session-bus-eds.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"/eds.sh"
+
+dbus_parse_args $@
+while test "z$1" != "z--"; do
+    shift
+done
+shift
+if test "z$1" = "z"; then dbus_usage; fi
+
+cleanup ()
+{
+    eds_stop
+    dbus_stop
+}
+
+trap cleanup INT HUP TERM
+
+eds_init_settings
+dbus_init 0
+
+dbus_start
+
+e=0
+"$@" || e=$?
+
+trap - INT HUP TERM
+cleanup
+
+exit $e



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