diff --git a/backends/Makefile.am b/backends/Makefile.am
index cd023e8..1cdcb6e 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -1,6 +1,7 @@
 SUBDIRS = \
 	telepathy \
 	key-file \
+	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..5ce2e9b
--- /dev/null
+++ b/backends/eds/Makefile.am
@@ -0,0 +1,69 @@
+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=vapi \
+	--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 \
+	posix \
+	$(NULL)
+
+libfolks_backend_eds_la_CFLAGS = \
+	$(GIO_CFLAGS) \
+	$(GLIB_CFLAGS) \
+	$(GEE_CFLAGS) \
+	$(EBOOK_CFLAGS) \
+	$(EDATASERVER_CFLAGS) \
+	$(LIBXML_CFLAGS) \
+	$(NULL)
+
+libfolks_backend_eds_la_LIBADD = \
+	$(GIO_LIBS) \
+	$(GLIB_LIBS) \
+	$(GEE_LIBS) \
+	$(top_builddir)/folks/libfolks.la \
+	$(EBOOK_LIBS) \
+	$(EDATASERVER_LIBS) \
+	$(LIBXML_LIBS) \
+	lib/libfolks-eds.la \
+	$(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..3069d49
--- /dev/null
+++ b/backends/eds/eds-backend-factory.vala
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2009 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>.
+ * Copyright (C) 2009 Nokia Corporation.
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
+ *          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.
+ */
+public void module_init (BackendStore backend_store)
+{
+  _backend_factory = new BackendFactory (backend_store);
+}
+
+/**
+ * The eds backend module exit point.
+ */
+public void module_finalize (BackendStore backend_store)
+{
+  _backend_factory = null;
+}
+
+/**
+ * A backend factory to create a single {@link Backend}.
+ */
+public class Folks.Backends.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..76e5e89
--- /dev/null
+++ b/backends/eds/eds-backend.vala
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Travis Reitter <travis.reitter@collabora.co.uk>
+ *       Marco Barisione <marco.barisione@collabora.co.uk>
+ *       Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
+ */
+
+using GLib;
+using E;
+using Posix;
+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 HashTable<string, PersonaStore> _persona_stores;
+  private E.SourceList _ab_sources;
+
+  /**
+   * {@inheritDoc}
+   */
+  public override string name { get { return BACKEND_NAME; } }
+
+  /**
+   * {@inheritDoc}
+   */
+  public override HashTable<string, PersonaStore> persona_stores
+    {
+      get { return this._persona_stores; }
+    }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Backend ()
+    {
+      this._persona_stores = new HashTable<string, PersonaStore>
+          (str_hash, str_equal);
+    }
+
+  /**
+   * Whether this Backend has been prepared.
+   *
+   * See {@link Folks.Backend.is_prepared}.
+   */
+  public override bool is_prepared
+    {
+      get { return this._is_prepared; }
+    }
+
+  /**
+   * {@inheritDoc}
+   *
+   */
+  public override async void prepare () throws GLib.Error
+    {
+      lock (this._is_prepared)
+        {
+          if (!this._is_prepared)
+            {
+              string [] use_addressbooks = this._get_addressbooks_from_env ();
+              this._create_avatars_cache_dir ();
+
+              E.Book.get_addressbooks (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)
+                            {
+                              add_addressbook (s);
+                            }
+                        }
+                      else
+                        {
+                          add_addressbook (s);
+                        }
+                    }
+                }
+
+              this._is_prepared = true;
+              this.notify_property ("is-prepared");
+            }
+        }
+    }
+
+  /**
+   * {@inheritDoc}
+   */
+  public override async void unprepare () throws GLib.Error
+    {
+      this._persona_stores.foreach ((k, v) =>
+        {
+          this.persona_store_removed ((PersonaStore) v);
+        });
+
+      this._persona_stores.remove_all ();
+      this.notify_property ("persona-stores");
+
+      this._is_prepared = false;
+      this.notify_property ("is-prepared");
+    }
+
+  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.lookup (relative_uri) != null)
+        return;
+
+      var store = new Edsf.PersonaStore (s);
+      this._persona_stores.insert (store.id, store);
+      store.removed.connect (this.store_removed_cb);
+      this.notify_property ("persona-stores");
+      this.persona_store_added (store);
+    }
+
+  private void store_removed_cb (Folks.PersonaStore store)
+    {
+      this.persona_store_removed (store);
+      this.persona_stores.remove (store.id);
+    }
+
+  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..7025f69
--- /dev/null
+++ b/backends/eds/lib/Makefile.am
@@ -0,0 +1,126 @@
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/folks \
+	-include $(CONFIG_HEADER) \
+	-DPACKAGE_DATADIR=\"$(pkgdatadir)\" \
+	-DBACKEND_NAME=\"$(BACKEND_NAME)\" \
+	-DG_LOG_DOMAIN=\"$(BACKEND_NAME)\" \
+	$(NULL)
+
+VAPIGENFLAGS += \
+	--vapidir=. \
+	--vapidir=$(top_srcdir)/folks
+
+folks_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)
+
+libfolks_eds_la_VALAFLAGS = \
+	--vapidir=. \
+	--vapidir=$(top_srcdir)/backends/eds/vapi \
+	--vapidir=$(top_srcdir)/folks \
+	--pkg folks \
+	--pkg gobject-2.0 \
+	--pkg gio-2.0 \
+	--pkg gee-1.0 \
+	--pkg libebook-1.2 \
+	--pkg libedataserver-1.2 \
+	--pkg libxml-2.0 \
+	--pkg posix \
+	--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 \
+	posix \
+	$(NULL)
+
+libfolks_eds_la_CFLAGS = \
+	$(GIO_CFLAGS) \
+	$(GLIB_CFLAGS) \
+	$(GEE_CFLAGS) \
+	$(EBOOK_CFLAGS) \
+	$(EDATASERVER_CFLAGS) \
+	$(LIBXML_CFLAGS) \
+	$(NULL)
+
+libfolks_eds_la_LIBADD = \
+	$(GIO_LIBS) \
+	$(GLIB_LIBS) \
+	$(GEE_LIBS) \
+	$(TP_GLIB_LIBS) \
+	$(top_builddir)/folks/libfolks.la \
+	$(EBOOK_LIBS) \
+	$(EDATASERVER_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 \
+	libtp_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..ce29e54
--- /dev/null
+++ b/backends/eds/lib/edsf-persona-store.vala
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       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 GLib;
+using Folks;
+
+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 HashTable<string, Persona> _personas;
+  private bool _is_prepared = false;
+  private E.Book _addressbook;
+  private E.BookView _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}.
+   */
+  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.3.1
+   */
+  public override MaybeBool can_add_personas
+    {
+      get { return MaybeBool.FALSE; }
+    }
+
+  /**
+   * Whether this PersonaStore can set the alias of {@link Folks.Persona}s.
+   *
+   * See {@link Folks.PersonaStore.can_alias_personas}.
+   *
+   * @since 0.3.1
+   */
+  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.3.1
+   */
+  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.3.1
+   */
+  public override MaybeBool can_remove_personas
+    {
+      get { return MaybeBool.FALSE; }
+    }
+
+  /**
+   * Whether this PersonaStore has been prepared.
+   *
+   * See {@link Folks.PersonaStore.is_prepared}.
+   *
+   * @since 0.3.0
+   */
+  public override bool is_prepared
+    {
+      get { return this._is_prepared; }
+    }
+
+  /**
+   * The {@link Persona}s exposed by this PersonaStore.
+   *
+   * See {@link Folks.PersonaStore.personas}.
+   */
+  public override HashTable<string, Persona> personas
+    {
+      get { return this._personas; }
+    }
+
+  /**
+   * Create a new PersonaStore.
+   *
+   * Create a new persona store to store the {@link Persona}s for the contacts
+   */
+  public PersonaStore (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 HashTable<string, Persona> (str_hash, str_equal);
+      this._query_str = "(contains \"x-evolution-any-field\" \"\")";
+    }
+
+  /**
+   * Add a new {@link Persona} to the PersonaStore.
+   *
+   * See {@link Folks.PersonaStore.add_persona_from_details}.
+   */
+  public override async Folks.Persona? add_persona_from_details (
+      HashTable<string, Value?> details) throws Folks.PersonaStoreError
+    {
+      throw new PersonaStoreError.READ_ONLY (
+          "Personas cannot be added to this store.");
+    }
+
+  /**
+   * Remove a {@link Persona} from the PersonaStore.
+   *
+   * See {@link Folks.PersonaStore.remove_persona}.
+   */
+  public override async void remove_persona (Folks.Persona persona)
+      throws Folks.PersonaStoreError
+    {
+      throw new PersonaStoreError.READ_ONLY (
+          "Personas cannot be removed from this store.");
+    }
+
+  /**
+   * Prepare the PersonaStore for use.
+   *
+   * TODO: we should throw different errors dependening on what went wrong
+   *       when we were trying to setup the PersonaStore.
+   *
+   * See {@link Folks.PersonaStore.prepare}.
+   */
+  public override async void prepare ()
+    {
+      lock (this._is_prepared)
+        {
+          if (!this._is_prepared)
+            {
+              try
+                {
+                  this._addressbook = new E.Book (this._source);
+
+                  this._addressbook.open (true);
+
+                  if (this._addressbook.is_opened ())
+                    {
+                      E.BookQuery q = E.BookQuery.from_string (this._query_str);
+                      if (this._addressbook.get_book_view
+                              (q, null, 0, out this._ebookview))
+                        {
+                          this._ebookview.contacts_added.connect
+                              (this._contacts_added_cb);
+                          this._ebookview.contacts_removed.connect
+                              (this._contacts_removed_cb);
+                          this._ebookview.contacts_changed.connect
+                              (this._contacts_changed_cb);
+
+                          this._ebookview.start ();
+
+                          if (this._addressbook.is_writable ())
+                            {
+                              this.trust_level = PersonaStoreTrust.FULL;
+                            }
+                          else
+                            {
+                              this.trust_level = PersonaStoreTrust.PARTIAL;
+                            }
+
+                          this._is_prepared = true;
+                          this.notify_property ("is-prepared");
+                        }
+                    }
+                }
+              catch (GLib.Error e1)
+                {
+                  throw new PersonaStoreError.INVALID_ARGUMENT (
+                      "Couldn't open addressbook with URI: %s\n", e1.message);
+                }
+            }
+        }
+    }
+
+  private void _contacts_added_cb (GLib.List<E.Contact> contacts)
+    {
+      var added_personas = new Queue<Persona> ();
+
+      foreach (E.Contact c in (GLib.List<E.Contact>) contacts)
+        {
+          var persona = new Persona (this, c);
+           _personas.insert (persona.iid, persona);
+           added_personas.push_tail (persona);
+        }
+
+      if (added_personas.length > 0)
+        this.personas_changed (added_personas.head, null, null, null, 0);
+    }
+
+  private void _contacts_changed_cb (GLib.List<E.Contact> contacts)
+    {
+      foreach (E.Contact c in (GLib.List<E.Contact>) contacts)
+        {
+          string persona_id = this.id + ":" + (string) c.get
+              (E.Contact.field_id ("id"));
+          var persona = _personas.lookup (persona_id);
+          if (persona != null)
+            {
+              persona._update (c);
+            }
+        }
+    }
+
+  private void _contacts_removed_cb (GLib.List<string> ids)
+    {
+      var removed_personas = new Queue<Persona> ();
+
+      foreach (string id in (GLib.List<string>) ids)
+        {
+          var persona = _personas.lookup (this.id + ":" + id);
+          if (persona != null)
+            {
+              removed_personas.push_tail (persona);
+              _personas.remove (persona.iid);
+            }
+        }
+
+       if (removed_personas.length > 0)
+         {
+           this.personas_changed (null, removed_personas.head, null, null, 0);
+         }
+    }
+}
diff --git a/backends/eds/lib/edsf-persona.vala b/backends/eds/lib/edsf-persona.vala
new file mode 100644
index 0000000..55f58ec
--- /dev/null
+++ b/backends/eds/lib/edsf-persona.vala
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Travis Reitter <travis.reitter@collabora.co.uk>
+ *       Marco Barisione <marco.barisione@collabora.co.uk>
+ *       Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
+ */
+
+using GLib;
+using Folks;
+using E;
+using Xml;
+using Posix;
+
+/**
+ * A persona subclass which represents a single EDS contact.
+ */
+public class Edsf.Persona : Folks.Persona,
+    HasAvatar,
+    NameOwner,
+    GenderOwner,
+    IMable,
+    Phoneable,
+    Emailable,
+    URLable
+{
+  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", "email-addresses"
+  };
+  private GLib.List<FieldDetails> _phone_numbers;
+  private GLib.List<FieldDetails> _email_addresses;
+  private weak E.Contact _contact;
+  private static HashTable<string, GLib.List<string>> _im_eds_map = null;
+
+  /**
+   * {@inheritDoc}
+   */
+  public GLib.List<FieldDetails> phone_numbers
+    {
+      get { return this._phone_numbers; }
+      private set
+        {
+          this._phone_numbers = new GLib.List<FieldDetails> ();
+          foreach (unowned FieldDetails ps in value)
+            {
+              this._phone_numbers.prepend (ps);
+            }
+          this._phone_numbers.reverse ();
+        }
+    }
+
+  /**
+   * {@inheritDoc}
+   */
+  public GLib.List<FieldDetails> email_addresses
+    {
+      get { return this._email_addresses; }
+      private set
+        {
+          this._email_addresses = new GLib.List<FieldDetails> ();
+          foreach (unowned FieldDetails e in value)
+            {
+              this._email_addresses.prepend (e);
+            }
+          this._email_addresses.reverse ();
+        }
+    }
+
+  /**
+   * {@inheritDoc}
+   */
+  public override string[] linkable_properties
+    {
+      get { return this._linkable_properties; }
+    }
+
+  /**
+   * An avatar for the Persona.
+   *
+   * See {@link Folks.Avatar.avatar}.
+   */
+  public File avatar { get; set; }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StructuredName structured_name { get; private set; }
+
+  /**
+   * {@inheritDoc}
+   */
+  public string full_name { get; private set; }
+
+
+  private string _nickname;
+  /**
+   * {@inheritDoc}
+   */
+  public string nickname { get { return this._nickname; } }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Gender gender { get; private set; }
+
+  private GLib.List<FieldDetails> _urls;
+
+  /**
+   * {@inheritDoc}
+   */
+  public GLib.List<FieldDetails> urls
+    {
+      get { return this._urls; }
+      private set
+        {
+          this._urls = new GLib.List<FieldDetails> ();
+          foreach (unowned FieldDetails ps in value)
+            this._urls.prepend (ps);
+          this._urls.reverse ();
+        }
+    }
+
+  private HashTable<string, GenericArray<string>> _im_addresses =
+      new HashTable<string, GenericArray<string>> (str_hash, str_equal);
+  /**
+   * {@inheritDoc}
+   */
+  public HashTable<string, GenericArray<string>> im_addresses
+    {
+      get { return this._im_addresses; }
+      private set {}
+    }
+
+  /**
+   * Create a new persona.
+   *
+   * Create a new persona for the {@link PersonaStore} `store`, representing
+   * the EDS contact given by `contact`.
+   */
+  public Persona (PersonaStore store, E.Contact contact)
+    {
+      string id = (string) contact.get (E.Contact.field_id ("id"));
+      string full_name = (string) contact.get
+          (E.Contact.field_id ("full_name"));
+      string uid = this.build_uid (BACKEND_NAME, store.id, id);
+      string iid = store.id + ":" + id;
+
+      debug ("Creating new Edsf.Persona with id '%s'", full_name);
+
+      Object (display_id: full_name,
+              uid: uid,
+              iid: iid,
+              store: store,
+              gender: Gender.UNSPECIFIED,
+              is_user: E.Book.is_self (contact));
+
+      this._update (contact);
+    }
+
+  ~Persona ()
+    {
+      debug ("Destroying Edsf.Persona '%s': %p", this.uid, this);
+    }
+
+  /**
+   * Update attribs of the persona.
+   *
+   * FIXME: save contact as an instance var and then call all the others.
+   */
+  internal void _update (E.Contact contact)
+    {
+      this._contact = contact;
+
+      this._update_names ();
+      this._update_avatar ();
+      this._update_urls ();
+      this._update_phones ();
+      this._update_email_addresses ();
+      this._update_im_addresses ();
+    }
+
+  private void _update_email_addresses ()
+    {
+      var email_addresses = new GLib.List<FieldDetails> ();
+
+      foreach (string efield in this.email_fields)
+        {
+          string e = (string) this._contact.get (E.Contact.field_id (efield));
+          if (e != null && e != "")
+            {
+              var fd = new FieldDetails (e);
+              fd.add_parameter ("type", efield);
+              email_addresses.prepend (fd);
+            }
+        }
+
+      if (this.email_addresses != email_addresses)
+        {
+          email_addresses.reverse ();
+          this.email_addresses = email_addresses;
+        }
+    }
+
+  private void _update_names ()
+    {
+      string full_name = (string) this._contact.get
+          (E.Contact.field_id ("full_name"));
+      if (this.full_name != full_name)
+        {
+          this.full_name = full_name;
+        }
+
+      E.ContactName cn = (E.ContactName) this._contact.get
+          (E.Contact.field_id ("name"));
+      string family_name = cn.family;
+      string given_name  = cn.given;
+      string additional_names = cn.additional;
+      string prefixes = cn.prefixes;
+      string suffixes = cn.suffixes;
+      var structured_name = new StructuredName (family_name, given_name,
+                                                additional_names, prefixes,
+                                                suffixes);
+      if (!structured_name.is_empty ())
+        {
+          this.structured_name = structured_name;
+        }
+      else if (this.structured_name != null)
+        {
+          this.structured_name = null;
+        }
+    }
+
+  private void _update_avatar ()
+    {
+      var avatar_path = this._get_photo_path ();
+      if (avatar_path != "")
+        {
+          var avatar_file = File.new_for_path (avatar_path);
+          if (this.avatar == null || !this.avatar.equal (avatar_file))
+            {
+              this.avatar = avatar_file;
+            }
+        }
+    }
+
+  private void _update_urls ()
+    {
+      var urls = new GLib.List<FieldDetails> ();
+
+      foreach (var u in this._get_urls ())
+        {
+          urls.prepend (new FieldDetails (u));
+        }
+
+      if (this.urls != urls)
+        {
+          urls.reverse ();
+          this.urls = urls;
+        }
+    }
+
+  private void _update_im_addresses ()
+    {
+      this._im_addresses.remove_all ();
+      var im_eds_map = this._get_im_eds_map ();
+
+      foreach (var proto in im_eds_map.get_keys ())
+        {
+          unowned GLib.List<string> proto_aliases = im_eds_map.lookup (proto);
+          var im_address_array = new GenericArray<string> ();
+
+          foreach (string field_id in proto_aliases)
+            {
+              string field_value = (string) this._contact.get
+                  (E.Contact.field_id (field_id));
+              if (field_value != null && field_value.length != 0)
+                {
+                  try
+                    {
+                      string address = (owned) normalise_im_address
+                          ((owned) field_value, proto);
+                      im_address_array.add (address);
+                    }
+                  catch (Folks.IMableError e)
+                    {
+                      GLib.warning (
+                          "Problem when trying to normalise address: %s\n",
+                          e.message);
+                    }
+                }
+            }
+
+          this._im_addresses.insert (proto, im_address_array);
+        }
+    }
+
+  private string _get_photo_path ()
+    {
+      string filename = this.uid;
+      string photo_path = GLib.Path.build_filename (
+                            GLib.Environment.get_user_cache_dir (), "folks",
+                            "avatars", filename);
+      E.ContactPhoto p = (E.ContactPhoto) this._contact.get (
+                           E.Contact.field_id ("photo"));
+
+      if (p != null)
+        {
+          this._save_photo (photo_path, p);
+        }
+      else
+        {
+          /* if image ceased existing on EDS but exists on cache,
+           * we remove it. */
+          FileUtils.remove (photo_path);
+          photo_path = "";
+        }
+
+      return photo_path;
+    }
+
+  /*
+   * TODO: check if image exists on cache and hasn't change.
+   *       If so, don't overwrite.
+   */
+  private bool _save_photo (string photo_path, E.ContactPhoto p)
+    {
+      bool saved = false;
+
+      if (p.type == ContactPhotoType.INLINED)
+        {
+          try
+            {
+              GLib.FileUtils.set_data (photo_path, (uint8 []) p.photo_data);
+              saved = true;
+            }
+          catch (GLib.FileError e)
+            {
+              GLib.warning ("Problem in writing file in _save_photo: %s\n",
+                            e.message);
+            }
+        }
+      else if (p.type == ContactPhotoType.URI)
+        {
+          try
+            {
+              File src_file = File.new_for_uri (p.photo_uri);
+              File dst_file = File.new_for_path (photo_path);
+              src_file.copy (dst_file, FileCopyFlags.OVERWRITE);
+              saved = true;
+            }
+          catch (GLib.Error e)
+            {
+              GLib.warning ("Problem in writing file in _save_photo: %s\n",
+                            e.message);
+            }
+        }
+
+      return saved;
+    }
+
+  /**
+   * build a table of im protocols / im protocol aliass
+   */
+  private HashTable<string, GLib.List<string>> _get_im_eds_map ()
+    {
+      if (this._im_eds_map == null)
+        {
+          this._im_eds_map = new HashTable<string, GLib.List<string>>
+              (str_hash, str_equal);
+          string [] protos = { "aim", "yahoo", "groupwise", "jabber" };
+
+          foreach (string p in protos)
+            {
+              GLib.List<string>  properties = new GLib.List<string> ();
+              properties.prepend ("im_" + p + "_home_1");
+              properties.prepend ("im_" + p + "_home_2");
+              properties.prepend ("im_" + p + "_home_3");
+              properties.prepend ("im_" + p + "_work_1");
+              properties.prepend ("im_" + p + "_work_2");
+              properties.prepend ("im_" + p + "_work_3");
+
+              properties.reverse ();
+
+              this._im_eds_map.insert ((owned) p, (owned) properties);
+            }
+        }
+
+      return this._im_eds_map;
+    }
+
+  /**
+   * TODO: add parameters to each URL
+   */
+  private GLib.List<string> _get_urls ()
+    {
+      GLib.List<string>  urls = new GLib.List<string> ();
+
+      foreach (string url_property in this.url_properties)
+        {
+          string u = (string) this._contact.get (E.Contact.field_id (url_property));
+          if (u != null && u != "")
+            {
+              urls.prepend (u);
+            }
+        }
+
+      urls.reverse ();
+      return urls;
+    }
+
+  private void _update_phones ()
+    {
+      var phones = new GLib.List<FieldDetails> ();
+
+      foreach (string pfield in this.phone_fields)
+        {
+          string p = (string) this._contact.get (E.Contact.field_id (pfield));
+          if (p != null && p != "")
+            {
+              var fd = new FieldDetails (p);
+              fd.add_parameter ("type", pfield);
+              phones.prepend (fd);
+            }
+        }
+
+      if (this.phone_numbers != phones)
+        {
+          phones.reverse ();
+          this.phone_numbers = phones;
+        }
+    }
+}
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..dfe44c6
--- /dev/null
+++ b/backends/eds/lib/folks-eds.deps
@@ -0,0 +1,5 @@
+glib-2.0
+gobject-2.0
+folks
+libebook-1.2
+libedataserver-1.2
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/backends/eds/vapi/libebook-1.2.vapi b/backends/eds/vapi/libebook-1.2.vapi
new file mode 100644
index 0000000..11a1cf2
--- /dev/null
+++ b/backends/eds/vapi/libebook-1.2.vapi
@@ -0,0 +1,703 @@
+/* ebook.vapi generated by vapigen, do not modify. */
+
+[CCode (cprefix = "E", lower_case_cprefix = "e_", gir_namespace = "EBook", gir_version = "1.2")]
+namespace E {
+	[CCode (cheader_filename = "libebook/e-book.h")]
+	public class Book : GLib.Object {
+		public E.BookPrivate priv;
+		[CCode (has_construct_function = false)]
+		public Book (E.Source source) throws GLib.Error;
+		public bool add_contact (E.Contact contact) throws GLib.Error;
+		public uint async_get_book_view (E.BookQuery query, GLib.List<weak void*> requested_fields, int max_results, E.BookBookViewCallback cb, void* closure);
+		public bool authenticate_user (string user, string passwd, string auth_method) throws GLib.Error;
+		public bool cancel () throws GLib.Error;
+		public bool cancel_async_op () throws GLib.Error;
+		public bool check_static_capability (string cap);
+		public bool commit_contact (E.Contact contact) throws GLib.Error;
+		[CCode (has_construct_function = false)]
+		public Book.default_addressbook () throws GLib.Error;
+		public static GLib.Quark error_quark ();
+		public static void free_change_list (GLib.List<weak void*> change_list);
+		[CCode (has_construct_function = false)]
+		public Book.from_uri (string uri) throws GLib.Error;
+		public static bool get_addressbooks (out E.SourceList addressbook_sources) throws GLib.Error;
+		public bool get_book_view (E.BookQuery query, GLib.List<weak void*>? requested_fields, int max_results, out E.BookView book_view) throws GLib.Error;
+		public bool get_book_view_async (E.BookQuery query, GLib.List<weak void*> requested_fields, int max_results, E.BookBookViewAsyncCallback cb, void* closure);
+		public bool get_changes (string changeid, GLib.List<weak void*> changes) throws GLib.Error;
+		public bool get_contact (string id, E.Contact contact) throws GLib.Error;
+		public bool get_contacts (E.BookQuery query, GLib.List<weak void*> contacts) throws GLib.Error;
+		public bool get_required_fields (GLib.List<weak void*> fields) throws GLib.Error;
+		public static bool get_self (out E.Contact contact, out E.Book book) throws GLib.Error;
+		public unowned string get_static_capabilities () throws GLib.Error;
+		public bool get_supported_auth_methods (GLib.List<weak void*> auth_methods) throws GLib.Error;
+		public bool get_supported_fields (GLib.List<weak void*> fields) throws GLib.Error;
+		public unowned string get_uri ();
+		public bool is_online ();
+		public bool is_opened ();
+		public static bool is_self (E.Contact contact);
+		public bool is_writable ();
+		public bool open (bool only_if_exists) throws GLib.Error;
+		public bool remove () throws GLib.Error;
+		public bool remove_contact (string id) throws GLib.Error;
+		public bool remove_contacts (GLib.List<weak void*> ids) throws GLib.Error;
+		public bool set_default_addressbook () throws GLib.Error;
+		public static bool set_default_source (E.Source source) throws GLib.Error;
+		public bool set_self (E.Contact contact) throws GLib.Error;
+		[CCode (has_construct_function = false)]
+		public Book.system_addressbook () throws GLib.Error;
+		public virtual signal void auth_required ();
+		public virtual signal void backend_died ();
+		public virtual signal void connection_status (bool book);
+		public virtual signal void writable_status (bool book);
+	}
+	[Compact]
+	[CCode (copy_function = "g_boxed_copy", free_function = "g_boxed_free", type_id = "e_book_query_get_type ()", cheader_filename = "libebook/e-book.h")]
+	public class BookQuery {
+		public static E.BookQuery and (int nqs, E.BookQuery qs, bool unref);
+		public static E.BookQuery any_field_contains (string value);
+		public E.BookQuery copy ();
+		public static E.BookQuery field_exists (E.ContactField field);
+		public static E.BookQuery field_test (E.ContactField field, E.BookQueryTest test, string value);
+		public static E.BookQuery from_string (string query_string);
+		public E.BookQuery not (bool unref);
+		public static E.BookQuery or (int nqs, E.BookQuery qs, bool unref);
+		public E.BookQuery @ref ();
+		public string to_string ();
+		public void unref ();
+		public static E.BookQuery vcard_field_exists (string field);
+		public static E.BookQuery vcard_field_test (string field, E.BookQueryTest test, string value);
+	}
+	[CCode (cheader_filename = "libebook/e-book.h")]
+	public class BookView : GLib.Object {
+		public E.BookViewPrivate priv;
+		public void start ();
+		public void stop ();
+		public virtual signal void contacts_added (GLib.List<weak void*> contacts);
+		public virtual signal void contacts_changed (GLib.List<weak void*> contacts);
+		public virtual signal void contacts_removed (GLib.List<weak void*> ids);
+		public virtual signal void sequence_complete (uint book_view);
+		public virtual signal void status_message (string book_view);
+		public virtual signal void view_complete (uint book_view, string status);
+	}
+	[CCode (cheader_filename = "libebook/e-book.h")]
+	public class Contact : E.VCard {
+		public E.ContactPrivate priv;
+		[CCode (has_construct_function = false)]
+		public Contact ();
+		public static E.ContactField field_id (string field_name);
+		public static E.ContactField field_id_from_vcard (string vcard_field);
+		public static unowned string field_name (E.ContactField field_id);
+		[CCode (has_construct_function = false)]
+		public Contact.from_vcard (string vcard);
+		public void* @get (E.ContactField field_id);
+		public static unowned string pretty_name (E.ContactField field_id);
+		public void @set (E.ContactField field_id, void* value);
+		public void set_attributes (E.ContactField field_id, GLib.List<weak void*> attributes);
+		public static unowned string vcard_attribute (E.ContactField field_id);
+		[NoAccessorMethod]
+		public string Rev { get; set; }
+		[NoAccessorMethod]
+		public void* address { get; set; }
+		[NoAccessorMethod]
+		public E.ContactAddress address_home { get; set; }
+		[NoAccessorMethod]
+		public string address_label_home { get; set; }
+		[NoAccessorMethod]
+		public string address_label_other { get; set; }
+		[NoAccessorMethod]
+		public string address_label_work { get; set; }
+		[NoAccessorMethod]
+		public E.ContactAddress address_other { get; set; }
+		[NoAccessorMethod]
+		public E.ContactAddress address_work { get; set; }
+		[NoAccessorMethod]
+		public E.ContactDate anniversary { get; set; }
+		[NoAccessorMethod]
+		public string assistant { get; set; }
+		[NoAccessorMethod]
+		public string assistant_phone { get; set; }
+		[NoAccessorMethod]
+		public E.ContactDate birth_date { get; set; }
+		[NoAccessorMethod]
+		public string blog_url { get; set; }
+		[NoAccessorMethod]
+		public string book_uri { get; set; }
+		[NoAccessorMethod]
+		public string business_fax { get; set; }
+		[NoAccessorMethod]
+		public string business_phone { get; set; }
+		[NoAccessorMethod]
+		public string business_phone_2 { get; set; }
+		[NoAccessorMethod]
+		public string callback_phone { get; set; }
+		[NoAccessorMethod]
+		public string caluri { get; set; }
+		[NoAccessorMethod]
+		public string car_phone { get; set; }
+		[NoAccessorMethod]
+		public string categories { get; set; }
+		[NoAccessorMethod]
+		public void* category_list { get; set; }
+		[NoAccessorMethod]
+		public string company_phone { get; set; }
+		[NoAccessorMethod]
+		public void* email { get; set; }
+		[NoAccessorMethod]
+		public string email_1 { get; set; }
+		[NoAccessorMethod]
+		public string email_2 { get; set; }
+		[NoAccessorMethod]
+		public string email_3 { get; set; }
+		[NoAccessorMethod]
+		public string email_4 { get; set; }
+		[NoAccessorMethod]
+		public string family_name { get; set; }
+		[NoAccessorMethod]
+		public string fburl { get; set; }
+		[NoAccessorMethod]
+		public string file_as { get; set; }
+		[NoAccessorMethod]
+		public string full_name { get; set; }
+		[NoAccessorMethod]
+		public E.ContactGeo geo { get; set; }
+		[NoAccessorMethod]
+		public string given_name { get; set; }
+		[NoAccessorMethod]
+		public string home_fax { get; set; }
+		[NoAccessorMethod]
+		public string home_phone { get; set; }
+		[NoAccessorMethod]
+		public string home_phone_2 { get; set; }
+		[NoAccessorMethod]
+		public string homepage_url { get; set; }
+		[NoAccessorMethod]
+		public string icscalendar { get; set; }
+		[NoAccessorMethod]
+		public string id { get; set; }
+		[NoAccessorMethod]
+		public void* im_aim { get; set; }
+		[NoAccessorMethod]
+		public string im_aim_home_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_aim_home_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_aim_home_3 { get; set; }
+		[NoAccessorMethod]
+		public string im_aim_work_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_aim_work_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_aim_work_3 { get; set; }
+		[NoAccessorMethod]
+		public void* im_gadugadu { get; set; }
+		[NoAccessorMethod]
+		public string im_gadugadu_home_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_gadugadu_home_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_gadugadu_home_3 { get; set; }
+		[NoAccessorMethod]
+		public string im_gadugadu_work_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_gadugadu_work_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_gadugadu_work_3 { get; set; }
+		[NoAccessorMethod]
+		public void* im_groupwise { get; set; }
+		[NoAccessorMethod]
+		public string im_groupwise_home_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_groupwise_home_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_groupwise_home_3 { get; set; }
+		[NoAccessorMethod]
+		public string im_groupwise_work_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_groupwise_work_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_groupwise_work_3 { get; set; }
+		[NoAccessorMethod]
+		public void* im_icq { get; set; }
+		[NoAccessorMethod]
+		public string im_icq_home_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_icq_home_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_icq_home_3 { get; set; }
+		[NoAccessorMethod]
+		public string im_icq_work_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_icq_work_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_icq_work_3 { get; set; }
+		[NoAccessorMethod]
+		public void* im_jabber { get; set; }
+		[NoAccessorMethod]
+		public string im_jabber_home_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_jabber_home_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_jabber_home_3 { get; set; }
+		[NoAccessorMethod]
+		public string im_jabber_work_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_jabber_work_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_jabber_work_3 { get; set; }
+		[NoAccessorMethod]
+		public void* im_msn { get; set; }
+		[NoAccessorMethod]
+		public string im_msn_home_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_msn_home_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_msn_home_3 { get; set; }
+		[NoAccessorMethod]
+		public string im_msn_work_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_msn_work_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_msn_work_3 { get; set; }
+		[NoAccessorMethod]
+		public void* im_skype { get; set; }
+		[NoAccessorMethod]
+		public string im_skype_home_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_skype_home_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_skype_home_3 { get; set; }
+		[NoAccessorMethod]
+		public string im_skype_work_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_skype_work_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_skype_work_3 { get; set; }
+		[NoAccessorMethod]
+		public void* im_yahoo { get; set; }
+		[NoAccessorMethod]
+		public string im_yahoo_home_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_yahoo_home_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_yahoo_home_3 { get; set; }
+		[NoAccessorMethod]
+		public string im_yahoo_work_1 { get; set; }
+		[NoAccessorMethod]
+		public string im_yahoo_work_2 { get; set; }
+		[NoAccessorMethod]
+		public string im_yahoo_work_3 { get; set; }
+		[NoAccessorMethod]
+		public string isdn_phone { get; set; }
+		[NoAccessorMethod]
+		public bool list { get; set; }
+		[NoAccessorMethod]
+		public bool list_show_addresses { get; set; }
+		[NoAccessorMethod]
+		public E.ContactPhoto logo { get; set; }
+		[NoAccessorMethod]
+		public string mailer { get; set; }
+		[NoAccessorMethod]
+		public string manager { get; set; }
+		[NoAccessorMethod]
+		public string mobile_phone { get; set; }
+		[NoAccessorMethod]
+		public E.ContactName name { get; set; }
+		[NoAccessorMethod]
+		public string name_or_org { get; }
+		[NoAccessorMethod]
+		public string nickname { get; set; }
+		[NoAccessorMethod]
+		public string note { get; set; }
+		[NoAccessorMethod]
+		public string office { get; set; }
+		[NoAccessorMethod]
+		public string org { get; set; }
+		[NoAccessorMethod]
+		public string org_unit { get; set; }
+		[NoAccessorMethod]
+		public string other_fax { get; set; }
+		[NoAccessorMethod]
+		public string other_phone { get; set; }
+		[NoAccessorMethod]
+		public string pager { get; set; }
+		[NoAccessorMethod]
+		public void* phone { get; set; }
+		[NoAccessorMethod]
+		public E.ContactPhoto photo { get; set; }
+		[NoAccessorMethod]
+		public string primary_phone { get; set; }
+		[NoAccessorMethod]
+		public string radio { get; set; }
+		[NoAccessorMethod]
+		public string role { get; set; }
+		[NoAccessorMethod]
+		public void* sip { get; set; }
+		[NoAccessorMethod]
+		public string spouse { get; set; }
+		[NoAccessorMethod]
+		public string telex { get; set; }
+		[NoAccessorMethod]
+		public string title { get; set; }
+		[NoAccessorMethod]
+		public string tty { get; set; }
+		[NoAccessorMethod]
+		public string video_url { get; set; }
+		[NoAccessorMethod]
+		public bool wants_html { get; set; }
+		[NoAccessorMethod]
+		public E.ContactCert x509Cert { get; set; }
+	}
+	[Compact]
+	[CCode (copy_function = "g_boxed_copy", free_function = "g_boxed_free", type_id = "e_contact_address_get_type ()", cheader_filename = "libebook/e-book.h")]
+	public class ContactAddress {
+		public string address_format;
+		public string code;
+		public string country;
+		public string ext;
+		public string locality;
+		public string po;
+		public string region;
+		public string street;
+		public void free ();
+	}
+	[Compact]
+	[CCode (copy_function = "g_boxed_copy", free_function = "g_boxed_free", type_id = "e_contact_cert_get_type ()", cheader_filename = "libebook/e-book.h")]
+	public class ContactCert {
+		public weak string data;
+		public ulong length;
+		public void free ();
+	}
+	[Compact]
+	[CCode (copy_function = "g_boxed_copy", free_function = "g_boxed_free", type_id = "e_contact_date_get_type ()", cheader_filename = "libebook/e-book.h")]
+	public class ContactDate {
+		public uint day;
+		public uint month;
+		public uint year;
+		public bool equal (E.ContactDate dt2);
+		public void free ();
+		public static E.ContactDate from_string (string str);
+		public string to_string ();
+	}
+	[Compact]
+	[CCode (copy_function = "g_boxed_copy", free_function = "g_boxed_free", type_id = "e_contact_geo_get_type ()", cheader_filename = "libebook/e-book.h")]
+	public class ContactGeo {
+		public double latitude;
+		public double longitude;
+		public void free ();
+	}
+	[Compact]
+	[CCode (copy_function = "g_boxed_copy", free_function = "g_boxed_free", type_id = "e_contact_name_get_type ()", cheader_filename = "libebook/e-book.h")]
+	public class ContactName {
+		public string additional;
+		public string family;
+		public string given;
+		public string prefixes;
+		public string suffixes;
+		public E.ContactName copy ();
+		public ContactName ();
+		public void free ();
+		public static E.ContactName from_string (string name_str);
+		public string to_string ();
+	}
+	[Compact]
+	[CCode (copy_function = "g_boxed_copy", free_function = "g_boxed_free", type_id = "e_contact_photo_get_type ()", cheader_filename = "libebook/e-book.h")]
+	public class ContactPhoto {
+		public E.ContactPhotoType type;
+		[CCode (cname="data.inlined.mime_type")]
+		public string mime_type;
+		[CCode (cname="data.inlined.length")]
+		public int photo_data_length;
+		[CCode (cname="data.inlined.data", array_length_cname="data.inlined.length")]
+		public uchar [] photo_data;
+		[CCode (cname="data.uri")]
+		public string photo_uri;
+		public void free ();
+	}
+	[CCode (cheader_filename = "libebook/e-book.h")]
+	public class VCard : GLib.Object {
+		public E.VCardPrivate priv;
+		[CCode (has_construct_function = false)]
+		public VCard ();
+		public void add_attribute (E.VCardAttribute attr);
+		public void add_attribute_with_value (E.VCardAttribute attr, string value);
+		public void append_attribute (E.VCardAttribute attr);
+		public void append_attribute_with_value (E.VCardAttribute attr, string value);
+		public void @construct (string str);
+		public void dump_structure ();
+		public static string escape_string (string s);
+		[CCode (has_construct_function = false)]
+		public VCard.from_string (string str);
+		public void remove_attribute (E.VCardAttribute attr);
+		public void remove_attributes (string attr_group, string attr_name);
+		public string to_string (E.VCardFormat format);
+		public static string unescape_string (string s);
+	}
+	[CCode (type_id = "E_TYPE_BOOK_CHANGE", cheader_filename = "libebook/e-book.h")]
+	public struct BookChange {
+		public E.BookChangeType change_type;
+		public weak E.Contact contact;
+	}
+	[CCode (type_id = "E_TYPE_BOOK_PRIVATE", cheader_filename = "libebook/e-book.h")]
+	public struct BookPrivate {
+	}
+	[CCode (type_id = "E_TYPE_BOOK_VIEW_PRIVATE", cheader_filename = "libebook/e-book.h")]
+	public struct BookViewPrivate {
+	}
+	[CCode (type_id = "E_TYPE_CONTACT_PRIVATE", cheader_filename = "libebook/e-book.h")]
+	public struct ContactPrivate {
+	}
+	[CCode (type_id = "E_TYPE_VCARD_ATTRIBUTE", cheader_filename = "libebook/e-book.h")]
+	public struct VCardAttribute {
+		public void add_param (E.VCardAttributeParam param);
+		public void add_param_with_value (E.VCardAttributeParam param, string value);
+		public void add_value (string value);
+		public void add_value_decoded (string value, int len);
+		public void free ();
+		public unowned string get_group ();
+		public unowned string get_name ();
+		public string get_value ();
+		public bool has_type (string typestr);
+		public bool is_single_valued ();
+		public void remove_param (string param_name);
+		public void remove_param_value (string param_name, string s);
+		public void remove_params ();
+		public void remove_value (string s);
+		public void remove_values ();
+	}
+	[CCode (type_id = "E_TYPE_VCARD_ATTRIBUTE_PARAM", cheader_filename = "libebook/e-book.h")]
+	public struct VCardAttributeParam {
+		public void add_value (string value);
+		public void free ();
+		public unowned string get_name ();
+		public void remove_values ();
+	}
+	[CCode (type_id = "E_TYPE_VCARD_PRIVATE", cheader_filename = "libebook/e-book.h")]
+	public struct VCardPrivate {
+	}
+	[CCode (cprefix = "E_BOOK_CHANGE_CARD_", cheader_filename = "libebook/e-book.h")]
+	public enum BookChangeType {
+		ADDED,
+		DELETED,
+		MODIFIED
+	}
+	[CCode (cprefix = "E_BOOK_QUERY_", cheader_filename = "libebook/e-book.h")]
+	public enum BookQueryTest {
+		IS,
+		CONTAINS,
+		BEGINS_WITH,
+		ENDS_WITH
+	}
+	[CCode (cprefix = "E_BOOK_ERROR_", cheader_filename = "libebook/e-book.h")]
+	public enum BookStatus {
+		OK,
+		INVALID_ARG,
+		BUSY,
+		REPOSITORY_OFFLINE,
+		NO_SUCH_BOOK,
+		NO_SELF_CONTACT,
+		SOURCE_NOT_LOADED,
+		SOURCE_ALREADY_LOADED,
+		PERMISSION_DENIED,
+		CONTACT_NOT_FOUND,
+		CONTACT_ID_ALREADY_EXISTS,
+		PROTOCOL_NOT_SUPPORTED,
+		CANCELLED,
+		COULD_NOT_CANCEL,
+		AUTHENTICATION_FAILED,
+		AUTHENTICATION_REQUIRED,
+		TLS_NOT_AVAILABLE,
+		DBUS_EXCEPTION,
+		NO_SUCH_SOURCE,
+		OFFLINE_UNAVAILABLE,
+		OTHER_ERROR,
+		INVALID_SERVER_VERSION,
+		UNSUPPORTED_AUTHENTICATION_METHOD,
+		NO_SPACE,
+		NOT_SUPPORTED
+	}
+	[CCode (cprefix = "E_BOOK_VIEW_", cheader_filename = "libebook/e-book.h")]
+	public enum BookViewStatus {
+		STATUS_OK,
+		STATUS_TIME_LIMIT_EXCEEDED,
+		STATUS_SIZE_LIMIT_EXCEEDED,
+		ERROR_INVALID_QUERY,
+		ERROR_QUERY_REFUSED,
+		ERROR_OTHER_ERROR
+	}
+	[CCode (cprefix = "E_CONTACT_", cheader_filename = "libebook/e-book.h")]
+	public enum ContactField {
+		UID,
+		FILE_AS,
+		BOOK_URI,
+		FULL_NAME,
+		GIVEN_NAME,
+		FAMILY_NAME,
+		NICKNAME,
+		EMAIL_1,
+		EMAIL_2,
+		EMAIL_3,
+		EMAIL_4,
+		MAILER,
+		ADDRESS_LABEL_HOME,
+		ADDRESS_LABEL_WORK,
+		ADDRESS_LABEL_OTHER,
+		PHONE_ASSISTANT,
+		PHONE_BUSINESS,
+		PHONE_BUSINESS_2,
+		PHONE_BUSINESS_FAX,
+		PHONE_CALLBACK,
+		PHONE_CAR,
+		PHONE_COMPANY,
+		PHONE_HOME,
+		PHONE_HOME_2,
+		PHONE_HOME_FAX,
+		PHONE_ISDN,
+		PHONE_MOBILE,
+		PHONE_OTHER,
+		PHONE_OTHER_FAX,
+		PHONE_PAGER,
+		PHONE_PRIMARY,
+		PHONE_RADIO,
+		PHONE_TELEX,
+		PHONE_TTYTDD,
+		ORG,
+		ORG_UNIT,
+		OFFICE,
+		TITLE,
+		ROLE,
+		MANAGER,
+		ASSISTANT,
+		HOMEPAGE_URL,
+		BLOG_URL,
+		CATEGORIES,
+		CALENDAR_URI,
+		FREEBUSY_URL,
+		ICS_CALENDAR,
+		VIDEO_URL,
+		SPOUSE,
+		NOTE,
+		IM_AIM_HOME_1,
+		IM_AIM_HOME_2,
+		IM_AIM_HOME_3,
+		IM_AIM_WORK_1,
+		IM_AIM_WORK_2,
+		IM_AIM_WORK_3,
+		IM_GROUPWISE_HOME_1,
+		IM_GROUPWISE_HOME_2,
+		IM_GROUPWISE_HOME_3,
+		IM_GROUPWISE_WORK_1,
+		IM_GROUPWISE_WORK_2,
+		IM_GROUPWISE_WORK_3,
+		IM_JABBER_HOME_1,
+		IM_JABBER_HOME_2,
+		IM_JABBER_HOME_3,
+		IM_JABBER_WORK_1,
+		IM_JABBER_WORK_2,
+		IM_JABBER_WORK_3,
+		IM_YAHOO_HOME_1,
+		IM_YAHOO_HOME_2,
+		IM_YAHOO_HOME_3,
+		IM_YAHOO_WORK_1,
+		IM_YAHOO_WORK_2,
+		IM_YAHOO_WORK_3,
+		IM_MSN_HOME_1,
+		IM_MSN_HOME_2,
+		IM_MSN_HOME_3,
+		IM_MSN_WORK_1,
+		IM_MSN_WORK_2,
+		IM_MSN_WORK_3,
+		IM_ICQ_HOME_1,
+		IM_ICQ_HOME_2,
+		IM_ICQ_HOME_3,
+		IM_ICQ_WORK_1,
+		IM_ICQ_WORK_2,
+		IM_ICQ_WORK_3,
+		REV,
+		NAME_OR_ORG,
+		ADDRESS,
+		ADDRESS_HOME,
+		ADDRESS_WORK,
+		ADDRESS_OTHER,
+		CATEGORY_LIST,
+		PHOTO,
+		LOGO,
+		NAME,
+		EMAIL,
+		IM_AIM,
+		IM_GROUPWISE,
+		IM_JABBER,
+		IM_YAHOO,
+		IM_MSN,
+		IM_ICQ,
+		WANTS_HTML,
+		IS_LIST,
+		LIST_SHOW_ADDRESSES,
+		BIRTH_DATE,
+		ANNIVERSARY,
+		X509_CERT,
+		IM_GADUGADU_HOME_1,
+		IM_GADUGADU_HOME_2,
+		IM_GADUGADU_HOME_3,
+		IM_GADUGADU_WORK_1,
+		IM_GADUGADU_WORK_2,
+		IM_GADUGADU_WORK_3,
+		IM_GADUGADU,
+		GEO,
+		TEL,
+		IM_SKYPE_HOME_1,
+		IM_SKYPE_HOME_2,
+		IM_SKYPE_HOME_3,
+		IM_SKYPE_WORK_1,
+		IM_SKYPE_WORK_2,
+		IM_SKYPE_WORK_3,
+		IM_SKYPE,
+		SIP,
+		FIELD_LAST,
+		FIELD_FIRST,
+		LAST_SIMPLE_STRING,
+		FIRST_PHONE_ID,
+		LAST_PHONE_ID,
+		FIRST_EMAIL_ID,
+		LAST_EMAIL_ID,
+		FIRST_ADDRESS_ID,
+		LAST_ADDRESS_ID,
+		FIRST_LABEL_ID,
+		LAST_LABEL_ID
+	}
+	[CCode (cprefix = "E_CONTACT_PHOTO_TYPE_", cheader_filename = "libebook/e-book.h")]
+	public enum ContactPhotoType {
+		INLINED,
+		URI
+	}
+	[CCode (cprefix = "EVC_FORMAT_VCARD_", cheader_filename = "libebook/e-book.h")]
+	public enum VCardFormat {
+		@21,
+		@30
+	}
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookAsyncCallback (E.Book book, GLib.Error error, void* closure);
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookBookViewAsyncCallback (E.Book book, GLib.Error error, E.BookView book_view, void* closure);
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookBookViewCallback (E.Book book, E.BookStatus status, E.BookView book_view, void* closure);
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookCallback (E.Book book, E.BookStatus status, void* closure);
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookContactAsyncCallback (E.Book book, GLib.Error error, E.Contact contact, void* closure);
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookContactCallback (E.Book book, E.BookStatus status, E.Contact contact, void* closure);
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookEListAsyncCallback (E.Book book, GLib.Error error, E.List list, void* closure);
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookEListCallback (E.Book book, E.BookStatus status, E.List list, void* closure);
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookIdAsyncCallback (E.Book book, GLib.Error error, string id, void* closure);
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookIdCallback (E.Book book, E.BookStatus status, string id, void* closure);
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookListAsyncCallback (E.Book book, GLib.Error error, GLib.List<weak void*> list, void* closure);
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookListCallback (E.Book book, E.BookStatus status, GLib.List<weak void*> list, void* closure);
+	[CCode (cheader_filename = "libebook/e-book.h", has_target = false)]
+	public delegate void BookOpenProgressCallback (E.Book book, string status_message, short percent, void* closure);
+}
diff --git a/backends/eds/vapi/libedataserver-1.2.vapi b/backends/eds/vapi/libedataserver-1.2.vapi
new file mode 100644
index 0000000..fc4e0a9
--- /dev/null
+++ b/backends/eds/vapi/libedataserver-1.2.vapi
@@ -0,0 +1,458 @@
+/* EDataServer-1.2.vapi generated by vapigen, do not modify. */
+
+[CCode (cprefix = "E", lower_case_cprefix = "e_", gir_namespace = "EDataServer", gir_version = "1.2")]
+namespace E {
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public class Account : GLib.Object {
+		[CCode (type = "EAccount*", has_construct_function = false)]
+		public Account ();
+		[CCode (type = "EAccount*", has_construct_function = false)]
+		public Account.from_xml (string xml);
+		public void import (E.Account src);
+		public bool set_from_xml (string xml);
+		public string to_xml ();
+		public static string uid_from_xml (string xml);
+		public bool writable_option (string protocol, string option);
+		public virtual signal void changed (int arg0);
+	}
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public class AccountList : E.List {
+		public int account_has_proxies ();
+		public void add ();
+		public void change ();
+		public unowned E.Account get_default ();
+		public void prune_proxies ();
+		public void remove ();
+		public void remove_account_proxies ();
+		public void save ();
+		public void set_default ();
+		public virtual signal void account_added (E.Account arg0);
+		public virtual signal void account_changed (E.Account arg0);
+		public virtual signal void account_removed (E.Account arg0);
+	}
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public class Iterator : GLib.Object {
+		public void @delete ();
+		public void* @get ();
+		public virtual void insert (void* object, bool before);
+		public virtual bool is_valid ();
+		public virtual void last ();
+		public virtual bool next ();
+		public virtual bool prev ();
+		[NoWrapper]
+		public virtual void remove ();
+		public virtual void reset ();
+		public virtual void @set (void* object);
+		[HasEmitter]
+		public virtual signal void invalidate ();
+	}
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public class List : GLib.Object {
+		public void* closure;
+		public weak E.ListFreeFunc free;
+		public weak GLib.List<weak void*> iterators;
+		public weak GLib.List<weak void*> list;
+		public void append (void* data);
+		public void invalidate_iterators (E.Iterator skip);
+		public int length ();
+		public void remove (void* data);
+		public void remove_iterator (E.Iterator iterator);
+		public void remove_link (GLib.List<weak void*> link);
+	}
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public class ListIterator : E.Iterator {
+		public weak GLib.List<weak void*> iterator;
+		public weak E.List list;
+		[CCode (type = "EIterator*", has_construct_function = false)]
+		public ListIterator (E.List list);
+	}
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public class Proxy : GLib.Object {
+		public E.ProxyPrivate priv;
+		[CCode (has_construct_function = false)]
+		public Proxy ();
+		public bool require_proxy_for_uri (string uri);
+		public void setup_proxy ();
+		public virtual signal void changed ();
+	}
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public class SoapMessage : GLib.Object {
+		[CCode (has_construct_function = false)]
+		public SoapMessage (string method, string uri_string, bool standalone, string xml_encoding, string env_prefix, string env_uri);
+		public void add_attribute (string name, string value, string prefix, string ns_uri);
+		public void add_namespace (string prefix, string ns_uri);
+		public void end_body ();
+		public void end_element ();
+		public void end_envelope ();
+		public void end_fault ();
+		public void end_fault_detail ();
+		public void end_header ();
+		public void end_header_element ();
+		public unowned string get_namespace_prefix (string ns_uri);
+		public void persist ();
+		public void reset ();
+		public void set_default_namespace (string ns_uri);
+		public void set_element_type (string xsi_type);
+		public void set_encoding_style (string enc_style);
+		public void set_null ();
+		public void start_body ();
+		public void start_element (string name, string prefix, string ns_uri);
+		public void start_envelope ();
+		public void start_fault (string faultcode, string faultstring, string faultfactor);
+		public void start_fault_detail ();
+		public void start_header ();
+		public void start_header_element (string name, bool must_understand, string actor_uri, string prefix, string ns_uri);
+		public void write_base64 (string string, int len);
+		public void write_buffer (string buffer, int len);
+		public void write_double (double d);
+		public void write_int (long i);
+		public void write_string (string string);
+		public void write_time (long timeval);
+	}
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public class SoapResponse : GLib.Object {
+		[CCode (has_construct_function = false)]
+		public SoapResponse ();
+		public int dump_response (void* buffer);
+		[CCode (has_construct_function = false)]
+		public SoapResponse.from_string (string xmlstr);
+		public unowned string get_method_name ();
+		public unowned GLib.List<weak void*> get_parameters ();
+		public void set_method_name (string method_name);
+		[CCode (cname = "e_soap_response_from_string")]
+		public bool soap_response_from_string (string xmlstr);
+	}
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public class Source : GLib.Object {
+		public E.SourcePrivate priv;
+		[CCode (has_construct_function = false)]
+		public Source (string name, string relative_uri);
+		public string build_absolute_uri ();
+		public void dump_to_xml_node (Xml.Node parent_node);
+		public bool equal (E.Source b);
+		[CCode (has_construct_function = false)]
+		public Source.from_standalone_xml (string xml);
+		[CCode (has_construct_function = false)]
+		public Source.from_xml_node (Xml.Node node);
+		public string get_duped_property (string property);
+		public unowned string get_property (string property);
+		public bool get_readonly ();
+		public string get_uri ();
+		public unowned string peek_absolute_uri ();
+		public unowned string peek_color_spec ();
+		public unowned string peek_name ();
+		public unowned string peek_relative_uri ();
+		public unowned string peek_uid ();
+		public void set_absolute_uri (string absolute_uri);
+		public void set_color_spec (string color_spec);
+		public void set_group (E.SourceGroup group);
+		public void set_name (string name);
+		public void set_property (string property, string value);
+		public void set_readonly (bool readonly);
+		public void set_relative_uri (string relative_uri);
+		public string to_standalone_xml ();
+		public static string uid_from_xml_node (Xml.Node node);
+		public bool update_from_xml_node (Xml.Node node, bool changed_return);
+		[CCode (has_construct_function = false)]
+		public Source.with_absolute_uri (string name, string absolute_uri);
+		public static bool xmlstr_equal (string a, string b);
+		public virtual signal void changed ();
+	}
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public class SourceGroup : GLib.Object {
+		public E.SourceGroupPrivate priv;
+		[CCode (has_construct_function = false)]
+		public SourceGroup (string name, string base_uri);
+		public bool add_source (E.Source source, int position);
+		public bool equal (E.SourceGroup b);
+		[CCode (has_construct_function = false)]
+		public SourceGroup.from_xml (string xml);
+		[CCode (has_construct_function = false)]
+		public SourceGroup.from_xmldoc (Xml.Doc doc);
+		public string get_property (string property);
+		public bool get_readonly ();
+		public unowned string peek_base_uri ();
+		public unowned string peek_name ();
+		public unowned GLib.SList<weak E.Source> peek_sources ();
+		public unowned string peek_uid ();
+		public bool remove_source (E.Source source);
+		public bool remove_source_by_uid (string uid);
+		public void set_base_uri (string base_uri);
+		public void set_name (string name);
+		public void set_property (string property, string value);
+		public void set_readonly (bool readonly);
+		public string to_xml ();
+		public static string uid_from_xmldoc (Xml.Doc doc);
+		public bool update_from_xml (string xml, bool changed_return);
+		public bool update_from_xmldoc (Xml.Doc doc, bool changed_return);
+		public static bool xmlstr_equal (string a, string b);
+		public virtual signal void changed ();
+		public virtual signal void source_added (GLib.Object source_list);
+		public virtual signal void source_removed (GLib.Object source_list);
+	}
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public class SourceList : GLib.Object {
+		public E.SourceListPrivate priv;
+		[CCode (has_construct_function = false)]
+		public SourceList ();
+		public bool add_group (E.SourceGroup group, int position);
+		[CCode (has_construct_function = false)]
+		public SourceList.for_gconf_default (string path);
+		public bool is_gconf_updated ();
+		public unowned GLib.SList<weak E.SourceGroup> peek_groups ();
+		public bool remove_group (E.SourceGroup group);
+		public bool remove_group_by_base_uri (string base_uri);
+		public bool remove_group_by_uid (string uid);
+		public bool remove_source_by_uid (string uidj);
+		public bool sync () throws GLib.Error;
+		public virtual signal void changed ();
+		public virtual signal void group_added (E.SourceGroup source_list);
+		public virtual signal void group_removed (E.SourceGroup source_list);
+	}
+	[CCode (type_id = "E_TYPE_ACCOUNT_LIST_PRIVATE", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct AccountListPrivate {
+	}
+	[CCode (type_id = "E_TYPE_FLAG", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct Flag {
+		public void clear ();
+		public void free ();
+		public bool is_set ();
+		public void @set ();
+		public bool timed_wait (GLib.TimeVal abs_time);
+		public void wait ();
+	}
+	[CCode (type_id = "E_TYPE_MEM_CHUNK", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct MemChunk {
+	}
+	[CCode (type_id = "E_TYPE_PROXY_PRIVATE", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct ProxyPrivate {
+	}
+	[CCode (type_id = "E_TYPE_SOAP_PARAMETER", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	// public struct SoapParameter : Xml.Node {
+	// 	public int get_int_value ();
+	// 	public unowned string get_name ();
+	// 	public string get_property ([CCode (pos = 2, array_length_pos = 2.1, delegate_target_pos = 2.1)] string prop_name);
+	// 	public string get_string_value ();
+	// }
+	[CCode (type_id = "E_TYPE_SOURCE_GROUP_PRIVATE", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct SourceGroupPrivate {
+	}
+	[CCode (type_id = "E_TYPE_SOURCE_LIST_PRIVATE", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct SourceListPrivate {
+	}
+	[CCode (type_id = "E_TYPE_SOURCE_PRIVATE", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct SourcePrivate {
+	}
+	[CCode (type_id = "E_TYPE_URI", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct Uri {
+		public weak string protocol;
+		public weak string user;
+		public weak string authmech;
+		public weak string passwd;
+		public weak string host;
+		public int port;
+		public weak string path;
+		public GLib.Datalist @params;
+		public weak string query;
+		public weak string fragment;
+		public void free ();
+		public unowned string get_param (string name);
+		public string to_string (bool show_password);
+	}
+	[CCode (type_id = "E_TYPE_XML_HASH", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct XmlHash {
+	}
+	[CCode (cname = "_EAccount", type_id = "E_TYPE__ACCOUNT", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct _Account {
+		public weak GLib.Object parent_object;
+		public weak string name;
+		public weak string uid;
+		public bool enabled;
+		public weak string drafts_folder_uri;
+		public weak string sent_folder_uri;
+		public weak string templates_folder_uri;
+		public bool always_cc;
+		public weak string cc_addrs;
+		public bool always_bcc;
+		public weak string bcc_addrs;
+		public E.AccountReceiptPolicy receipt_policy;
+		public weak string pgp_key;
+		public weak string pgp_hash_algorithm;
+		public bool pgp_encrypt_to_self;
+		public bool pgp_always_sign;
+		public bool pgp_no_imip_sign;
+		public bool pgp_always_trust;
+		public weak string parent_uid;
+		public weak string smime_sign_key;
+		public weak string smime_encrypt_key;
+		public weak string smime_hash_algorithm;
+		public bool smime_sign_default;
+		public bool smime_encrypt_to_self;
+		public bool smime_encrypt_default;
+	}
+	[CCode (cname = "_EAccountIdentity", type_id = "E_TYPE__ACCOUNTIDENTITY", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct _AccountIdentity {
+		public weak string name;
+		public weak string address;
+		public weak string reply_to;
+		public weak string organization;
+		public weak string sig_uid;
+	}
+	[CCode (cname = "_EAccountList", type_id = "E_TYPE__ACCOUNTLIST", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct _AccountList {
+		public weak E.List parent_object;
+		public E.AccountListPrivate priv;
+	}
+	[CCode (cname = "_EAccountService", type_id = "E_TYPE__ACCOUNTSERVICE", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public struct _AccountService {
+		public weak string url;
+		public bool keep_on_server;
+		public bool auto_check;
+		public int auto_check_time;
+		public bool save_passwd;
+		public bool get_password_canceled;
+	}
+	[CCode (cprefix = "E_ACCOUNT_RECEIPT_", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public enum AccountReceiptPolicy {
+		NEVER,
+		ASK,
+		ALWAYS
+	}
+	[CCode (cprefix = "E_TIME_PARSE_", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public enum TimeParseStatus {
+		OK,
+		NONE,
+		INVALID
+	}
+	[CCode (cprefix = "E_XMLHASH_STATUS_", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public enum XmlHashStatus {
+		SAME,
+		DIFFERENT,
+		NOT_FOUND
+	}
+	[CCode (cprefix = "E_XML_HASH_TYPE_", cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public enum XmlHashType {
+		OBJECT_UID,
+		PROPERTY
+	}
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h", has_target = false)]
+	public delegate void ListFreeFunc (void* data, void* closure);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h", instance_pos = 2.9)]
+	public delegate void XmlHashFunc (string key, string value);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h", instance_pos = 2.9)]
+	public delegate bool XmlHashRemoveFunc (string key, string value);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public const string DEBUG_LOG_DOMAIN_CAL_QUERIES;
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public const string DEBUG_LOG_DOMAIN_GLOG;
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public const string DEBUG_LOG_DOMAIN_USER;
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public const int SOAP_MESSAGE_H;
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void categories_add (string category, string unused, string icon_file, bool searchable);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static bool categories_exist (string category);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static unowned string categories_get_icon_file_for (string category);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static bool categories_is_searchable (string category);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void categories_remove (string category);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void categories_set_icon_file_for (string category, string icon_file);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void debug_log_clear ();
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void debug_log_disable_domains (string domains, int n_domains);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static bool debug_log_dump (string filename) throws GLib.Error;
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static bool debug_log_dump_to_dated_file () throws GLib.Error;
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void debug_log_enable_domains (string domains, int n_domains);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static int debug_log_get_max_lines ();
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static bool debug_log_is_domain_enabled (string domain);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static bool debug_log_load_configuration (string filename) throws GLib.Error;
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void debug_log_set_max_lines (int num_lines);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void filename_make_safe (string string);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static unowned string get_user_cache_dir ();
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static unowned string get_user_config_dir ();
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static unowned string get_user_data_dir ();
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void localtime_with_offset (long tt, void* tm, int offset);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void memchunk_clean (E.MemChunk m);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void memchunk_destroy (E.MemChunk m);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void memchunk_empty (E.MemChunk m);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void memchunk_free (E.MemChunk m, void* mem);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static long mktime_utc (void* tm);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static ulong strftime (string string, ulong max, string fmt, void* tm);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void time_format_date_and_time (void* date_tm, bool use_24_hour_format, bool show_midnight, bool show_zero_seconds, string buffer, int buffer_size);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void time_format_time (void* date_tm, bool use_24_hour_format, bool show_zero_seconds, string buffer, int buffer_size);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static string time_get_d_fmt_with_4digit_year ();
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static E.TimeParseStatus time_parse_date (string value, void* result);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static E.TimeParseStatus time_parse_date_and_time (string value, void* result);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static E.TimeParseStatus time_parse_date_and_time_ex (string value, void* result, bool two_digit_year);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static E.TimeParseStatus time_parse_date_ex (string value, void* result, bool two_digit_year);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static E.TimeParseStatus time_parse_time (string value, void* result);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static string uid_new ();
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static bool url_equal (string url1, string url2);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static string url_shroud (string url);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static ulong utf8_strftime (string string, ulong max, string fmt, void* tm);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static unowned string util_ensure_gdbus_string (string str, string gdbus_str);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static uint64 util_gthread_id (GLib.Thread thread);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static string util_strstrcase (string haystack, string needle);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static string util_utf8_make_valid (string str);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static string util_utf8_remove_accents (string str);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static int util_utf8_strcasecmp (string s1, string s2);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static unowned string util_utf8_strstrcase (string haystack, string needle);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static unowned string util_utf8_strstrcasedecomp (string haystack, string needle);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void xml_destroy_hash (GLib.HashTable<weak void*,weak void*> hash);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static int xml_save_file (string filename, Xml.Doc doc);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void xmlhash_add (E.XmlHash hash, string key, string data);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static E.XmlHashStatus xmlhash_compare (E.XmlHash hash, string key, string compare_data);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void xmlhash_destroy (E.XmlHash hash);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void xmlhash_remove (E.XmlHash hash, string key);
+	[CCode (cheader_filename = "libedataserver/e-account.h,libedataserver/e-source.h")]
+	public static void xmlhash_write (E.XmlHash hash);
+}
diff --git a/configure.ac b/configure.ac
index 5efb4b6..b18b22f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -73,6 +73,8 @@ AC_SUBST([FOLKS_MAJOR_MINOR_VERSION])
 GLIB_REQUIRED=2.24.0
 TP_GLIB_REQUIRED=0.13.1
 VALA_REQUIRED=0.10.0
+EBOOK_REQUIRED=2.30.3
+EDATASERVER_REQUIRED=2.30.3
 
 PKG_CHECK_MODULES([GLIB],
                   [glib-2.0 >= $GLIB_REQUIRED
@@ -84,6 +86,8 @@ PKG_CHECK_MODULES([DBUS_GLIB], [dbus-glib-1])
 # FIXME: We depend on libgee < 0.7 because 0.7 breaks API. bgo#627746
 PKG_CHECK_MODULES([GEE], [gee-1.0 < 0.7])
 PKG_CHECK_MODULES([TP_GLIB], [telepathy-glib >= $TP_GLIB_REQUIRED])
+PKG_CHECK_MODULES([EBOOK], [libebook-1.2 >= $EBOOK_REQUIRED])
+PKG_CHECK_MODULES([EDATASERVER], [libedataserver-1.2 >= $EDATASERVER_REQUIRED])
 
 #
 # Vala building options -- allows tarball builds without installing Vala
@@ -155,7 +159,8 @@ if test "x$enable_vala" = "xyes" ; then
         VALA_CHECK_PACKAGES([telepathy-glib
                              dbus-glib-1
                              gio-2.0
-                             gee-1.0])
+                             gee-1.0
+                             libxml-2.0])
 
         # this will set HAVE_INTROSPECTION
         GOBJECT_INTROSPECTION_REQUIRE([0.9.12])
@@ -182,9 +187,11 @@ 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])
 
 # All of the backend libraries in our tree; to be used by the tests
-BACKEND_UNINST_PATH='$(BACKEND_KF):$(BACKEND_TP)'
+BACKEND_UNINST_PATH='$(BACKEND_KF):$(BACKEND_TP):$(BACKEND_EDS)'
 AC_SUBST([BACKEND_UNINST_PATH])
 
 # -----------------------------------------------------------
@@ -318,6 +325,8 @@ AC_SUBST([ERROR_VALAFLAGS])
 AC_CONFIG_FILES([
     backends/telepathy/lib/folks-telepathy.pc
     backends/telepathy/lib/folks-telepathy-uninstalled.pc
+    backends/eds/lib/folks-eds.pc
+    backends/eds/lib/folks-eds-uninstalled.pc
     folks/folks.pc
     folks/folks-uninstalled.pc
     Makefile
@@ -325,16 +334,22 @@ AC_CONFIG_FILES([
     backends/key-file/Makefile
     backends/telepathy/Makefile
     backends/telepathy/lib/Makefile
+    backends/eds/Makefile
+    backends/eds/lib/Makefile
     folks/Makefile
     docs/Makefile
     po/Makefile.in
     tests/Makefile
+    tests/data/Makefile
+    tests/data/gconf.path
     tests/folks/Makefile
     tests/key-file/Makefile
     tests/telepathy/Makefile
+    tests/eds/Makefile
     tests/lib/Makefile
     tests/lib/folks-test-uninstalled.pc
     tests/lib/key-file/Makefile
+    tests/lib/eds/Makefile
     tests/lib/telepathy/Makefile
     tests/lib/telepathy/contactlist/Makefile
     tests/lib/telepathy/contactlist/session.conf
diff --git a/folks/Makefile.am b/folks/Makefile.am
index afe0fda..ca470cd 100644
--- a/folks/Makefile.am
+++ b/folks/Makefile.am
@@ -15,7 +15,10 @@ libfolks_la_SOURCES = \
 	aliasable.vala \
 	backend.vala \
 	backend-store.vala \
+	emailable.vala \
 	favouritable.vala \
+	field-details.vala \
+	gender-owner.vala \
 	groupable.vala \
 	has-avatar.vala \
 	has-presence.vala \
@@ -23,9 +26,12 @@ libfolks_la_SOURCES = \
 	individual.vala \
 	individual-aggregator.vala \
 	linked-hash-set.vala \
+	name-owner.vala \
 	persona.vala \
 	persona-store.vala \
+	phoneable.vala \
 	types.vala \
+	urlable.vala \
 	debug.vala \
 	$(NULL)
 
diff --git a/folks/emailable.vala b/folks/emailable.vala
new file mode 100644
index 0000000..09db2ad
--- /dev/null
+++ b/folks/emailable.vala
@@ -0,0 +1,37 @@
+/*
+ * 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:
+ *       Marco Barisione <marco.barisione@collabora.co.uk>
+ */
+
+using GLib;
+
+/**
+ * Interface for classes that have email addresses, such as {@link Persona}
+ * and {@link Individual}.
+ *
+ * @since 0.3.UNRELEASED
+ */
+public interface Folks.Emailable : Object
+{
+  /**
+   * The email addresses of the contact.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public abstract List<FieldDetails> email_addresses { get; set; }
+}
diff --git a/folks/field-details.vala b/folks/field-details.vala
new file mode 100644
index 0000000..54a60cf
--- /dev/null
+++ b/folks/field-details.vala
@@ -0,0 +1,164 @@
+/*
+ * 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:
+ *       Marco Barisione <marco.barisione@collabora.co.uk>
+ */
+
+using GLib;
+
+/**
+ * Object representing a string value that can have some parameters
+ * associated with it.
+ *
+ * Some contact details, like phone numbers or URLs, can have some
+ * extra details associated with them.
+ * For instance, a phone number expressed in vcard notation as
+ * `tel;type=work,voice:(111) 555-1234` would be represented as
+ * a FieldDetails with value "(111) 555-1234" and with parameters
+ * `['type': ('work', 'voice')]`.
+ *
+ * @since 0.3.UNRELEASED
+ */
+public class Folks.FieldDetails : Object
+{
+  /* The value of the field.
+   *
+   * The value of the field, the exact content depends on what the structure is
+   * used for.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public string value { get; set; }
+
+  /**
+   * The parameters associated with the value.
+   *
+   * A hash table of the parameters associated with
+   * {@link Folks.FieldDetails.value}. The keys are the names of the
+   * parameters, while the values are a list of strings.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public HashTable<string, List<string>> parameters { get; set; }
+
+  /**
+   * Create a new FieldDetails.
+   *
+   * @param value the value of the field
+   * @return a new FieldDetails
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public FieldDetails (string value)
+    {
+      this.value = value;
+      this.parameters = new HashTable<string, List<string>> (str_hash,
+          str_equal);
+    }
+
+  /**
+   * Get the values for a parameter
+   *
+   * @param parameter_name the parameter name
+   * @return a list of values for `parameter_name` or `null` (i.e. an empty
+   * list) if there are no such parameters.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public unowned List<string> get_parameter_values (string parameter_name)
+    {
+      return this.parameters.lookup (parameter_name);
+    }
+
+  /**
+   * Add a new value for a parameter.
+   *
+   * If there is already a parameter called `parameter_name` then
+   * `parameter_value` is added to the existing ones.
+   *
+   * @param parameter_name the name of the parameter
+   * @param parameter_value the value to add
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public void add_parameter (string parameter_name, string parameter_value)
+    {
+      unowned List<string> values = this.parameters.lookup (parameter_name);
+      if (values == null)
+        {
+          var new_values = new List<string> ();
+          new_values.append (parameter_value);
+          this.parameters.insert (parameter_name, (owned) new_values);
+        }
+      else
+        {
+          if (values.find_custom (parameter_value, strcmp) == null)
+            values.append (parameter_value);
+        }
+    }
+
+  /**
+   * Set the value of a parameter.
+   *
+   * Sets the parameter called `parameter_name` to be `parameter_value`.
+   * If there were already parameters with the same name they are replaced.
+   *
+   * @param parameter_name the name of the parameter
+   * @param parameter_value the value to add
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public void set_parameter (string parameter_name, string parameter_value)
+    {
+      var values = new List<string> ();
+      values.append (parameter_value);
+      this.parameters.insert (parameter_name, (owned) values);
+    }
+
+  /**
+   * Extend the existing parameters.
+   *
+   * Merge the parameters from `additional` into the existing ones.
+   *
+   * @param additional the parameters to add
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public void extend_parameters (HashTable<string, List<string>> additional)
+    {
+      var iter = HashTableIter<string, List<string>> (additional);
+      unowned string name;
+      unowned List<string> values;
+      while (iter.next (out name, out values))
+        {
+          foreach (unowned string val in values)
+            this.add_parameter (name, val);
+        }
+    }
+
+  /**
+   * Remove all instances of a parameter.
+   *
+   * @param parameter_name the name of the parameter
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public void remove_parameter_all (string parameter_name)
+    {
+      this.parameters.remove (parameter_name);
+    }
+}
diff --git a/folks/gender-owner.vala b/folks/gender-owner.vala
new file mode 100644
index 0000000..5d3b25a
--- /dev/null
+++ b/folks/gender-owner.vala
@@ -0,0 +1,58 @@
+/*
+ * 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:
+ *       Marco Barisione <marco.barisione@collabora.co.uk>
+ *       Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+using GLib;
+
+/**
+ * The gender of a contact
+ *
+ * @since 0.3.UNRELEASED
+ */
+public enum Folks.Gender
+{
+  /**
+   * The gender of the contact is unknown or the contact didn't specify it.
+   */
+  UNSPECIFIED,
+  /**
+   * The contact is male.
+   */
+  MALE,
+  /**
+   * The contact is female.
+   */
+  FEMALE
+}
+
+/**
+ * Interface for specifying the gender of a contact.
+ *
+ * @since 0.3.UNRELEASED
+ */
+public interface Folks.GenderOwner : Object
+{
+  /**
+   * The gender of the contact.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public abstract Gender gender { get; set; }
+}
diff --git a/folks/individual.vala b/folks/individual.vala
index 64717b1..ac63c17 100644
--- a/folks/individual.vala
+++ b/folks/individual.vala
@@ -64,11 +64,16 @@ public enum Folks.TrustLevel
  */
 public class Folks.Individual : Object,
     Aliasable,
+    Emailable,
     Favouritable,
+    GenderOwner,
     Groupable,
     HasAvatar,
     HasPresence,
-    IMable
+    IMable,
+    NameOwner,
+    Phoneable,
+    URLable
 {
   private bool _is_favourite;
   private string _alias;
@@ -214,6 +219,84 @@ public class Folks.Individual : Object,
     }
 
   /**
+   * {@inheritDoc}
+   */
+  public StructuredName structured_name { get; private set; }
+
+  /**
+   * {@inheritDoc}
+   */
+  public string full_name { get; private set; }
+
+  private string _nickname;
+  /**
+   * {@inheritDoc}
+   */
+  public string nickname { get { return this._nickname; } }
+
+  private Gender _gender;
+  /**
+   * {@inheritDoc}
+   */
+  public Gender gender
+    {
+      get { return this._gender; }
+      private set
+        {
+          this._gender = value;
+          this.notify_property ("gender");
+        }
+    }
+
+  private GLib.List<FieldDetails> _urls;
+  /**
+   * {@inheritDoc}
+   */
+  public GLib.List<FieldDetails> urls
+    {
+      get { return this._urls; }
+      private set
+        {
+          this._urls = new GLib.List<FieldDetails> ();
+          foreach (unowned FieldDetails ps in value)
+            this._urls.prepend (ps);
+          this._urls.reverse ();
+        }
+    }
+
+  private GLib.List<FieldDetails> _phone_numbers;
+  /**
+   * {@inheritDoc}
+   */
+  public GLib.List<FieldDetails> phone_numbers
+    {
+      get { return this._phone_numbers; }
+      private set
+        {
+          this._phone_numbers = new GLib.List<FieldDetails> ();
+          foreach (unowned FieldDetails fd in value)
+            this._phone_numbers.prepend (fd);
+          this._phone_numbers.reverse ();
+        }
+    }
+
+  private GLib.List<FieldDetails> _email_addresses;
+  /**
+   * {@inheritDoc}
+   */
+  public GLib.List<FieldDetails> email_addresses
+    {
+      get { return this._email_addresses; }
+      private set
+        {
+          this._email_addresses = new GLib.List<FieldDetails> ();
+          foreach (unowned FieldDetails fd in value)
+            this._email_addresses.prepend (fd);
+          this._email_addresses.reverse ();
+        }
+    }
+
+  /**
    * Whether this Individual is a user-defined favourite.
    *
    * This property is `true` if any of this Individual's {@link Persona}s are
@@ -313,11 +396,46 @@ public class Folks.Individual : Object,
       this._update_avatar ();
     }
 
+  private void _notify_full_name_cb ()
+    {
+      this._update_full_name ();
+    }
+
+  private void _notify_structured_name_cb ()
+    {
+      this._update_structured_name ();
+    }
+
+  private void _notify_nickname_cb ()
+    {
+      this._update_nickname ();
+    }
+
   private void _persona_group_changed_cb (string group, bool is_member)
     {
       this._update_groups ();
     }
 
+  private void _notify_gender_cb ()
+    {
+      this._update_gender ();
+    }
+
+  private void _notify_urls_cb ()
+    {
+      this._update_urls ();
+    }
+
+  private void _notify_phone_numbers_cb ()
+    {
+      this._update_phone_numbers ();
+    }
+
+  private void _notify_email_addresses_cb ()
+    {
+      this._update_email_addresses ();
+    }
+
   /**
    * Add or remove the Individual from the specified group.
    *
@@ -375,6 +493,7 @@ public class Folks.Individual : Object,
           new HashTable<string, LinkedHashSet<string>> (str_hash, str_equal);
       this._persona_set = new HashSet<Persona> (null, null);
       this._stores = new HashMap<PersonaStore, uint> (null, null);
+      this._gender = Gender.UNSPECIFIED;
       this.personas = personas;
     }
 
@@ -446,6 +565,13 @@ public class Folks.Individual : Object,
       this._update_alias ();
       this._update_trust_level ();
       this._update_im_addresses ();
+      this._update_structured_name ();
+      this._update_full_name ();
+      this._update_nickname ();
+      this._update_gender ();
+      this._update_urls ();
+      this._update_phone_numbers ();
+      this._update_email_addresses ();
     }
 
   private void _update_groups ()
@@ -718,6 +844,15 @@ public class Folks.Individual : Object,
       persona.notify["presence-type"].connect (this._notify_presence_cb);
       persona.notify["im-addresses"].connect (this._notify_im_addresses_cb);
       persona.notify["is-favourite"].connect (this._notify_is_favourite_cb);
+      persona.notify["structured-name"].connect (
+          this._notify_structured_name_cb);
+      persona.notify["full-name"].connect (this._notify_full_name_cb);
+      persona.notify["nickname"].connect (this._notify_nickname_cb);
+      persona.notify["gender"].connect (this._notify_gender_cb);
+      persona.notify["urls"].connect (this._notify_urls_cb);
+      persona.notify["phone-numbers"].connect (this._notify_phone_numbers_cb);
+      persona.notify["email-addresses"].connect (
+          this._notify_email_addresses_cb);
 
       if (persona is Groupable)
         {
@@ -726,6 +861,66 @@ public class Folks.Individual : Object,
         }
     }
 
+  private void _update_structured_name ()
+    {
+      foreach (var persona in this._persona_list)
+        {
+          var name_owner = persona as NameOwner;
+          if (name_owner != null)
+            {
+              var new_value = name_owner.structured_name;
+              if (new_value != null)
+                {
+                  if (new_value != this.structured_name)
+                    this.structured_name = new_value;
+
+                  break;
+                }
+            }
+        }
+    }
+
+  private void _update_full_name ()
+    {
+      foreach (var persona in this._persona_list)
+        {
+          var name_owner = persona as NameOwner;
+          if (name_owner != null)
+            {
+              var new_value = name_owner.full_name;
+              if (new_value != null)
+                {
+                  if (new_value != this.full_name)
+                    this.full_name = new_value;
+
+                  break;
+                }
+            }
+        }
+    }
+
+  private void _update_nickname ()
+    {
+      foreach (var persona in this._persona_list)
+        {
+          var name_owner = persona as NameOwner;
+          if (name_owner != null)
+            {
+              var new_value = name_owner.nickname;
+              if (new_value != null)
+                {
+                  if (new_value != this._nickname)
+                    {
+                      this._nickname = new_value;
+                      this.notify_property ("nickname");
+                    }
+
+                  break;
+                }
+            }
+        }
+    }
+
   private void _disconnect_from_persona (Persona persona)
     {
       persona.notify["alias"].disconnect (this._notify_alias_cb);
@@ -737,6 +932,16 @@ public class Folks.Individual : Object,
           this._notify_im_addresses_cb);
       persona.notify["is-favourite"].disconnect (
           this._notify_is_favourite_cb);
+      persona.notify["structured-name"].disconnect (
+          this._notify_structured_name_cb);
+      persona.notify["full-name"].disconnect (this._notify_full_name_cb);
+      persona.notify["nickname"].disconnect (this._notify_nickname_cb);
+      persona.notify["gender"].disconnect (this._notify_gender_cb);
+      persona.notify["urls"].disconnect (this._notify_urls_cb);
+      persona.notify["phone-numbers"].disconnect (
+          this._notify_phone_numbers_cb);
+      persona.notify["email-addresses"].disconnect (
+          this._notify_email_addresses_cb);
 
       if (persona is Groupable)
         {
@@ -745,6 +950,106 @@ public class Folks.Individual : Object,
         }
     }
 
+  private void _update_gender ()
+    {
+      foreach (var persona in this._persona_list)
+        {
+          var gender_owner = persona as GenderOwner;
+          if (gender_owner != null)
+            {
+              var new_value = gender_owner.gender;
+              if (new_value != Gender.UNSPECIFIED)
+                {
+                  if (new_value != this.gender)
+                    this.gender = new_value;
+                  break;
+                }
+            }
+        }
+    }
+
+  private void _update_urls ()
+    {
+      /* Populate the URLs as the union of our Personas' URLs.
+       * If the same URL exist multiple times we merge the parameters. */
+      var urls_set = new HashTable<unowned string, unowned FieldDetails> (
+          str_hash, str_equal);
+      foreach (var persona in this._persona_list)
+        {
+          var urlable = persona as URLable;
+          if (urlable != null)
+            {
+              foreach (unowned FieldDetails ps in urlable.urls)
+                {
+                  var existing = urls_set.lookup (ps.value);
+                  if (existing != null)
+                    existing.extend_parameters (ps.parameters);
+                  else
+                    urls_set.insert (ps.value, ps);
+                }
+            }
+        }
+      /* Set the private member directly to avoid iterating this list again */
+      this._urls = urls_set.get_values ();
+
+      this.notify_property ("urls");
+    }
+
+  private void _update_phone_numbers ()
+    {
+      /* Populate the phone numberss as the union of our Personas' numbers
+       * If the same number exist multiple times we merge the parameters. */
+      /* FIXME: We should handle phone numbers better, just string comparison
+         doesn't work. */
+      var phone_numbers_set =
+          new HashTable<unowned string, unowned FieldDetails> (
+              str_hash, str_equal);
+      foreach (var persona in this._persona_list)
+        {
+          var phoneable = persona as Phoneable;
+          if (phoneable != null)
+            {
+              foreach (unowned FieldDetails fd in phoneable.phone_numbers)
+                {
+                  var existing = phone_numbers_set.lookup (fd.value);
+                  if (existing != null)
+                    existing.extend_parameters (fd.parameters);
+                  else
+                    phone_numbers_set.insert (fd.value, fd);
+                }
+            }
+        }
+      this._phone_numbers = phone_numbers_set.get_values ();
+
+      this.notify_property ("phone-numbers");
+    }
+
+  private void _update_email_addresses ()
+    {
+      /* Populate the email addresses as the union of our Personas' addresses.
+       * If the same URL exist multiple times we merge the parameters. */
+      var emails_set = new HashTable<unowned string, unowned FieldDetails> (
+          str_hash, str_equal);
+      foreach (var persona in this._persona_list)
+        {
+          var emailable = persona as Emailable;
+          if (emailable != null)
+            {
+              foreach (unowned FieldDetails fd in emailable.email_addresses)
+                {
+                  var existing = emails_set.lookup (fd.value);
+                  if (existing != null)
+                    existing.extend_parameters (fd.parameters);
+                  else
+                    emails_set.insert (fd.value, fd);
+                }
+            }
+        }
+      this._email_addresses = emails_set.get_values ();
+
+      this.notify_property ("email-addresses");
+    }
+
   private void _set_personas (GLib.List<Persona>? persona_list,
       Individual? replacement_individual)
     {
diff --git a/folks/name-owner.vala b/folks/name-owner.vala
new file mode 100644
index 0000000..8c0314e
--- /dev/null
+++ b/folks/name-owner.vala
@@ -0,0 +1,212 @@
+/*
+ * 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:
+ *       Marco Barisione <marco.barisione@collabora.co.u>
+ */
+
+using GLib;
+
+/**
+ * Object for a full name split in its constituent parts (given name,
+ * family name, etc.). This structure corresponds to the "N" field in
+ * VCards.  The parts of the name are never null, an empty string
+ * indicates that a property is not set.
+ *
+ * @since 0.3.UNRELEASED
+ */
+public class Folks.StructuredName : Object
+{
+  private string _family_name = "";
+  /**
+   * The family name.
+   *
+   * The family name (also known as surname or last name) of a contact.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public string family_name
+    {
+      get { return this._family_name; }
+      construct set { this._family_name = value != null ? value : ""; }
+    }
+
+  private string _given_name = "";
+  /**
+   * The given name.
+   *
+   * The family name (also known as first name) of a contact.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public string given_name
+    {
+      get { return this._given_name; }
+      construct set { this._given_name = value != null ? value : ""; }
+    }
+
+  private string _additional_names = "";
+  /**
+   * Additional names.
+   *
+   * The additional names of a contact, for instance the contact's
+   * middle name.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public string additional_names
+    {
+      get { return this._additional_names; }
+      construct set { this._additional_names = value != null ? value : ""; }
+    }
+
+  private string _prefixes = "";
+  /**
+   * The prefixes of a name.
+   *
+   * The prefixes used in front of the name (for instance "Mr", "Mrs",
+   * "Doctor" or honorific titles).
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public string prefixes
+    {
+      get { return this._prefixes; }
+      construct set { this._prefixes = value != null ? value : ""; }
+    }
+
+  private string _suffixes = "";
+  /**
+   * The suffixes of a name.
+   *
+   * The suffixes used after a name (for instance "PhD" or "Junior").
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public string suffixes
+    {
+      get { return this._suffixes; }
+      construct set { this._suffixes = value != null ? value : ""; }
+    }
+
+  /**
+   * Create a StructuredName.
+   *
+   * You can pass `null` if a component is not set.
+   *
+   * @param family_name the family (last) name
+   * @param given_name the given (first) name
+   * @param additional_names additional names
+   * @param prefixes prefixes of the name
+   * @param suffixes suffixes of the name
+   * @return a new StructuredName
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public StructuredName (string? family_name, string? given_name,
+      string? additional_names, string? prefixes, string? suffixes)
+    {
+      Object (family_name:      family_name,
+              given_name:       given_name,
+              additional_names: additional_names,
+              prefixes:         prefixes,
+              suffixes:         suffixes);
+    }
+
+  /**
+   * Create a StructuredName.
+   *
+   * Shorthand for the common case of just having the family and given
+   * name of a contact. It's equivalent to calling
+   * {@link StructuredName.StructuredName} and passing `null` for all
+   * the other components.
+   *
+   * @param family_name the family (last) name
+   * @param given_name the given (first) name
+   * @return a new StructuredName
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public StructuredName.simple (string? family_name, string? given_name)
+    {
+      Object (family_name: family_name,
+              given_name:  given_name);
+    }
+
+  /**
+   * Whether none of the components is set.
+   *
+   * @return `true` if all the components are the empty string, `false`
+   * otherwise.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public bool is_empty ()
+    {
+      return this._family_name      == "" &&
+             this._given_name       == "" &&
+             this._additional_names == "" &&
+             this._prefixes         == "" &&
+             this._suffixes         == "";
+    }
+}
+
+/**
+ * Interface for classes which represent contacts with names, such as
+ * {@link Persona} and {@link Individual}.
+ *
+ * @since 0.3.UNRELEASED
+ */
+public interface Folks.NameOwner : Object
+{
+  /**
+   * The contact name split in its constituent parts.
+   *
+   * Note that most of the time the structured name is not set (i.e.
+   * it's `null`) or just some of the components are set.
+   * The components are immutable. To get notification of changes of
+   * the structured name, you just have to connect to the `notify` signal
+   * of this property.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public abstract StructuredName structured_name { get; set; }
+
+  /**
+   * The full name of the contact.
+   *
+   * The full name is the name of the contact written in the way the contact
+   * prefers. For instance for English names this is usually the given name
+   * followed by the family name, but Chinese names are usually the family
+   * name followed by the given name.
+   * The full name could or could not contain additional names (like a
+   * middle name), prefixes or suffixes.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public abstract string full_name { get; set; }
+
+  /**
+   * The nickname of the contact.
+   *
+   * The nickname is the name that the contact chose for himself. This is
+   * different from {@link Aliasable.alias} as aliases can be chosen by
+   * the user and not by the contacts themselves.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public abstract string nickname { get; }
+}
diff --git a/folks/persona-store.vala b/folks/persona-store.vala
index 7118cb4..88194ea 100644
--- a/folks/persona-store.vala
+++ b/folks/persona-store.vala
@@ -91,6 +91,13 @@ public errordomain Folks.PersonaStoreError
    * @since 0.3.0
    */
   STORE_OFFLINE,
+
+  /**
+   * The {@link PersonaStore} doesn't support write operations.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  READ_ONLY,
 }
 
 /**
diff --git a/folks/phoneable.vala b/folks/phoneable.vala
new file mode 100644
index 0000000..b4d9cbe
--- /dev/null
+++ b/folks/phoneable.vala
@@ -0,0 +1,39 @@
+/*
+ * 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:
+ *       Marco Barisione <marco.barisione@collabora.co.uk>
+ */
+
+using GLib;
+
+/**
+ * Interface for classes that can provide a phone number, such as
+ * {@link Persona} and {@link Individual}.
+ *
+ * @since 0.3.UNRELEASED
+ */
+public interface Folks.Phoneable : Object
+{
+  /**
+   * The phone numbers of the contact.
+   *
+   * A list of phone numbers associated to the contact.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public abstract List<FieldDetails> phone_numbers { get; set; }
+}
diff --git a/folks/urlable.vala b/folks/urlable.vala
new file mode 100644
index 0000000..cba5c6c
--- /dev/null
+++ b/folks/urlable.vala
@@ -0,0 +1,39 @@
+/*
+ * 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:
+ *       Marco Barisione <marco.barisione@collabora.co.uk>
+ *       Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+using GLib;
+
+/**
+ * Associates a list of URLs with a contact.
+ *
+ * @since 0.3.UNRELEASED
+ */
+public interface Folks.URLable : Object
+{
+  /**
+   * The websites of the contact.
+   *
+   * A list or websites associated to the contact.
+   *
+   * @since 0.3.UNRELEASED
+   */
+  public abstract List<FieldDetails> urls { get; set; }
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index dafbd35..c5065cc 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,9 +1,11 @@
 SUBDIRS = \
+    data \
     lib \
     tools \
     folks \
     key-file \
     telepathy \
+    eds \
     $(NULL)
 
 TESTS_ENVIRONMENT = \
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
new file mode 100644
index 0000000..12dfa6a
--- /dev/null
+++ b/tests/data/Makefile.am
@@ -0,0 +1,13 @@
+clean-gconf:
+	rm -rf gconf.d
+
+clean-local: clean-gconf
+
+check: clean-gconf
+
+GITIGNOREFILES = \
+	gconf.d \
+	gconf.path \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/tests/data/backend-store-all.ini b/tests/data/backend-store-all.ini
index 42e8fb5..6164c17 100644
--- a/tests/data/backend-store-all.ini
+++ b/tests/data/backend-store-all.ini
@@ -3,3 +3,6 @@ enabled=true
 
 [telepathy]
 enabled=true
+
+[eds]
+enabled=true
diff --git a/tests/data/gconf.path.in b/tests/data/gconf.path.in
new file mode 100644
index 0000000..36b92cf
--- /dev/null
+++ b/tests/data/gconf.path.in
@@ -0,0 +1,51 @@
+# These settings set up gconf in a sandbox for our tests for a few reasons:
+#
+#  1. So we don't read or mangle the $USER's gconf settings
+#
+#  2. To have a consistent state every time we run the tests for
+#     repeatability
+#
+# Note that we still source the system settings to hopefully play nicely
+# with unusual (yet valid) set-ups. We can't source /etc/gconf/2/path directly
+# since it will pull in various config paths in $(HOME).
+#
+# This is based on the default /etc/gconf/2/path
+###########################################################################
+
+######################
+# 1. Forced settings #
+######################
+
+# Settings forced by the local administrator
+xml:readonly:/etc/gconf/gconf.xml.mandatory
+
+# Other forced sources imagined by the local administrator
+include /etc/gconf/2/local-mandatory.path
+
+#######################
+# 2. User Preferences #
+#######################
+
+# our in-tree writeable directory for these tests
+xml:readwrite:@abs_top_srcdir@/tests/data/gconf.d
+
+# mandatory path for desktop-profiles
+include $(ENV_MANDATORY_PATH)
+
+# default path for desktop-profiles
+include $(ENV_DEFAULTS_PATH)
+
+######################
+# 3. System defaults #
+######################
+
+# Other default sources imagined by the local administrator
+include /etc/gconf/2/local-defaults.path
+
+# System administrator's defaults. This source also serves as a legacy
+# source for packages not using a recent dh_gconf, or for applications
+# installed by hand.
+xml:readonly:/etc/gconf/gconf.xml.defaults
+
+# Upstream application defaults
+xml:readonly:/var/lib/gconf/defaults
diff --git a/tests/eds/Makefile.am b/tests/eds/Makefile.am
new file mode 100644
index 0000000..e17612d
--- /dev/null
+++ b/tests/eds/Makefile.am
@@ -0,0 +1,100 @@
+AM_CPPFLAGS = \
+	$(GLIB_CFLAGS) \
+	$(GEE_CFLAGS) \
+	$(EBOOK_CFLAGS) \
+	$(EDATASERVER_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) \
+	-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 \
+	--vapidir=$(top_srcdir)/backends/eds/vapi \
+	--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 \
+	$(NULL)
+
+# in order from least to most complex
+noinst_PROGRAMS = \
+	persona-store-tests \
+	individual-retrieval \
+	interfaces \
+	removing-contacts \
+	updating-contacts \
+	$(NULL)
+
+backend_store_key_file=$(srcdir)/data/backend-eds-only.ini
+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) \
+        $(NULL)
+
+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)
+
+interfaces_SOURCES = \
+	interfaces.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 \
+        interfaces_vala.stamp \
+        $(NULL)
+
+EXTRA_DIST = \
+	$(MAINTAINERCLEANFILES) \
+	$(backend_store_key_file) \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/tests/eds/data/backend-eds-only.ini b/tests/eds/data/backend-eds-only.ini
new file mode 100644
index 0000000..848581b
--- /dev/null
+++ b/tests/eds/data/backend-eds-only.ini
@@ -0,0 +1,9 @@
+
+[key-file]
+enabled=false
+
+[telepathy]
+enabled=false
+
+[eds]
+enabled=true
diff --git a/tests/eds/individual-retrieval.vala b/tests/eds/individual-retrieval.vala
new file mode 100644
index 0000000..0447c3b
--- /dev/null
+++ b/tests/eds/individual-retrieval.vala
@@ -0,0 +1,93 @@
+using EdsTest;
+using Folks;
+using Gee;
+
+public class IndividualRetrievalTests : Folks.TestCase
+{
+  private static const string TEST_SINGLETON_INDIVIDUALS_ADDREBOOK =
+    "test_singleton_individuals";
+  private EdsTest.Backend eds_backend;
+
+  public IndividualRetrievalTests ()
+    {
+      base ("IndividualRetrieval");
+
+      this.eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("singleton individuals", this.test_singleton_individuals);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_singleton_individuals ()
+    {
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      Gee.HashMap<string, string> c2 = new Gee.HashMap<string, string> ();
+      HashSet<string> found_individuals = new HashSet<string> (str_hash,
+          str_equal);
+      var main_loop = new GLib.MainLoop (null, false);
+
+      this.eds_backend.reset();
+
+      c1.set("full_name", "bernie h. innocenti");
+      c1.set(Edsf.Persona.email_fields[0], "bernie@example.org");
+      this.eds_backend.add_contact (c1);
+
+      c2.set("full_name", "richard m. stallman");
+      c2.set(Edsf.Persona.email_fields[0], "rms@example.org");
+      this.eds_backend.add_contact (c2);
+
+      this.eds_backend.set_up (this.TEST_SINGLETON_INDIVIDUALS_ADDREBOOK);
+
+      var store = BackendStore.dup ();
+      store.prepare.begin ((o, r) =>
+        {
+          store.prepare.end (r);
+        });
+
+      /* Set up the aggregator */
+      var aggregator = new IndividualAggregator ();
+      aggregator.individuals_changed.connect ((added, removed, m, a, r) =>
+        {
+          foreach (Individual i in added)
+            {
+              assert (i.personas.length () == 1);
+              found_individuals.add (i.personas.data.display_id);
+            }
+
+          assert (removed == null);
+        });
+      aggregator.prepare ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          main_loop.quit ();
+          return false;
+        });
+
+      main_loop.run ();
+
+      /* We should have enumerated exactly the individuals in the set */
+      assert (found_individuals.size == 2);
+
+      this.eds_backend.tear_down ();
+    }
+}
+
+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/interfaces.vala b/tests/eds/interfaces.vala
new file mode 100644
index 0000000..1faef51
--- /dev/null
+++ b/tests/eds/interfaces.vala
@@ -0,0 +1,342 @@
+using EdsTest;
+using Folks;
+using Gee;
+
+public class InterfacesTests : Folks.TestCase
+{
+  private static const string TEST_PHONE_NUMBERS_ADDRESSBOOK =
+  "test_phone_numbers";
+  private static const string TEST_EMAIL_OWNER_ADDRESSBOOK =
+  "test_emailable";
+  private static const string TEST_NAMES_ADDRESSBOOK =
+  "test_names";
+  private EdsTest.Backend eds_backend;
+
+  public InterfacesTests ()
+    {
+      base ("Interfaces");
+
+      this.eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("phone numbers interface", this.test_phone_numbers);
+      this.add_test ("email owner interface", this.test_emailable);
+      this.add_test ("names interface", this.test_names);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_phone_numbers ()
+    {
+      var phones_count = 0;
+      HashSet<string> phone_types = new HashSet<string> (str_hash,
+                                                         str_equal);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      Gee.HashMap<string, string> c2 = new Gee.HashMap<string, string> ();
+      var main_loop = new GLib.MainLoop (null, false);
+
+      this.eds_backend.reset();
+
+      c1.set ("full_name", "bernie h. innocenti");
+      c1.set ("car_phone", "123");
+      c1.set ("company_phone", "1234");
+      c1.set ("home_phone", "12345");
+      this.eds_backend.add_contact (c1);
+
+      c2.set ("full_name", "richard m. stallman");
+      c2.set ("car_phone", "54321");
+      c2.set ("company_phone", "4321");
+      c2.set ("home_phone", "321");
+      this.eds_backend.add_contact (c2);
+
+      this.eds_backend.set_up (this.TEST_PHONE_NUMBERS_ADDRESSBOOK);
+
+      var store = BackendStore.dup ();
+      store.prepare.begin ((o, r) =>
+        {
+          store.prepare.end (r);
+        });
+
+      var aggregator = new IndividualAggregator ();
+      aggregator.individuals_changed.connect ((added, removed, m, a, r) =>
+        {
+          foreach (Individual i in added)
+            {
+              unowned Gee.HashMap<string, string> contact = null;
+              assert (i.personas.length () == 1);
+
+              if (i.full_name == "bernie h. innocenti")
+                {
+                  contact = c1;
+                }
+              else if (i.full_name == "richard m. stallman")
+                {
+                  contact = c2;
+                }
+
+              if (contact == null)
+                {
+                  continue;
+                }
+
+              contact.unset ("full_name");
+              var phone_numbers = (Folks.Phoneable) i;
+              foreach (var p in phone_numbers.phone_numbers)
+                {
+                  phones_count++;
+                  foreach (var v in p.get_parameter_values("type"))
+                    {
+                      phone_types.add (v);
+                      assert (contact.get (v) == p.value);
+                      contact.unset (v);
+                    }
+                }
+            }
+
+          assert (removed == null);
+        });
+      aggregator.prepare ();
+
+      Timeout.add_seconds (5, () =>
+        {
+          main_loop.quit ();
+          return false;
+        });
+
+      main_loop.run ();
+
+      assert (phones_count == 6);
+      assert (phone_types.size == 3);
+      assert (c1.size == 0);
+      assert (c2.size == 0);
+
+      foreach (var pt in phone_types)
+        {
+          assert(pt in Edsf.Persona.phone_fields);
+        }
+
+      this.eds_backend.tear_down ();
+    }
+
+  public void test_emailable ()
+    {
+      var email_count = 0;
+      HashSet<string> email_types = new HashSet<string> (str_hash,
+                                                         str_equal);
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      Gee.HashMap<string, string> c2 = new Gee.HashMap<string, string> ();
+      Gee.HashMap<string, string> c3 = new Gee.HashMap<string, string> ();
+      var main_loop = new GLib.MainLoop (null, false);
+
+      this.eds_backend.reset ();
+
+      c1.set ("full_name", "bernie h. innocenti");
+      c1.set (Edsf.Persona.email_fields[0], "bernie@example.org");
+      c1.set (Edsf.Persona.email_fields[1], "bernie.innocenti@example.org");
+      this.eds_backend.add_contact (c1);
+
+      c2.set ("full_name", "richard m. stallman");
+      c2.set (Edsf.Persona.email_fields[0], "rms@example.org");
+      c2.set (Edsf.Persona.email_fields[1], "rms.1@example.org");
+      this.eds_backend.add_contact ((owned) c2);
+
+      c3.set ("full_name", "foo bar");
+      c3.set (Edsf.Persona.email_fields[0], "foo@example.org");
+      c3.set (Edsf.Persona.email_fields[1], "foo.bar@example.org");
+      this.eds_backend.add_contact ((owned) c3);
+
+      this.eds_backend.set_up (this.TEST_EMAIL_OWNER_ADDRESSBOOK);
+
+      var store = BackendStore.dup ();
+      store.prepare.begin ((o, r) =>
+          {
+            store.prepare.end (r);
+          });
+
+      var aggregator = new IndividualAggregator ();
+      aggregator.individuals_changed.connect ((added, removed, m, a, r) =>
+          {
+            foreach (Individual i in added)
+              {
+                unowned Gee.HashMap<string, string> contact = null;
+                assert (i.personas.length () == 1);
+
+                if (i.full_name == "bernie h. innocenti")
+                  {
+                    contact = c1;
+                  }
+                else if (i.full_name == "richard m. stallman")
+                  {
+                    contact = c2;
+                  }
+                else if (i.full_name == "foo bar")
+                  {
+                    contact = c3;
+                  }
+
+                if (contact == null)
+                  {
+                    continue;
+                  }
+
+                contact.unset ("full_name");
+                var emailable = (Folks.Emailable) i;
+                foreach (var e in emailable.email_addresses)
+                  {
+                    email_count++;
+                    foreach (var v in e.get_parameter_values("type"))
+                      {
+                        email_types.add (v);
+                        assert (contact.get (v) == e.value);
+                        contact.unset (v);
+                      }
+                  }
+              }
+
+            assert (removed == null);
+          });
+      aggregator.prepare ();
+
+      Timeout.add_seconds (5, () =>
+          {
+            main_loop.quit ();
+            return false;
+          });
+
+      main_loop.run ();
+
+      assert (email_count == 6);
+      assert (email_types.size == 2);
+      assert (c1.size == 0);
+      assert (c2.size == 0);
+      assert (c3.size == 0);
+
+      foreach (var pt in email_types)
+        {
+          assert (pt in Edsf.Persona.email_fields);
+        }
+
+      this.eds_backend.tear_down ();
+    }
+
+  public void test_names ()
+    {
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      Gee.HashMap<string, string> c2 = new Gee.HashMap<string, string> ();
+      var names_count = 0;
+      var main_loop = new GLib.MainLoop (null, false);
+
+      this.eds_backend.reset ();
+
+      c1.set ("full_name", "bernie h. innocenti");
+      c1.set ("contact_name_family", "Innocenti");
+      c1.set ("contact_name_given", "Bernardo");
+      c1.set ("contact_name_additional", "H.");
+      c1.set ("contact_name_prefixes", "Mr.");
+      c1.set ("contact_name_suffixes", "(sysadmin FSF)");
+      this.eds_backend.add_contact (c1);
+
+      c2.set ("full_name", "richard m. stallman");
+      c2.set ("contact_name_family", "Stallman");
+      c2.set ("contact_name_given", "Richard M.");
+      this.eds_backend.add_contact ((owned) c2);
+
+      this.eds_backend.set_up (this.TEST_NAMES_ADDRESSBOOK);
+
+      var store = BackendStore.dup ();
+      store.prepare.begin ((o, r) =>
+          {
+            store.prepare.end (r);
+          });
+
+      var aggregator = new IndividualAggregator ();
+      aggregator.individuals_changed.connect ((added, removed, m, a, r) =>
+          {
+            foreach (Individual i in added)
+              {
+                assert (i.personas.length () == 1);
+
+                var name = (Folks.NameOwner) i;
+
+                if (i.full_name == "bernie h. innocenti")
+                  {
+                    assert (name.structured_name.is_empty () == false);
+
+                    assert (name.full_name == c1.get ("full_name"));
+                    c1.unset (c1.get ("full_name"));
+
+                    assert (name.structured_name.family_name ==
+                            c1.get ("contact_name_family"));
+                    c1.unset ("contact_name_family");
+
+                    assert (name.structured_name.given_name ==
+                            c1.get ("contact_name_given"));
+                    c1.unset ("contact_name_given");
+
+                    assert (name.structured_name.additional_names ==
+                            c1.get ("contact_name_additional"));
+                    c1.unset ("contact_name_additional");
+
+                    assert (name.structured_name.prefixes ==
+                            c1.get ("contact_name_prefixes"));
+                    c1.unset ("contact_name_prefixes");
+
+                    assert (name.structured_name.suffixes ==
+                            c1.get ("contact_name_suffixes"));
+                    c1.unset ("contact_name_suffixes");
+                  }
+                else if (i.full_name == "richard m. stallman")
+                  {
+                    assert (name.structured_name.is_empty () == false);
+
+                    assert (name.full_name == c1.get ("full_name"));
+                    c1.unset (c1.get ("full_name"));
+
+                    assert (name.structured_name.family_name ==
+                            c1.get ("contact_name_family"));
+                    c1.unset ("contact_name_family");
+
+                    assert (name.structured_name.given_name ==
+                            c1.get ("contact_name_given"));
+                    c1.unset ("contact_name_given");
+                  }
+
+                names_count++;
+              }
+
+            assert (removed == null);
+          });
+      aggregator.prepare ();
+
+      Timeout.add_seconds (5, () =>
+          {
+            main_loop.quit ();
+            return false;
+          });
+
+      main_loop.run ();
+
+      assert (names_count > 0);
+      assert (c1.size == 0);
+      assert (c2.size == 0);
+
+      this.eds_backend.tear_down ();
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  TestSuite root = TestSuite.get_root ();
+  root.add_suite (new InterfacesTests ().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..e94fa0d
--- /dev/null
+++ b/tests/eds/persona-store-tests.vala
@@ -0,0 +1,190 @@
+using EdsTest;
+using Folks;
+using Gee;
+
+public class PersonaStoreTests : Folks.TestCase
+{
+  private static const string TEST_PERSONA_STORE_ADDRESSBOOK =
+  "test_persona_store";
+  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);
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  public void test_persona_store ()
+    {
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      Gee.HashMap<string, string> c2 = new Gee.HashMap<string, string> ();
+      var main_loop = new GLib.MainLoop (null, false);
+
+      this.eds_backend.reset ();
+
+      c1.set ("full_name", "bernie h. innocenti");
+      c1.set (Edsf.Persona.email_fields[0], "bernie@example.org");
+      this.eds_backend.add_contact (c1);
+
+      c2.set ("full_name", "richard m. stallman");
+      c2.set (Edsf.Persona.email_fields[0], "rms@example.org");
+      this.eds_backend.add_contact (c2);
+
+      this.eds_backend.set_up (this.TEST_PERSONA_STORE_ADDRESSBOOK);
+
+      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.get_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.FALSE);
+
+          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.FALSE);
+
+          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/removing-contacts.vala b/tests/eds/removing-contacts.vala
new file mode 100644
index 0000000..370da9d
--- /dev/null
+++ b/tests/eds/removing-contacts.vala
@@ -0,0 +1,99 @@
+using EdsTest;
+using Folks;
+using Gee;
+
+public class RemovingContactsTests : Folks.TestCase
+{
+  private static const string TEST_REMOVAL_ADDRESSBOOK =
+  "test_removal";
+  private EdsTest.Backend eds_backend;
+  private IndividualAggregator aggregator;
+
+  public RemovingContactsTests ()
+    {
+      base ("IndividualRetrieval");
+
+      this.eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("removing contact", this.test_removal);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  void test_removal ()
+    {
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      var main_loop = new GLib.MainLoop (null, false);
+
+      this.eds_backend.reset ();
+
+      c1.set ("full_name", "bernie h. innocenti");
+      this.eds_backend.add_contact (c1);
+
+      this.eds_backend.set_up (this.TEST_REMOVAL_ADDRESSBOOK);
+
+      var store = BackendStore.dup ();
+      store.prepare.begin ((o, r) =>
+          {
+            store.prepare.end (r);
+          });
+
+      this.aggregator = new IndividualAggregator ();
+      this.aggregator.individuals_changed.connect ((added, removed, m, a, r) =>
+        {
+          if (added != null)
+            {
+              foreach (Individual i in added)
+                {
+                  var name = (Folks.NameOwner) i;
+                  assert (name.full_name == "bernie h. innocenti");
+                }
+            }
+
+          if (removed != null)
+            {
+              foreach (Individual i in removed)
+                {
+                  var name = (Folks.NameOwner) i;
+                  assert (name.full_name == "bernie h. innocenti");
+                }
+            }
+        });
+      this.aggregator.prepare ();
+
+      Timeout.add_seconds (5, () =>
+          {
+            Gee.HashMap<string, string> updated_data = new Gee.HashMap<string, string> ();
+            updated_data.set ("full_name", "bernie innocenti");
+            this.eds_backend.remove_contact (0);
+            Timeout.add_seconds (5, () =>
+                {
+                  main_loop.quit ();
+                  return false;
+                });
+            return false;
+          });
+
+      main_loop.run ();
+
+      this.eds_backend.tear_down ();
+    }
+}
+
+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/updating-contacts.vala b/tests/eds/updating-contacts.vala
new file mode 100644
index 0000000..c9c2453
--- /dev/null
+++ b/tests/eds/updating-contacts.vala
@@ -0,0 +1,96 @@
+using EdsTest;
+using Folks;
+using Gee;
+
+public class UpdatingContactsTests : Folks.TestCase
+{
+  private static const string TEST_UPDATES_ADDRESSBOOK =
+  "test_updates";
+  private EdsTest.Backend eds_backend;
+  private IndividualAggregator aggregator;
+
+  public UpdatingContactsTests ()
+    {
+      base ("UpdatingContacts");
+
+      this.eds_backend = new EdsTest.Backend ();
+
+      this.add_test ("updating contact", this.test_updates);
+    }
+
+  public override void set_up ()
+    {
+    }
+
+  public override void tear_down ()
+    {
+    }
+
+  void test_updates ()
+    {
+      Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
+      var main_loop = new GLib.MainLoop (null, false);
+
+      this.eds_backend.reset ();
+
+      c1.set ("full_name", "bernie h. innocenti");
+      this.eds_backend.add_contact (c1);
+
+      this.eds_backend.set_up (this.TEST_UPDATES_ADDRESSBOOK);
+
+      var store = BackendStore.dup ();
+      store.prepare.begin ((o, r) =>
+          {
+            store.prepare.end (r);
+          });
+
+      this.aggregator = new IndividualAggregator ();
+      this.aggregator.individuals_changed.connect ((added, removed, m, a, r) =>
+        {
+          foreach (Individual i in added)
+            {
+              var name = (Folks.NameOwner) i;
+              assert (name.full_name == "bernie h. innocenti");
+              i.notify["full-name"].connect (this.notify_full_name_cb);
+            }
+          assert (removed == null);
+        });
+      this.aggregator.prepare ();
+
+      Timeout.add_seconds (5, () =>
+          {
+            Gee.HashMap<string, string> updated_data = new Gee.HashMap<string, string> ();
+            updated_data.set("full_name", "bernie innocenti");
+            this.eds_backend.update_contact (0, updated_data);
+            Timeout.add_seconds (5, () =>
+                {
+                  main_loop.quit ();
+                  return false;
+                });
+            return false;
+          });
+
+      main_loop.run ();
+
+      this.eds_backend.tear_down ();
+    }
+
+  private void notify_full_name_cb ()
+    {
+      var i = this.aggregator.individuals.lookup (this.aggregator.individuals.get_keys ().nth_data (0));
+      var name = (Folks.NameOwner) i;
+      assert (name.full_name == "bernie innocenti");
+    }
+}
+
+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/folks/Makefile.am b/tests/folks/Makefile.am
index dd96a8d..1e2908e 100644
--- a/tests/folks/Makefile.am
+++ b/tests/folks/Makefile.am
@@ -46,6 +46,7 @@ noinst_PROGRAMS = \
 SESSION_CONF = $(top_builddir)/tests/lib/telepathy/contactlist/session.conf
 backend_store_key_file=$(top_srcdir)/tests/data/backend-store-all.ini
 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) \
 	$(RUN_WITH_PRIVATE_BUS) \
diff --git a/tests/folks/backend-loading.vala b/tests/folks/backend-loading.vala
index 23a3f29..6576396 100644
--- a/tests/folks/backend-loading.vala
+++ b/tests/folks/backend-loading.vala
@@ -54,6 +54,7 @@ public class BackendLoadingTests : Folks.TestCase
       var store = BackendStore.dup ();
       store.prepare.begin ((o, r) =>
         {
+          store.disable_backend("eds");
           store.prepare.end (r);
         });
 
@@ -72,6 +73,7 @@ public class BackendLoadingTests : Folks.TestCase
                 });
 
               assert (backends_expected.size == 0);
+              store.enable_backend("eds");
               main_loop.quit ();
             }
           catch (GLib.Error e)
@@ -100,8 +102,9 @@ public class BackendLoadingTests : Folks.TestCase
     {
       var backends_expected = new HashSet<string> (str_hash, str_equal);
       backends_expected.add ("key-file");
+      backends_expected.add ("eds");
 
-      /* Disable some backends */
+      /* Disable a backend */
       yield store.prepare ();
       yield store.disable_backend ("telepathy");
 
@@ -148,6 +151,7 @@ public class BackendLoadingTests : Folks.TestCase
       backends_expected = new HashSet<string> (str_hash, str_equal);
       backends_expected.add ("key-file");
       backends_expected.add ("telepathy");
+      backends_expected.add ("eds");
 
       try
         {
@@ -172,6 +176,7 @@ public class BackendLoadingTests : Folks.TestCase
        */
       backends_expected = new HashSet<string> (str_hash, str_equal);
       backends_expected.add ("telepathy");
+      backends_expected.add ("eds");
 
       /* Disable some backends */
       yield store.disable_backend ("key-file");
@@ -201,6 +206,7 @@ public class BackendLoadingTests : Folks.TestCase
       backends_expected = new HashSet<string> (str_hash, str_equal);
       backends_expected.add ("key-file");
       backends_expected.add ("telepathy");
+      backends_expected.add ("eds");
 
       /* Re-enable some backends */
       yield store.enable_backend ("key-file");
@@ -231,6 +237,7 @@ public class BackendLoadingTests : Folks.TestCase
       backends_expected = new HashSet<string> (str_hash, str_equal);
       backends_expected.add ("key-file");
       backends_expected.add ("telepathy");
+      backends_expected.add ("eds");
 
       /* this time we should get them all */
       try
diff --git a/tests/key-file/data/backend-store-key-file-only.ini b/tests/key-file/data/backend-store-key-file-only.ini
index 3ca88dc..4f82ebf 100644
--- a/tests/key-file/data/backend-store-key-file-only.ini
+++ b/tests/key-file/data/backend-store-key-file-only.ini
@@ -1,5 +1,9 @@
+
 [key-file]
 enabled=true
 
 [telepathy]
 enabled=false
+
+[eds]
+enabled=false
diff --git a/tests/key-file/individual-retrieval.vala b/tests/key-file/individual-retrieval.vala
index 531f636..d581006 100644
--- a/tests/key-file/individual-retrieval.vala
+++ b/tests/key-file/individual-retrieval.vala
@@ -45,6 +45,14 @@ public class IndividualRetrievalTests : Folks.TestCase
       expected_individuals.add ("0");
       expected_individuals.add ("1");
 
+      var store = BackendStore.dup ();
+      store.prepare.begin ((o, r) =>
+        {
+          store.disable_backend("eds");
+          store.disable_backend("telepathy");
+          store.prepare.end (r);
+        });
+
       /* Set up the aggregator */
       var aggregator = new IndividualAggregator ();
       aggregator.individuals_changed.connect ((added, removed, m, a, r) =>
@@ -87,6 +95,14 @@ public class IndividualRetrievalTests : Folks.TestCase
           "__alias=Brian Briansson\n" +
           "msn=foo@hotmail.com\n");
 
+      var store = BackendStore.dup ();
+      store.prepare.begin ((o, r) =>
+        {
+          store.disable_backend("eds");
+          store.disable_backend("telepathy");
+          store.prepare.end (r);
+        });
+
       /* Set up the aggregator */
       var aggregator = new IndividualAggregator ();
       uint individuals_changed_count = 0;
diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am
index 8f633b7..b7f02b4 100644
--- a/tests/lib/Makefile.am
+++ b/tests/lib/Makefile.am
@@ -1,6 +1,7 @@
 SUBDIRS = \
 	key-file \
 	telepathy \
+	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..a6e4a35
--- /dev/null
+++ b/tests/lib/eds/Makefile.am
@@ -0,0 +1,54 @@
+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 = \
+	$(GLIB_LIBS) \
+	$(GEE_LIBS) \
+	$(top_builddir)/folks/libfolks.la \
+	$(top_builddir)/backends/eds/libfolks-backend-eds.la \
+	$(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..ed2d263
--- /dev/null
+++ b/tests/lib/eds/backend.vala
@@ -0,0 +1,222 @@
+using E;
+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.Book _addressbook;
+  private GLib.List<E.Contact> _e_contacts;
+  private GLib.List<Gee.HashMap<string, string>> _contacts;
+
+  public Backend ()
+    {
+      this._contacts = new GLib.List<Gee.HashMap<string, string>> ();
+      this._e_contacts = new GLib.List<E.Contact> ();
+   }
+
+  public void add_contact (owned Gee.HashMap<string, string> c)
+    {
+      this._contacts.prepend (c);
+    }
+
+  public void update_contact (int contact_pos,
+                              owned Gee.HashMap<string, string> updated_data)
+    {
+      E.Contact contact = this._e_contacts.nth_data (contact_pos);
+      this._set_contact_fields (contact, updated_data);
+      try
+        {
+          this._addressbook.commit_contact (contact);
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Couldn't update contact\n");
+        }
+    }
+
+  public void remove_contact (int contact_pos)
+    {
+      E.Contact contact = this._e_contacts.nth_data (contact_pos);
+      try
+        {
+          this._addressbook.remove_contact
+            ((string) contact.get (E.Contact.field_id("id")));
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Couldn't remove contact\n");
+        }
+    }
+
+  public void reset ()
+    {
+      this._contacts = new GLib.List<Gee.HashMap<string, string>> ();
+      this._e_contacts = new GLib.List<E.Contact> ();
+    }
+
+  /* Create a temporary addressbook */
+  public void set_up (string addressbook_name)
+    {
+      this._addressbook_name = addressbook_name;
+
+      try
+        {
+          this._prepare_addressbook ();
+          this._add_contacts_to_addressbook ();
+          Environment.set_variable ("FOLKS_BACKEND_EDS_USE_ADDRESSBOOKS",
+                                    this._addressbook_name, true);
+        }
+      catch (BackendSetupError e)
+        {
+          GLib.warning ("unable to create test data: %s\n", e.message);
+        }
+    }
+
+  private void _prepare_addressbook () throws BackendSetupError
+    {
+      E.SourceList ab_sources;
+      E.SourceGroup source_group = null;
+      unowned GLib.SList<weak E.SourceGroup> groups;
+
+      try
+        {
+          E.Book.get_addressbooks (out ab_sources);
+          groups = ab_sources.peek_groups ();
+
+          foreach (var g in groups)
+            {
+              if (g.peek_base_uri ().slice (0, 7) == "file://")
+                {
+                  source_group = g;
+                  break;
+                }
+            }
+        }
+      catch (GLib.Error e)
+        {
+          throw new BackendSetupError.FETCH_SOURCE_GROUP_FAILED
+            ("couldn't find the source group: %s\n", e.message);
+        }
+
+      if (source_group == null)
+        {
+          throw new BackendSetupError.FETCH_SOURCE_GROUP_FAILED
+            ("couldn't find the source group\n");
+        }
+
+      E.Source s = new E.Source (this._addressbook_name, this._addressbook_name);
+      if (source_group.add_source (s, -1) == false)
+        {
+          throw new BackendSetupError.ADD_TO_SOURCE_GROUP_FAILED
+            ("couldn't add source to source group\n");
+        }
+      s.set_property ("completion", "true");
+      try
+        {
+          this._addressbook = new E.Book (s);
+          this._addressbook.open (false);
+        }
+      catch (GLib.Error e)
+        {
+          throw new BackendSetupError.OPENING_FAILED
+            ("couldn't open addressbook %s : %s\n", this._addressbook_name,
+             e.message);
+        }
+    }
+
+  private void _add_contacts_to_addressbook () throws BackendSetupError
+    {
+      this._contacts.reverse ();
+      foreach (var c in this._contacts)
+        {
+          E.Contact contact = new E.Contact ();
+
+          this._set_contact_fields (contact, c);
+
+          try
+            {
+              this._addressbook.add_contact (contact);
+              this._e_contacts.prepend ((owned) contact);
+            }
+          catch (GLib.Error e)
+            {
+              throw new BackendSetupError.ADD_CONTACT_FAILED
+                ("couldn't add contact %s : %s\n", e.message);
+            }
+        }
+        this._e_contacts.reverse ();
+    }
+
+  private void _set_contact_fields (E.Contact contact,
+                                    Gee.HashMap<string, string> 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)
+            {
+              if (k.str("family") != null)
+                {
+                  contact_name.family = c.get (k);
+                }
+              else if (k.str("given") != null)
+                {
+                  contact_name.given = c.get (k);
+                }
+              else if (k.str("additional") != null)
+                {
+                  contact_name.additional = c.get (k);
+                }
+              else if (k.str("prefixes") != null)
+                {
+                  contact_name.prefixes = c.get (k);
+                }
+              else if (k.str("suffixes") != null)
+                {
+                  contact_name.suffixes = c.get (k);
+                }
+
+              added_contact_name = true;
+            }
+          else
+            {
+              contact.set (E.Contact.field_id (k), c.get (k));
+            }
+        }
+      if (added_contact_name)
+        {
+          contact.set (E.Contact.field_id ("name"), contact_name);
+        }
+    }
+
+  /* Remove the addressbook */
+  public void tear_down ()
+    {
+      try
+        {
+          if (this._addressbook.remove () == false)
+            {
+              GLib.warning ("remove() addressbook returned false on %s\n",
+                                  this._addressbook_name);
+            }
+          Environment.set_variable ("FOLKS_BACKEND_EDS_USE_ADDRESSBOOKS", "", true);
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("unable to remove addressbook %s because: %s\n",
+                              this._addressbook_name, e.message);
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/lib/test-case.vala b/tests/lib/test-case.vala
index ace4c8d..f2a8b5a 100644
--- a/tests/lib/test-case.vala
+++ b/tests/lib/test-case.vala
@@ -75,8 +75,10 @@ public abstract class Folks.TestCase : Object {
 		}
 
 		private void _printerr_func_stack_trace (string? text) {
-			if (text != null)
-				stderr.printf (text);
+			if (text == null || str_equal (text, ""))
+				return;
+
+			stderr.printf (text);
 
 			/* Print a stack trace since we've hit some major issue */
 			GLib.on_error_stack_trace ("libtool --mode=execute gdb");
diff --git a/tests/telepathy/data/backend-store-telepathy-only.ini b/tests/telepathy/data/backend-store-telepathy-only.ini
index 1a47b83..833a2c5 100644
--- a/tests/telepathy/data/backend-store-telepathy-only.ini
+++ b/tests/telepathy/data/backend-store-telepathy-only.ini
@@ -1,3 +1,6 @@
+[eds]
+enabled=false
+
 [key-file]
 enabled=false
 
