[folks/648811-dummy-backend-non-dbus-rebase1: 1/2] dummy: Add a UNFINISHED dummy backend
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [folks/648811-dummy-backend-non-dbus-rebase1: 1/2] dummy: Add a UNFINISHED dummy backend
- Date: Sun, 27 Oct 2013 18:22:18 +0000 (UTC)
commit 64f9569d932e78558dd8a45d51d5ea2cfa2024ab
Author: Philip Withnall <philip tecnocode co uk>
Date: Wed Jan 2 15:22:12 2013 +0000
dummy: Add a UNFINISHED dummy backend
backends/Makefile.am | 1 +
backends/dummy/Makefile.am | 74 +++
backends/dummy/backend.mk | 1 +
backends/dummy/dummy-backend-factory.vala | 47 ++
backends/dummy/lib/Makefile.am | 110 ++++
backends/dummy/lib/dummy-backend.vala | 318 ++++++++++
backends/dummy/lib/dummy-fat-persona.vala | 916 +++++++++++++++++++++++++++
backends/dummy/lib/dummy-persona-store.vala | 639 +++++++++++++++++++
backends/dummy/lib/dummy-persona.vala | 231 +++++++
backends/dummy/lib/folks-dummy.deps | 4 +
configure.ac | 2 +
11 files changed, 2343 insertions(+), 0 deletions(-)
---
diff --git a/backends/Makefile.am b/backends/Makefile.am
index fd51853..1a3d1c4 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -1,4 +1,5 @@
SUBDIRS = \
+ dummy \
key-file \
$(NULL)
diff --git a/backends/dummy/Makefile.am b/backends/dummy/Makefile.am
new file mode 100644
index 0000000..c5a4dcd
--- /dev/null
+++ b/backends/dummy/Makefile.am
@@ -0,0 +1,74 @@
+SUBDIRS = lib
+
+BACKEND_NAME = "dummy"
+
+VALAFLAGS += \
+ $(AM_VALAFLAGS) \
+ $(ERROR_VALAFLAGS) \
+ --vapidir=. \
+ --vapidir=$(top_builddir)/backends/dummy/lib \
+ --vapidir=$(top_srcdir)/folks \
+ $(addprefix --pkg ,$(dummy_deps)) \
+ $(NULL)
+
+backenddir = $(BACKEND_DIR)/dummy
+backend_LTLIBRARIES = dummy.la
+
+dummy_la_SOURCES = \
+ dummy-backend-factory.vala \
+ $(NULL)
+
+dummy_deps = \
+ folks \
+ folks-internal \
+ folks-dummy \
+ gee-1.0 \
+ gio-2.0 \
+ gobject-2.0 \
+ $(NULL)
+
+dummy_la_CPPFLAGS = \
+ -I$(top_srcdir)/folks \
+ -I$(top_srcdir)/backends/dummy/lib \
+ -I$(top_srcdir)/backends/dummy/lib/folks \
+ -include $(CONFIG_HEADER) \
+ -DPACKAGE_DATADIR=\"$(pkgdatadir)\" \
+ -DBACKEND_NAME=\"$(BACKEND_NAME)\" \
+ -DG_LOG_DOMAIN=\"$(BACKEND_NAME)\" \
+ $(NULL)
+
+dummy_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(CODE_COVERAGE_CFLAGS) \
+ $(GIO_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(GEE_CFLAGS) \
+ $(NULL)
+
+dummy_la_LIBADD = \
+ $(AM_LIBADD) \
+ $(top_builddir)/backends/dummy/lib/libfolks-dummy.la \
+ $(top_builddir)/folks/libfolks.la \
+ $(top_builddir)/folks/libfolks-internal.la \
+ $(GIO_LIBS) \
+ $(GLIB_LIBS) \
+ $(GEE_LIBS) \
+ $(NULL)
+
+dummy_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ $(CODE_COVERAGE_LDFLAGS) \
+ -shared \
+ -fPIC \
+ -module \
+ -avoid-version \
+ $(NULL)
+
+GITIGNOREFILES = \
+ folks-backend-dummy.vapi \
+ $(dummy_la_SOURCES:.vala=.c) \
+ dummy_la_vala.stamp \
+ $(NULL)
+
+-include backend.mk
+-include $(top_srcdir)/git.mk
diff --git a/backends/dummy/backend.mk b/backends/dummy/backend.mk
new file mode 100644
index 0000000..df1091c
--- /dev/null
+++ b/backends/dummy/backend.mk
@@ -0,0 +1 @@
+BACKEND_NAME = dummy
diff --git a/backends/dummy/dummy-backend-factory.vala b/backends/dummy/dummy-backend-factory.vala
new file mode 100644
index 0000000..9b7a6f6
--- /dev/null
+++ b/backends/dummy/dummy-backend-factory.vala
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2009 Zeeshan Ali (Khattak) <zeeshanak gnome org>.
+ * Copyright (C) 2009 Nokia Corporation.
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ * Travis Reitter <travis reitter collabora co uk>
+ * Marco Barisione <marco barisione collabora co uk>
+ * Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
+ *
+ * This file was originally part of Rygel.
+ */
+
+using Folks;
+
+/**
+ * The dummy backend module entry point.
+ *
+ * @since UNRELEASED
+ */
+public void module_init (BackendStore backend_store)
+{
+ backend_store.add_backend (new Dummyf.Backend ());
+}
+
+/**
+ * The dummy backend module exit point.
+ *
+ * @since UNRELEASED
+ */
+public void module_finalize (BackendStore backend_store)
+{
+ /* TODO */
+}
diff --git a/backends/dummy/lib/Makefile.am b/backends/dummy/lib/Makefile.am
new file mode 100644
index 0000000..be80ed6
--- /dev/null
+++ b/backends/dummy/lib/Makefile.am
@@ -0,0 +1,110 @@
+BACKEND_NAME = "dummy"
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/folks \
+ -include $(CONFIG_HEADER) \
+ -DPACKAGE_DATADIR=\"$(pkgdatadir)\" \
+ -DBACKEND_NAME=\"$(BACKEND_NAME)\" \
+ -DG_LOG_DOMAIN=\"$(BACKEND_NAME)\" \
+ $(NULL)
+
+VAPIGENFLAGS += \
+ --vapidir=. \
+ --vapidir=$(top_srcdir)/folks \
+ $(NULL)
+
+folks_dummydir = $(libdir)
+folks_dummy_LTLIBRARIES = libfolks-dummy.la
+
+##################################################################
+# Support library
+##################################################################
+
+libfolks_dummy_la_vala.stamp:
+
+folks_dummy_valasources = \
+ dummy-backend.vala \
+ dummy-fat-persona.vala \
+ dummy-persona.vala \
+ dummy-persona-store.vala \
+ $(NULL)
+
+libfolks_dummy_la_SOURCES = \
+ $(folks_dummy_valasources) \
+ $(NULL)
+
+# XXX: it would be nice to do something like this below:
+# $(addprefix --pkg ,$(folks_backend_dummy_deps)) \
+# to factor out repetition, but automake's Vala support doesn't like it
+# because it assumes it can simply match every flag in any _VALAFLAGS string.
+libfolks_dummy_la_VALAFLAGS = \
+ $(AM_VALAFLAGS) \
+ $(ERROR_VALAFLAGS) \
+ --vapidir=. \
+ --vapidir=$(top_srcdir)/folks \
+ --pkg folks \
+ --pkg folks-internal \
+ --pkg gee-1.0 \
+ --pkg gio-2.0 \
+ --pkg gobject-2.0 \
+ --includedir folks \
+ --vapi folks-dummy.vapi \
+ -H folks/folks-dummy.h \
+ $(NULL)
+
+libfolks_dummy_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(CODE_COVERAGE_CFLAGS) \
+ $(GIO_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(GEE_CFLAGS) \
+ $(NULL)
+
+libfolks_dummy_la_LIBADD = \
+ $(AM_LIBADD) \
+ $(top_builddir)/folks/libfolks.la \
+ $(top_builddir)/folks/libfolks-internal.la \
+ $(GIO_LIBS) \
+ $(GLIB_LIBS) \
+ $(GEE_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_dummy_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ $(CODE_COVERAGE_LDFLAGS) \
+ -version-info "$(LT_CURRENT)":"$(LT_REVISION)":"$(LT_AGE)" \
+ -export-symbols-regex "^(DUMMY|dummy)_.*|" \
+ $(NULL)
+
+folks_dummy_includedir = $(includedir)/folks
+folks_dummy_include_HEADERS = \
+ folks/folks-dummy.h \
+ $(NULL)
+
+vapidir = $(datadir)/vala/vapi
+dist_vapi_DATA = \
+ folks-dummy.vapi \
+ folks-dummy.deps \
+ $(NULL)
+
+##################################################################
+# General
+##################################################################
+
+MAINTAINERCLEANFILES = \
+ $(folks_dummy_valasources:.vala=.c) \
+ libfolks_dummy_la_vala.stamp \
+ folks/folks-dummy.h \
+ folks-dummy.vapi \
+ $(NULL)
+
+EXTRA_DIST = \
+ $(pkgconfig_in) \
+ $(MAINTAINERCLEANFILES) \
+ $(NULL)
+
+-include ../backend.mk
+-include $(top_srcdir)/git.mk
diff --git a/backends/dummy/lib/dummy-backend.vala b/backends/dummy/lib/dummy-backend.vala
new file mode 100644
index 0000000..6646649
--- /dev/null
+++ b/backends/dummy/lib/dummy-backend.vala
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2013 Philip Withnall
+ *
+ * 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:
+ * Philip Withnall <philip tecnocode co uk>
+ */
+
+using Gee;
+using GLib;
+using Folks;
+
+extern const string BACKEND_NAME;
+
+/**
+ * A backend which connects to EDS and creates a { link PersonaStore}
+ * for each service. TODO
+ *
+ * @since UNRELEASED
+ */
+public class Dummyf.Backend : Folks.Backend
+{
+ private bool _is_prepared = false;
+ private bool _prepare_pending = false; /* used for unprepare() too */
+ private bool _is_quiescent = false;
+
+ private HashMap<string, PersonaStore> _all_persona_stores;
+ private HashMap<string, PersonaStore> _enabled_persona_stores;
+ private Map<string, PersonaStore> _enabled_persona_stores_ro;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public Backend ()
+ {
+ Object ();
+ }
+
+ construct
+ {
+ this._all_persona_stores = new HashMap<string, PersonaStore> ();
+ this._enabled_persona_stores = new HashMap<string, PersonaStore> ();
+ this._enabled_persona_stores_ro =
+ this._enabled_persona_stores.read_only_view;
+ }
+
+ /**
+ * Whether this Backend has been prepared.
+ *
+ * See { link Folks.Backend.is_prepared}.
+ *
+ * @since UNRELEASED
+ */
+ public override bool is_prepared
+ {
+ get { return this._is_prepared; }
+ }
+
+ /**
+ * Whether this Backend has reached a quiescent state.
+ *
+ * See { link Folks.Backend.is_quiescent}.
+ *
+ * @since UNRELEASED
+ */
+ public override bool is_quiescent
+ {
+ get { return this._is_quiescent; }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override string name { get { return BACKEND_NAME; } }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override Map<string, PersonaStore> persona_stores
+ {
+ get { return this._enabled_persona_stores_ro; }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override void disable_persona_store (Folks.PersonaStore store)
+ {
+ this._disable_persona_store (store);
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override void enable_persona_store (Folks.PersonaStore store)
+ {
+ this._enable_persona_store ((Dummyf.PersonaStore) store);
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override void set_persona_stores (Set<string>? store_ids)
+ {
+ /* If the set is empty, load all unloaded stores then return. */
+ if (store_ids == null)
+ {
+ this.freeze_notify ();
+ foreach (var store in this._all_persona_stores.values)
+ {
+ this._enable_persona_store (store);
+ }
+ this.thaw_notify ();
+
+ return;
+ }
+
+ /* First handle adding any missing persona stores. */
+ this.freeze_notify ();
+
+ foreach (var id in store_ids)
+ {
+ if (!this._enabled_persona_stores.has_key (id))
+ {
+ var store = this._all_persona_stores.get (id);
+ if (store != null)
+ {
+ this._enable_persona_store (store);
+ }
+ }
+ }
+
+ /* Keep persona stores to remove in a separate array so we don't
+ * invalidate the list we are iterating over. */
+ PersonaStore[] stores_to_remove = {};
+
+ foreach (var store in this._enabled_persona_stores.values)
+ {
+ if (!store_ids.contains (store.id))
+ {
+ stores_to_remove += store;
+ }
+ }
+
+ foreach (var store in stores_to_remove)
+ {
+ this._disable_persona_store (store);
+ }
+
+ this.thaw_notify ();
+ }
+
+ private void _enable_persona_store (PersonaStore store)
+ {
+ if (this._enabled_persona_stores.has_key (store.id))
+ {
+ return;
+ }
+ assert (this._all_persona_stores.has_key (store.id));
+
+ store.removed.connect (this._store_removed_cb);
+
+ this._enabled_persona_stores.set (store.id, store);
+
+ this.persona_store_added (store);
+ this.notify_property ("persona-stores");
+ }
+
+ private void _disable_persona_store (Folks.PersonaStore store)
+ {
+ if (!this._enabled_persona_stores.unset (store.id))
+ {
+ return;
+ }
+ assert (this._all_persona_stores.has_key (store.id));
+
+ this.persona_store_removed (store);
+ this.notify_property ("persona-stores");
+
+ store.removed.disconnect (this._store_removed_cb);
+ }
+
+ private void _store_removed_cb (Folks.PersonaStore store)
+ {
+ this._disable_persona_store (store);
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override async void prepare () throws GLib.Error
+ {
+ Internal.profiling_start ("preparing Dummy.Backend");
+
+ if (this._is_prepared || this._prepare_pending)
+ {
+ return;
+ }
+
+ try
+ {
+ this._prepare_pending = true;
+ this.freeze_notify ();
+
+ this._is_prepared = true;
+ this.notify_property ("is-prepared");
+
+ this._is_quiescent = true;
+ this.notify_property ("is-quiescent");
+ }
+ finally
+ {
+ this.thaw_notify ();
+ this._prepare_pending = false;
+ }
+
+ Internal.profiling_end ("preparing Dummy.Backend");
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override async void unprepare () throws GLib.Error
+ {
+ if (!this._is_prepared || this._prepare_pending)
+ {
+ return;
+ }
+
+ try
+ {
+ this._prepare_pending = true;
+ this.freeze_notify ();
+
+ foreach (var persona_store in this._enabled_persona_stores.values)
+ {
+ this._disable_persona_store (persona_store);
+ }
+
+ this._is_quiescent = false;
+ this.notify_property ("is-quiescent");
+
+ this._is_prepared = false;
+ this.notify_property ("is-prepared");
+ }
+ finally
+ {
+ this.thaw_notify ();
+ this._prepare_pending = false;
+ }
+ }
+
+ /**
+ * TODO
+ *
+ * @since UNRELEASED
+ */
+ public void register_persona_stores (Set<PersonaStore> stores)
+ {
+ foreach (var store in stores)
+ {
+ if (this._all_persona_stores.has_key (store.id))
+ {
+ continue;
+ }
+
+ this._all_persona_stores.set (store.id, store);
+ this._enable_persona_store (store);
+ }
+ }
+
+ /**
+ * TODO
+ *
+ * @since UNRELEASED
+ */
+ public void unregister_persona_stores (Set<PersonaStore> stores)
+ {
+ foreach (var store in stores)
+ {
+ if (!this._all_persona_stores.has_key (store.id))
+ {
+ continue;
+ }
+
+ this._disable_persona_store (store);
+ this._all_persona_stores.unset (store.id);
+ }
+ }
+}
diff --git a/backends/dummy/lib/dummy-fat-persona.vala b/backends/dummy/lib/dummy-fat-persona.vala
new file mode 100644
index 0000000..643e5c8
--- /dev/null
+++ b/backends/dummy/lib/dummy-fat-persona.vala
@@ -0,0 +1,916 @@
+/*
+ * Copyright (C) 2013 Philip Withnall
+ *
+ * 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:
+ * Philip Withnall <philip tecnocode co uk>
+ * TODO
+ */
+
+using Folks;
+using Gee;
+using GLib;
+
+/**
+ * A persona subclass which represents a single EDS contact. TODO
+ *
+ * Each { link Dummy.Persona} instance represents a single EDS { link E.Contact}.
+ * When the contact is modified (either by this folks client, or a different
+ * client), the { link Dummy.Persona} remains the same, but is assigned a new
+ * { link E.Contact}. It then updates its properties from this new contact.
+ *
+ * @since UNRELEASED
+ */
+public class Dummyf.FatPersona : Dummyf.Persona,
+ AntiLinkable,
+ AvatarDetails,
+ BirthdayDetails,
+ EmailDetails,
+ FavouriteDetails,
+ GenderDetails,
+ GroupDetails,
+ ImDetails,
+ LocalIdDetails,
+ NameDetails,
+ NoteDetails,
+ PhoneDetails,
+ RoleDetails,
+ UrlDetails,
+ PostalAddressDetails,
+ WebServiceDetails
+{
+ /**
+ * Create a new persona.
+ *
+ * Create a new persona for the { link PersonaStore} ``store``, representing
+ * the EDS contact given by ``contact``. TODO
+ *
+ * @param store the store which will contain the persona
+ * @param contact_id TODO
+ * @param is_user TODO
+ *
+ * @since UNRELEASED
+ */
+ public FatPersona (PersonaStore store, string contact_id,
+ bool is_user = false, string[] linkable_properties = {})
+ {
+ base (store, contact_id, is_user, linkable_properties);
+ }
+
+ construct
+ {
+ this._local_ids_ro = this._local_ids.read_only_view;
+ this._postal_addresses_ro = this._postal_addresses.read_only_view;
+ this._email_addresses_ro = this._email_addresses.read_only_view;
+ this._phone_numbers_ro = this._phone_numbers.read_only_view;
+ this._notes_ro = this._notes.read_only_view;
+ this._urls_ro = this._urls.read_only_view;
+ this._groups_ro = this._groups.read_only_view;
+ this._roles_ro = this._roles.read_only_view;
+ this._anti_links_ro = this._anti_links.read_only_view;
+ }
+
+ private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses =
+ new HashMultiMap<string, WebServiceFieldDetails> (
+ null, null,
+ (GLib.HashFunc) WebServiceFieldDetails.hash,
+ (GLib.EqualFunc) WebServiceFieldDetails.equal);
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public MultiMap<string, WebServiceFieldDetails> web_service_addresses
+ {
+ get { return this._web_service_addresses; }
+ set { this.change_web_service_addresses.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_web_service_addresses (
+ MultiMap<string, WebServiceFieldDetails> web_service_addresses)
+ throws PropertyError
+ {
+ yield this.change_property ("web-service-addresses", () =>
+ {
+ this.update_web_service_addresses (web_service_addresses);
+ });
+ }
+
+ private HashSet<string> _local_ids = new HashSet<string> ();
+ private Set<string> _local_ids_ro;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public Set<string> local_ids
+ {
+ get
+ {
+ if (this._local_ids.contains (this.iid) == false)
+ {
+ this._local_ids.add (this.iid);
+ }
+ return this._local_ids_ro;
+ }
+ set { this.change_local_ids.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_local_ids (Set<string> local_ids)
+ throws PropertyError
+ {
+ yield this.change_property ("local-ids", () =>
+ {
+ this.update_local_ids (local_ids);
+ });
+ }
+
+ private HashSet<PostalAddressFieldDetails> _postal_addresses =
+ new HashSet<PostalAddressFieldDetails> ();
+ private Set<PostalAddressFieldDetails> _postal_addresses_ro;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public Set<PostalAddressFieldDetails> postal_addresses
+ {
+ get { return this._postal_addresses_ro; }
+ set { this.change_postal_addresses.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_postal_addresses (
+ Set<PostalAddressFieldDetails> postal_addresses) throws PropertyError
+ {
+ yield this.change_property ("postal-addresses", () =>
+ {
+ this.update_postal_addresses (postal_addresses);
+ });
+ }
+
+ private HashSet<PhoneFieldDetails> _phone_numbers =
+ new HashSet<PhoneFieldDetails> ();
+ private Set<PhoneFieldDetails> _phone_numbers_ro;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public Set<PhoneFieldDetails> phone_numbers
+ {
+ get { return this._phone_numbers_ro; }
+ set { this.change_phone_numbers.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_phone_numbers (
+ Set<PhoneFieldDetails> phone_numbers) throws PropertyError
+ {
+ yield this.change_property ("phone-numbers", () =>
+ {
+ this.update_phone_numbers (phone_numbers);
+ });
+ }
+
+ private HashSet<EmailFieldDetails>? _email_addresses =
+ new HashSet<EmailFieldDetails> (
+ (GLib.HashFunc) EmailFieldDetails.hash,
+ (GLib.EqualFunc) EmailFieldDetails.equal);
+ private Set<EmailFieldDetails> _email_addresses_ro;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public Set<EmailFieldDetails> email_addresses
+ {
+ get { return this._email_addresses_ro; }
+ set { this.change_email_addresses.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_email_addresses (
+ Set<EmailFieldDetails> email_addresses) throws PropertyError
+ {
+ yield this.change_property ("email-addresses", () =>
+ {
+ this.update_email_addresses (email_addresses);
+ });
+ }
+
+ private HashSet<NoteFieldDetails> _notes = new HashSet<NoteFieldDetails> ();
+ private Set<NoteFieldDetails> _notes_ro;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public Set<NoteFieldDetails> notes
+ {
+ get { return this._notes_ro; }
+ set { this.change_notes.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_notes (Set<NoteFieldDetails> notes)
+ throws PropertyError
+ {
+ yield this.change_property ("notes", () =>
+ {
+ this.update_notes (notes);
+ });
+ }
+
+ private LoadableIcon? _avatar = null;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public LoadableIcon? avatar
+ {
+ get { return this._avatar; }
+ set { this.change_avatar.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_avatar (LoadableIcon? avatar) throws PropertyError
+ {
+ yield this.change_property ("avatar", () =>
+ {
+ this.update_avatar (avatar);
+ });
+ }
+
+ private StructuredName? _structured_name = null;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public StructuredName? structured_name
+ {
+ get { return this._structured_name; }
+ set { this.change_structured_name.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_structured_name (StructuredName? structured_name)
+ throws PropertyError
+ {
+ yield this.change_property ("structured-name", () =>
+ {
+ this.update_structured_name (structured_name);
+ });
+ }
+
+ private string _full_name = "";
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public string full_name
+ {
+ get { return this._full_name; }
+ set { this.change_full_name.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_full_name (string full_name) throws PropertyError
+ {
+ yield this.change_property ("full-name", () =>
+ {
+ this.update_full_name (full_name);
+ });
+ }
+
+ private string _nickname = "";
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public string nickname
+ {
+ get { return this._nickname; }
+ set { this.change_nickname.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_nickname (string nickname) throws PropertyError
+ {
+ yield this.change_property ("nickname", () =>
+ {
+ this.update_nickname (nickname);
+ });
+ }
+
+ private Gender _gender = Gender.UNSPECIFIED;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public Gender gender
+ {
+ get { return this._gender; }
+ set { this.change_gender.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_gender (Gender gender) throws PropertyError
+ {
+ yield this.change_property ("gender", () =>
+ {
+ this.update_gender (gender);
+ });
+ }
+
+ private HashSet<UrlFieldDetails> _urls = new HashSet<UrlFieldDetails> ();
+ private Set<UrlFieldDetails> _urls_ro;
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public Set<UrlFieldDetails> urls
+ {
+ get { return this._urls_ro; }
+ set { this.change_urls.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_urls (Set<UrlFieldDetails> urls) throws PropertyError
+ {
+ yield this.change_property ("urls", () =>
+ {
+ this.update_urls (urls);
+ });
+ }
+
+ private HashMultiMap<string, ImFieldDetails> _im_addresses =
+ new HashMultiMap<string, ImFieldDetails> (null, null,
+ (GLib.HashFunc) ImFieldDetails.hash,
+ (GLib.EqualFunc) ImFieldDetails.equal);
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public MultiMap<string, ImFieldDetails> im_addresses
+ {
+ get { return this._im_addresses; }
+ set { this.change_im_addresses.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_im_addresses (
+ MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
+ {
+ yield this.change_property ("im-addresses", () =>
+ {
+ this.update_im_addresses (im_addresses);
+ });
+ }
+
+ private HashSet<string> _groups = new HashSet<string> ();
+ private Set<string> _groups_ro;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public Set<string> groups
+ {
+ get { return this._groups_ro; }
+ set { this.change_groups.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_group (string group, bool is_member)
+ throws GLib.Error
+ {
+ /* Nothing to do? */
+ if ((is_member == true && this._groups.contains (group) == true) ||
+ (is_member == false && this._groups.contains (group) == false))
+ {
+ return;
+ }
+
+ /* Replace the current set of groups with a modified one. */
+ var new_groups = new HashSet<string> ();
+ foreach (var category_name in this._groups)
+ {
+ new_groups.add (category_name);
+ }
+
+ if (is_member == false)
+ {
+ new_groups.remove (group);
+ }
+ else
+ {
+ new_groups.add (group);
+ }
+
+ yield this.change_groups (new_groups);
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_groups (Set<string> groups) throws PropertyError
+ {
+ yield this.change_property ("groups", () =>
+ {
+ this.update_groups (groups);
+ });
+ }
+
+ private string? _calendar_event_id = null;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public string? calendar_event_id
+ {
+ get { return this._calendar_event_id; }
+ set { this.change_calendar_event_id.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_calendar_event_id (string? calendar_event_id)
+ throws PropertyError
+ {
+ yield this.change_property ("calendar-event-id", () =>
+ {
+ this.update_calendar_event_id (calendar_event_id);
+ });
+ }
+
+ private DateTime? _birthday = null;
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public DateTime? birthday
+ {
+ get { return this._birthday; }
+ set { this.change_birthday.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_birthday (DateTime? bday)
+ throws PropertyError
+ {
+ yield this.change_property ("birthday", () =>
+ {
+ this.update_birthday (birthday);
+ });
+ }
+
+ private HashSet<RoleFieldDetails> _roles = new HashSet<RoleFieldDetails> ();
+ private Set<RoleFieldDetails> _roles_ro;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public Set<RoleFieldDetails> roles
+ {
+ get { return this._roles_ro; }
+ set { this.change_roles.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_roles (Set<RoleFieldDetails> roles)
+ throws PropertyError
+ {
+ yield this.change_property ("roles", () =>
+ {
+ this.update_roles (roles);
+ });
+ }
+
+ private bool _is_favourite = false;
+
+ /**
+ * Whether this contact is a user-defined favourite.
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public bool is_favourite
+ {
+ get { return this._is_favourite; }
+ set { this.change_is_favourite.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_is_favourite (bool is_favourite) throws PropertyError
+ {
+ yield this.change_property ("is-favourite", () =>
+ {
+ this.update_is_favourite (is_favourite);
+ });
+ }
+
+ private HashSet<string> _anti_links = new HashSet<string> ();
+ private Set<string> _anti_links_ro;
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public Set<string> anti_links
+ {
+ get { return this._anti_links_ro; }
+ set { this.change_anti_links.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_anti_links (Set<string> anti_links)
+ throws PropertyError
+ {
+ yield this.change_property ("anti-links", () =>
+ {
+ this.update_anti_links (anti_links);
+ });
+ }
+
+ /**
+ * TODO All the functions below here are...
+ */
+
+ private HashSet<T> _dup_to_hash_set<T> (Set<T> input_set)
+ {
+ var output_set = new HashSet<T> ();
+ output_set.add_all (input_set);
+ return output_set;
+ }
+
+ private HashMultiMap<S, T> _dup_to_hash_multi_map<S, T> (
+ MultiMap<S, T> input_multi_map)
+ {
+ var output_multi_map = new HashMultiMap<S, T> ();
+
+ foreach (var k in input_multi_map.get_keys ())
+ {
+ var values = input_multi_map.get (k);
+ foreach (var v in values)
+ {
+ output_multi_map.set (k, v);
+ }
+ }
+
+ return output_multi_map;
+ }
+
+ public void update_gender (Gender gender)
+ {
+ if (this._gender != gender)
+ {
+ this._gender = gender;
+ this.notify_property ("gender");
+ }
+ }
+
+ public void update_calendar_event_id (string? calendar_event_id)
+ {
+ if (calendar_event_id != this._calendar_event_id)
+ {
+ this._calendar_event_id = calendar_event_id;
+ this.notify_property ("calendar-event-id");
+ }
+ }
+
+ public void update_birthday (DateTime? birthday)
+ {
+ if ((this._birthday == null) != (birthday == null) ||
+ (this._birthday != null && birthday != null &&
+ !((!) this._birthday).equal ((!) birthday)))
+ {
+ this._birthday = birthday;
+ this.notify_property ("birthday");
+ }
+ }
+
+ public void update_roles (Set<RoleFieldDetails> roles)
+ {
+ if (!Folks.Internal.equal_sets<RoleFieldDetails> (roles, this._roles))
+ {
+ this._roles = this._dup_to_hash_set<RoleFieldDetails> (roles);
+ this._roles_ro = this._roles.read_only_view;
+ this.notify_property ("roles");
+ }
+ }
+
+ public void update_groups (Set<string> groups)
+ {
+ if (!Folks.Internal.equal_sets<string> (groups, this._groups))
+ {
+ this._groups = this._dup_to_hash_set<string> (groups);
+ this._groups_ro = this._groups.read_only_view;
+ this.notify_property ("groups");
+ }
+ }
+
+ public void update_web_service_addresses (
+ MultiMap<string, WebServiceFieldDetails> web_service_addresses)
+ {
+ if (!Utils.multi_map_str_afd_equal (web_service_addresses,
+ this._web_service_addresses))
+ {
+ this._web_service_addresses =
+ this._dup_to_hash_multi_map<string, WebServiceFieldDetails> (
+ web_service_addresses);
+ this.notify_property ("web-service-addresses");
+ }
+ }
+
+ public void update_email_addresses (Set<EmailFieldDetails> email_addresses)
+ {
+ if (!Folks.Internal.equal_sets<EmailFieldDetails> (email_addresses,
+ this._email_addresses))
+ {
+ this._email_addresses =
+ this._dup_to_hash_set<EmailFieldDetails> (email_addresses);
+ this._email_addresses_ro = this._email_addresses.read_only_view;
+ this.notify_property ("email-addresses");
+ }
+ }
+
+ public void update_notes (Set<NoteFieldDetails> notes)
+ {
+ if (!Folks.Internal.equal_sets<NoteFieldDetails> (notes, this._notes))
+ {
+ this._notes = this._dup_to_hash_set<NoteFieldDetails> (notes);
+ this._notes_ro = this._notes.read_only_view;
+ this.notify_property ("notes");
+ }
+ }
+
+ public void update_full_name (string full_name)
+ {
+ if (this._full_name != full_name)
+ {
+ this._full_name = full_name;
+ this.notify_property ("full-name");
+ }
+ }
+
+ public void update_nickname (string nickname)
+ {
+ if (this._nickname != nickname)
+ {
+ this._nickname = nickname;
+ this.notify_property ("nickname");
+ }
+ }
+
+ public void update_structured_name (StructuredName? structured_name)
+ {
+ if (structured_name != null && !((!) structured_name).is_empty ())
+ {
+ this._structured_name = (!) structured_name;
+ this.notify_property ("structured-name");
+ }
+ else if (this._structured_name != null)
+ {
+ this._structured_name = null;
+ this.notify_property ("structured-name");
+ }
+ }
+
+ public void update_avatar (LoadableIcon? avatar)
+ {
+ if ((this._avatar == null) != (avatar == null) ||
+ (this._avatar != null && avatar != null &&
+ !((!) this._avatar).equal ((!) avatar)))
+ {
+ this._avatar = avatar;
+ this.notify_property ("avatar");
+ }
+ }
+
+ public void update_urls (Set<UrlFieldDetails> urls)
+ {
+ if (!Utils.set_afd_equal (urls, this._urls))
+ {
+ this._urls = this._dup_to_hash_set<UrlFieldDetails> (urls);
+ this._urls_ro = this._urls.read_only_view;
+ this.notify_property ("urls");
+ }
+ }
+
+ public void update_im_addresses (
+ MultiMap<string, ImFieldDetails> im_addresses)
+ {
+ if (!Utils.multi_map_str_afd_equal (im_addresses,
+ this._im_addresses))
+ {
+ this._im_addresses =
+ this._dup_to_hash_multi_map<string, ImFieldDetails> (
+ im_addresses);
+ this.notify_property ("im-addresses");
+ }
+ }
+
+ public void _update_groups (Set<string> groups)
+ {
+ if (!Folks.Internal.equal_sets<string> (groups, this._groups))
+ {
+ this._groups = this._dup_to_hash_set<string> (groups);
+ this._groups_ro = this._groups.read_only_view;
+ this.notify_property ("groups");
+ }
+ }
+
+ public void update_phone_numbers (Set<PhoneFieldDetails> phone_numbers)
+ {
+ if (!Folks.Internal.equal_sets<PhoneFieldDetails> (phone_numbers,
+ this._phone_numbers))
+ {
+ this._phone_numbers =
+ this._dup_to_hash_set<PhoneFieldDetails> (phone_numbers);
+ this._phone_numbers_ro = this._phone_numbers.read_only_view;
+ this.notify_property ("phone-numbers");
+ }
+ }
+
+ public void update_postal_addresses (
+ Set<PostalAddressFieldDetails> postal_addresses)
+ {
+ if (!Folks.Internal.equal_sets<PostalAddressFieldDetails> (
+ postal_addresses, this._postal_addresses))
+ {
+ this._postal_addresses =
+ this._dup_to_hash_set<PostalAddressFieldDetails> (
+ postal_addresses);
+ this._postal_addresses_ro = this._postal_addresses.read_only_view;
+ this.notify_property ("postal-addresses");
+ }
+ }
+
+ public void update_local_ids (Set<string> local_ids)
+ {
+ /* Make sure it includes our local ID. */
+ local_ids.add (this.iid);
+
+ if (!Folks.Internal.equal_sets<string> (local_ids, this.local_ids))
+ {
+ this._local_ids = this._dup_to_hash_set<string> (local_ids);
+ this._local_ids_ro = this._local_ids.read_only_view;
+ this.notify_property ("local-ids");
+ }
+ }
+
+ public void update_is_favourite (bool is_favourite)
+ {
+ if (is_favourite != this._is_favourite)
+ {
+ this._is_favourite = is_favourite;
+ this.notify_property ("is-favourite");
+ }
+ }
+
+ public void update_anti_links (Set<string> anti_links)
+ {
+ if (!Folks.Internal.equal_sets<string> (anti_links, this._anti_links))
+ {
+ this._anti_links = this._dup_to_hash_set<string> (anti_links);
+ this._anti_links_ro = this._anti_links.read_only_view;
+ this.notify_property ("anti-links");
+ }
+ }
+}
diff --git a/backends/dummy/lib/dummy-persona-store.vala b/backends/dummy/lib/dummy-persona-store.vala
new file mode 100644
index 0000000..e2b6f2d
--- /dev/null
+++ b/backends/dummy/lib/dummy-persona-store.vala
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2013 Philip Withnall
+ *
+ * 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:
+ * Philip Withnall <philip tecnocode co uk>
+ */
+
+using Folks;
+using Gee;
+using GLib;
+
+/**
+ * A persona store representing a single EDS address book. TODO
+ *
+ * The persona store will contain { link Dummy.Persona}s for each contact in the
+ * address book it represents.
+ *
+ * TODO: trust_level and is_user_set_default can be set as normal properties
+ *
+ * @since UNRELEASED
+ */
+public class Dummyf.PersonaStore : Folks.PersonaStore
+{
+ private bool _is_prepared = false;
+ private bool _prepare_pending = false;
+ private bool _is_quiescent = false;
+ private bool _quiescent_on_prepare = false;
+
+ /**
+ * The type of persona store this is.
+ *
+ * See { link Folks.PersonaStore.type_id}.
+ *
+ * @since UNRELEASED
+ */
+ public override string type_id { get { return BACKEND_NAME; } }
+
+ private MaybeBool _can_add_personas = MaybeBool.FALSE;
+
+ /**
+ * Whether this PersonaStore can add { link Folks.Persona}s.
+ *
+ * See { link Folks.PersonaStore.can_add_personas}.
+ *
+ * @since UNRELEASED
+ */
+ public override MaybeBool can_add_personas
+ {
+ get
+ {
+ if (!this._is_prepared)
+ {
+ return MaybeBool.FALSE;
+ }
+
+ return this._can_add_personas;
+ }
+ }
+
+ private MaybeBool _can_alias_personas = MaybeBool.FALSE;
+
+ /**
+ * Whether this PersonaStore can set the alias of { link Folks.Persona}s.
+ *
+ * See { link Folks.PersonaStore.can_alias_personas}.
+ *
+ * @since UNRELEASED
+ */
+ public override MaybeBool can_alias_personas
+ {
+ get
+ {
+ if (!this._is_prepared)
+ {
+ return MaybeBool.FALSE;
+ }
+
+ return this._can_alias_personas;
+ }
+ }
+
+ /**
+ * Whether this PersonaStore can set the groups of { link Folks.Persona}s.
+ *
+ * See { link Folks.PersonaStore.can_group_personas}.
+ *
+ * @since UNRELEASED
+ */
+ public override MaybeBool can_group_personas
+ {
+ get
+ {
+ return ("groups" in this._always_writeable_properties)
+ ? MaybeBool.TRUE : MaybeBool.FALSE;
+ }
+ }
+
+ private MaybeBool _can_remove_personas = MaybeBool.FALSE;
+
+ /**
+ * Whether this PersonaStore can remove { link Folks.Persona}s.
+ *
+ * See { link Folks.PersonaStore.can_remove_personas}.
+ *
+ * @since UNRELEASED
+ */
+ public override MaybeBool can_remove_personas
+ {
+ get
+ {
+ if (!this._is_prepared)
+ {
+ return MaybeBool.FALSE;
+ }
+
+ return this._can_remove_personas;
+ }
+ }
+
+ /**
+ * Whether this PersonaStore has been prepared.
+ *
+ * See { link Folks.PersonaStore.is_prepared}.
+ *
+ * @since UNRELEASED
+ */
+ public override bool is_prepared
+ {
+ get { return this._is_prepared; }
+ }
+
+ private string[] _always_writeable_properties = {};
+ private static string[] _always_writeable_properties_empty = {}; /* oh Vala */
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override string[] always_writeable_properties
+ {
+ get
+ {
+ if (!this._is_prepared)
+ {
+ return PersonaStore._always_writeable_properties_empty;
+ }
+
+ return this._always_writeable_properties;
+ }
+ }
+
+ /*
+ * Whether this PersonaStore has reached a quiescent state.
+ *
+ * See { link Folks.PersonaStore.is_quiescent}.
+ *
+ * @since UNRELEASED
+ */
+ public override bool is_quiescent
+ {
+ get { return this._is_quiescent; }
+ }
+
+ private HashMap<string, Persona> _personas;
+ private Map<string, Persona> _personas_ro;
+ private HashSet<Persona> _pending_personas = null;
+
+ /**
+ * The { link Persona}s exposed by this PersonaStore.
+ *
+ * See { link Folks.PersonaStore.personas}.
+ *
+ * @since UNRELEASED
+ */
+ public override Map<string, Persona> personas
+ {
+ get { return this._personas_ro; }
+ }
+
+ /**
+ * Create a new PersonaStore.
+ *
+ * Create a new persona store to store the { link Persona}s for the contacts
+ * in ``s``. Passing a re-used source registry to the constructor (compared to
+ * the old { link Dummy.PersonaStore} constructor) saves a lot of time and
+ * D-Bus round trips. TODO
+ *
+ * @param id The new store's ID.
+ * @param display_name The new store's display name.
+ * @param always_writeable_properties The set of always writeable properties.
+ *
+ * @since UNRELEASED
+ */
+ public PersonaStore (string id, string display_name,
+ string[] always_writeable_properties)
+ {
+ Object (
+ id: id,
+ display_name: display_name);
+
+ this._always_writeable_properties = always_writeable_properties;
+ }
+
+ construct
+ {
+ this._personas = new HashMap<string, Persona> ();
+ this._personas_ro = this._personas.read_only_view;
+ }
+
+ /**
+ * Add a new { link Persona} to the PersonaStore.
+ *
+ * Accepted keys for ``details`` are:
+ * - PersonaStore.detail_key (PersonaDetail.AVATAR)
+ * - PersonaStore.detail_key (PersonaDetail.BIRTHDAY)
+ * - PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES)
+ * - PersonaStore.detail_key (PersonaDetail.FULL_NAME)
+ * - PersonaStore.detail_key (PersonaDetail.GENDER)
+ * - PersonaStore.detail_key (PersonaDetail.IM_ADDRESSES)
+ * - PersonaStore.detail_key (PersonaDetail.IS_FAVOURITE)
+ * - PersonaStore.detail_key (PersonaDetail.PHONE_NUMBERS)
+ * - PersonaStore.detail_key (PersonaDetail.POSTAL_ADDRESSES)
+ * - PersonaStore.detail_key (PersonaDetail.ROLES)
+ * - PersonaStore.detail_key (PersonaDetail.STRUCTURED_NAME)
+ * - PersonaStore.detail_key (PersonaDetail.LOCAL_IDS)
+ * - PersonaStore.detail_key (PersonaDetail.WEB_SERVICE_ADDRESSES)
+ * - PersonaStore.detail_key (PersonaDetail.NOTES)
+ * - PersonaStore.detail_key (PersonaDetail.URLS)
+ *
+ * See { link Folks.PersonaStore.add_persona_from_details}.
+ *
+ * @throws Folks.PersonaStoreError.STORE_OFFLINE if the store hasn’t been
+ * prepared
+ * @throws Folks.PersonaStoreError.CREATE_FAILED if creating the persona in
+ * the EDS store failed
+ *
+ * @since UNRELEASED
+ */
+ public override async Folks.Persona? add_persona_from_details (
+ HashTable<string, Value?> details) throws Folks.PersonaStoreError
+ {
+ // We have to have called prepare() beforehand.
+ if (!this._is_prepared)
+ {
+ throw new PersonaStoreError.STORE_OFFLINE (
+ "Persona store has not yet been prepared.");
+ }
+
+ /* TODO: Allow overriding the class used. */
+ var persona = new Dummyf.Persona (this, "TODO", false, {});
+
+ var iter = HashTableIter<string, Value?> (details);
+ unowned string k;
+ unowned Value? _v;
+
+ while (iter.next (out k, out _v) == true)
+ {
+ if (_v == null)
+ {
+ continue;
+ }
+ unowned Value v = (!) _v;
+
+ if (k == Folks.PersonaStore.detail_key (
+ PersonaDetail.FULL_NAME))
+ {
+ string? full_name = v.get_string ();
+ string _full_name = "";
+ if (full_name != null)
+ {
+ _full_name = (!) full_name;
+ }
+
+ var _persona = persona as NameDetails;
+ assert (_persona != null);
+ _persona.full_name = _full_name;
+ }
+ /* TODO */
+ /*else if (k == Folks.PersonaStore.detail_key (
+ PersonaDetail.EMAIL_ADDRESSES))
+ {
+ Set<EmailFieldDetails> email_addresses =
+ (Set<EmailFieldDetails>) v.get_object ();
+ this._set_contact_attributes_string (contact,
+ email_addresses,
+ "EMAIL", E.ContactField.EMAIL);
+ }
+ else if (k == Folks.PersonaStore.detail_key (PersonaDetail.AVATAR))
+ {
+ try
+ {
+ var avatar = (LoadableIcon?) v.get_object ();
+ yield this._set_contact_avatar (contact, avatar);
+ }
+ catch (PropertyError e1)
+ {
+ warning ("Couldn't set avatar on the EContact: %s",
+ e1.message);
+ }
+ }
+ else if (k == Folks.PersonaStore.detail_key (
+ PersonaDetail.IM_ADDRESSES))
+ {
+ var im_fds = (MultiMap<string, ImFieldDetails>) v.get_object ();
+ this._set_contact_im_fds (contact, im_fds);
+ }
+ else if (k == Folks.PersonaStore.detail_key (
+ PersonaDetail.PHONE_NUMBERS))
+ {
+ Set<PhoneFieldDetails> phone_numbers =
+ (Set<PhoneFieldDetails>) v.get_object ();
+ this._set_contact_attributes_string (contact,
+ phone_numbers, "TEL",
+ E.ContactField.TEL);
+ }
+ else if (k == Folks.PersonaStore.detail_key (
+ PersonaDetail.POSTAL_ADDRESSES))
+ {
+ Set<PostalAddressFieldDetails> postal_fds =
+ (Set<PostalAddressFieldDetails>) v.get_object ();
+ this._set_contact_postal_addresses (contact, postal_fds);
+ }
+ else if (k == Folks.PersonaStore.detail_key (
+ PersonaDetail.STRUCTURED_NAME))
+ {
+ StructuredName sname = (StructuredName) v.get_object ();
+ this._set_contact_name (contact, sname);
+ }
+ else if (k == Folks.PersonaStore.detail_key (PersonaDetail.LOCAL_IDS))
+ {
+ Set<string> local_ids = (Set<string>) v.get_object ();
+ this._set_contact_local_ids (contact, local_ids);
+ }
+ else if (k == Folks.PersonaStore.detail_key
+ (PersonaDetail.WEB_SERVICE_ADDRESSES))
+ {
+ HashMultiMap<string, WebServiceFieldDetails>
+ web_service_addresses =
+ (HashMultiMap<string, WebServiceFieldDetails>) v.get_object ();
+ this._set_contact_web_service_addresses (contact,
+ web_service_addresses);
+ }
+ else if (k == Folks.PersonaStore.detail_key (PersonaDetail.NOTES))
+ {
+ var notes = (Gee.HashSet<NoteFieldDetails>) v.get_object ();
+ this._set_contact_notes (contact, notes);
+ }
+ else if (k == Folks.PersonaStore.detail_key (PersonaDetail.GENDER))
+ {
+ var gender = (Gender) v.get_enum ();
+ this._set_contact_gender (contact, gender);
+ }
+ else if (k == Folks.PersonaStore.detail_key (PersonaDetail.URLS))
+ {
+ Set<UrlFieldDetails> urls = (Set<UrlFieldDetails>) v.get_object ();
+ this._set_contact_urls (contact, urls);
+ }
+ else if (k == Folks.PersonaStore.detail_key (PersonaDetail.BIRTHDAY))
+ {
+ var birthday = (DateTime?) v.get_boxed ();
+ this._set_contact_birthday (contact, birthday);
+ }
+ else if (k == Folks.PersonaStore.detail_key (PersonaDetail.ROLES))
+ {
+ Set<RoleFieldDetails> roles =
+ (Set<RoleFieldDetails>) v.get_object ();
+ this._set_contact_roles (contact, roles);
+ }
+ else if (k == Folks.PersonaStore.detail_key (
+ PersonaDetail.IS_FAVOURITE))
+ {
+ bool is_fav = v.get_boolean ();
+ this._set_contact_is_favourite (contact, is_fav);
+ }*/
+ }
+
+ /* TODO: How to simulate failure? */
+ this._personas.set (persona.iid, persona);
+
+ /* Notify of the new persona. */
+ var added_personas = new HashSet<Persona> ();
+ added_personas.add (persona);
+ this._emit_personas_changed (added_personas, null);
+
+ return persona;
+ }
+
+ /**
+ * Remove a { link Persona} from the PersonaStore.
+ *
+ * See { link Folks.PersonaStore.remove_persona}.
+ *
+ * @param persona the persona that should be removed
+ * @throws Folks.PersonaStoreError.STORE_OFFLINE if the store hasn’t been
+ * prepared or has gone offline
+ * @throws Folks.PersonaStoreError.PERMISSION_DENIED if the store denied
+ * permission to delete the contact
+ * @throws Folks.PersonaStoreError.READ_ONLY if the store is read only
+ * @throws Folks.PersonaStoreError.REMOVE_FAILED if any other errors happened
+ * in the store
+ *
+ * @since UNRELEASED
+ */
+ public override async void remove_persona (Folks.Persona persona)
+ throws Folks.PersonaStoreError
+ {
+ // We have to have called prepare() beforehand.
+ if (!this._is_prepared)
+ {
+ throw new PersonaStoreError.STORE_OFFLINE (
+ "Persona store has not yet been prepared.");
+ }
+
+ /* TODO: How to simulate failure? */
+ Persona? _persona = this._personas.get (persona.iid);
+ if (persona != null)
+ {
+ this._personas.unset (persona.iid);
+
+ /* Handle the case where a contact is removed before the persona
+ * store has reached quiescence. */
+ if (this._pending_personas != null)
+ {
+ this._pending_personas.remove ((!) _persona);
+ }
+
+ /* Notify of the removal. */
+ var removed_personas = new HashSet<Folks.Persona> ();
+ removed_personas.add ((!) persona);
+ this._emit_personas_changed (null, removed_personas);
+ }
+ }
+
+ /**
+ * Prepare the PersonaStore for use.
+ *
+ * See { link Folks.PersonaStore.prepare}.
+ *
+ * @throws Folks.PersonaStoreError.STORE_OFFLINE if the EDS store is offline
+ * @throws Folks.PersonaStoreError.PERMISSION_DENIED if permission was denied
+ * to open the EDS store
+ * @throws Folks.PersonaStoreError.INVALID_ARGUMENT if any other error
+ * occurred in the EDS store
+ *
+ * @since UNRELEASED
+ */
+ public override async void prepare () throws PersonaStoreError
+ {
+ Internal.profiling_start ("preparing Dummy.PersonaStore (ID: %s)",
+ this.id);
+
+ if (this._is_prepared == true || this._prepare_pending == true)
+ {
+ return;
+ }
+
+ try
+ {
+ this._prepare_pending = true;
+
+ this._is_prepared = true;
+ this.notify_property ("is-prepared");
+
+ /* If reach_quiescence() has been called already, signal
+ * quiescence. */
+ if (this._quiescent_on_prepare == true)
+ {
+ this.reach_quiescence ();
+ }
+ }
+ finally
+ {
+ this._prepare_pending = false;
+ }
+
+ Internal.profiling_end ("preparing Dummy.PersonaStore");
+ }
+
+ /**
+ * TODO
+ *
+ * @param can_add_personas TODO
+ * @param can_alias_personas TODO
+ * @param can_remove_personas TODO
+ * @since UNRELEASED
+ */
+ public void set_capabilities (MaybeBool can_add_personas,
+ MaybeBool can_alias_personas, MaybeBool can_remove_personas)
+ {
+ this.freeze_notify ();
+
+ if (can_add_personas != this._can_add_personas)
+ {
+ this._can_add_personas = can_add_personas;
+ this.notify_property ("can-add-personas");
+ }
+
+ if (can_alias_personas != this._can_alias_personas)
+ {
+ this._can_alias_personas = can_alias_personas;
+ this.notify_property ("can-alias-personas");
+ }
+
+ if (can_remove_personas != this._can_remove_personas)
+ {
+ this._can_remove_personas = can_remove_personas;
+ this.notify_property ("can-remove-personas");
+ }
+
+ this.thaw_notify ();
+ }
+
+ /**
+ * TODO
+ *
+ * @param personas TODO
+ * @since UNRELEASED
+ */
+ public void register_personas (Set<Persona> personas)
+ {
+ HashSet<Persona> added_personas;
+
+ /* If the persona store hasn't yet reached quiescence, queue up the
+ * personas and emit a notification about them later.. */
+ if (this._is_quiescent == false)
+ {
+ /* Lazily create pending_personas. */
+ if (this._pending_personas == null)
+ {
+ this._pending_personas = new HashSet<Persona> ();
+ }
+
+ added_personas = this._pending_personas;
+ }
+ else
+ {
+ added_personas = new HashSet<Persona> ();
+ }
+
+ foreach (var persona in personas)
+ {
+ if (!this._personas.has_key (persona.iid))
+ {
+ this._personas.set (persona.iid, persona);
+ added_personas.add (persona);
+ }
+ }
+
+ if (added_personas.size > 0 && this._is_quiescent == true)
+ {
+ this._emit_personas_changed (added_personas, null);
+ }
+ }
+
+ /**
+ * TODO
+ *
+ * @param personas TODO
+ * @since UNRELEASED
+ */
+ public void unregister_personas (Set<Persona> personas)
+ {
+ var removed_personas = new HashSet<Persona> ();
+
+ foreach (var _persona in personas)
+ {
+ Persona? persona = this._personas.get (_persona.iid);
+ if (persona != null)
+ {
+ removed_personas.add ((!) persona);
+ this._personas.unset (((!) persona).iid);
+
+ /* Handle the case where a contact is removed before the persona
+ * store has reached quiescence. */
+ if (this._pending_personas != null)
+ {
+ this._pending_personas.remove ((!) persona);
+ }
+ }
+ }
+
+ if (removed_personas.size > 0)
+ {
+ this._emit_personas_changed (null, removed_personas);
+ }
+ }
+
+/* TODO: Some method of emitting _emit_personas_changed with no null values */
+
+ /**
+ * TODO
+ *
+ * @since UNRELEASED
+ */
+ public void reach_quiescence ()
+ {
+ /* Can't reach quiescence until prepare() has been called. */
+ if (this._is_prepared == false)
+ {
+ this._quiescent_on_prepare = true;
+ return;
+ }
+
+ /* The initial query is complete, so signal that we've reached
+ * quiescence (even if there was an error). */
+ if (this._is_quiescent == false)
+ {
+ /* Emit a notification about all the personas which were found in the
+ * initial query. They're queued up in _contacts_added_cb() and only
+ * emitted here as _contacts_added_cb() may be called many times
+ * before _contacts_complete_cb() is called. For example, EDS seems to
+ * like emitting contacts in batches of 16 at the moment.
+ * Queueing the personas up and emitting a single notification is a
+ * lot more efficient for the individual aggregator to handle. */
+ if (this._pending_personas != null)
+ {
+ this._emit_personas_changed (this._pending_personas, null);
+ this._pending_personas = null;
+ }
+
+ this._is_quiescent = true;
+ this.notify_property ("is-quiescent");
+ }
+ }
+}
diff --git a/backends/dummy/lib/dummy-persona.vala b/backends/dummy/lib/dummy-persona.vala
new file mode 100644
index 0000000..8b5f467
--- /dev/null
+++ b/backends/dummy/lib/dummy-persona.vala
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2013 Philip Withnall
+ *
+ * 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:
+ * Philip Withnall <philip tecnocode co uk>
+ */
+
+using Folks;
+using Gee;
+using GLib;
+
+/**
+ * A persona subclass which represents a single EDS contact. TODO
+ *
+ * Each { link Dummy.Persona} instance represents a single EDS { link E.Contact}.
+ * When the contact is modified (either by this folks client, or a different
+ * client), the { link Dummy.Persona} remains the same, but is assigned a new
+ * { link E.Contact}. It then updates its properties from this new contact.
+ *
+ * @since UNRELEASED
+ */
+public class Dummyf.Persona : Folks.Persona
+{
+ private string[] _linkable_properties = new string[0];
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override string[] linkable_properties
+ {
+ get { return this._linkable_properties; }
+ }
+
+ private string[] _writeable_properties = new string[0];
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override string[] writeable_properties
+ {
+ get { return this._writeable_properties; }
+ }
+
+ /**
+ * Create a new persona.
+ *
+ * Create a new persona for the { link PersonaStore} ``store``, representing
+ * the EDS contact given by ``contact``. TODO
+ *
+ * @param store the store which will contain the persona
+ * @param contact_id TODO
+ * @param is_user TODO
+ *
+ * @since UNRELEASED
+ */
+ public Persona (PersonaStore store, string contact_id, bool is_user = false,
+ string[] linkable_properties = {})
+ {
+ var uid = Folks.Persona.build_uid (BACKEND_NAME, store.id, contact_id);
+ var iid = store.id + ":" + contact_id;
+
+ Object (display_id: contact_id,
+ uid: uid,
+ iid: iid,
+ store: store,
+ is_user: is_user);
+
+ this._linkable_properties = linkable_properties;
+ this._writeable_properties = this.store.always_writeable_properties;
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override void linkable_property_to_links (string prop_name,
+ Folks.Persona.LinkablePropertyCallback callback)
+ {
+ /* TODO */
+ if (prop_name == "im-addresses")
+ {
+ var persona = this as ImDetails;
+ assert (persona != null);
+
+ foreach (var protocol in persona.im_addresses.get_keys ())
+ {
+ var im_fds = persona.im_addresses.get (protocol);
+
+ foreach (var im_fd in im_fds)
+ {
+ callback (protocol + ":" + im_fd.value);
+ }
+ }
+ }
+ else if (prop_name == "local-ids")
+ {
+ var persona = this as LocalIdDetails;
+ assert (persona != null);
+
+ foreach (var id in persona.local_ids)
+ {
+ callback (id);
+ }
+ }
+ else if (prop_name == "web-service-addresses")
+ {
+ var persona = this as WebServiceDetails;
+ assert (persona != null);
+
+ foreach (var web_service in persona.web_service_addresses.get_keys ())
+ {
+ var web_service_addresses =
+ persona.web_service_addresses.get (web_service);
+
+ foreach (var ws_fd in web_service_addresses)
+ {
+ callback (web_service + ":" + ws_fd.value);
+ }
+ }
+ }
+ else if (prop_name == "email-addresses")
+ {
+ var persona = this as EmailDetails;
+ assert (persona != null);
+
+ foreach (var email in persona.email_addresses)
+ {
+ callback (email.value);
+ }
+ }
+ else
+ {
+ /* Chain up */
+ base.linkable_property_to_links (prop_name, callback);
+ }
+ }
+
+ /**
+ * TODO
+ *
+ * @since UNRELEASED
+ */
+ public void update_writeable_properties (string[] writeable_properties)
+ {
+ /* TODO: Don't update if there's no change. */
+ var new_length = this.store.always_writeable_properties.length +
+ writeable_properties.length;
+ this._writeable_properties = new string[new_length];
+ int i = 0;
+
+ foreach (var p in this.store.always_writeable_properties)
+ {
+ this._writeable_properties[i++] = p;
+ }
+ foreach (var p in writeable_properties)
+ {
+ this._writeable_properties[i++] = p;
+ }
+
+ this.notify_property ("writeable-properties");
+ }
+
+ /**
+ * TODO (in milliseconds)
+ *
+ * @since UNRELEASED
+ */
+ protected int property_change_delay { get; set; }
+
+ /* TODO */
+ protected delegate void ChangePropertyCallback ();
+
+ /**
+ * TODO
+ *
+ * @param property_name TODO
+ * @param callback TODO
+ * @since UNRELEASED
+ */
+ protected async void change_property (string property_name,
+ ChangePropertyCallback callback)
+ {
+ if (this.property_change_delay < 0)
+ {
+ /* No delay. */
+ callback ();
+ }
+ else if (this.property_change_delay == 0)
+ {
+ /* Idle delay. */
+ Idle.add (() =>
+ {
+ callback ();
+ this.change_property.callback ();
+ return false;
+ });
+
+ yield;
+ }
+ else
+ {
+ /* Timed delay. */
+ Timeout.add (this.property_change_delay, () =>
+ {
+ callback ();
+ this.change_property.callback ();
+ return false;
+ });
+
+ yield;
+ }
+ }
+}
diff --git a/backends/dummy/lib/folks-dummy.deps b/backends/dummy/lib/folks-dummy.deps
new file mode 100644
index 0000000..13b117b
--- /dev/null
+++ b/backends/dummy/lib/folks-dummy.deps
@@ -0,0 +1,4 @@
+glib-2.0
+gobject-2.0
+folks
+gee-1.0
diff --git a/configure.ac b/configure.ac
index d00dc94..c231deb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -645,6 +645,8 @@ AC_CONFIG_FILES([
folks/org.freedesktop.folks.gschema.xml
Makefile
backends/Makefile
+ backends/dummy/Makefile
+ backends/dummy/lib/Makefile
backends/key-file/Makefile
backends/libsocialweb/Makefile
backends/libsocialweb/lib/Makefile
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]