[folks/648811-dummy-backend-attempt3: 1/2] Another attempt at a dummy backend



commit 479f6b18265eb53eea65da0d3ee77e943db3abe1
Author: Philip Withnall <philip tecnocode co uk>
Date:   Sun Jul 22 21:26:11 2012 +0100

    Another attempt at a dummy backend
    
    This one has folks as a D-Bus client, and the tests as a D-Bus server.
    Stalled because Vala doesn't natively support GDBusObjectManager, so
    I'd be fighting Vala's D-Bus stuff all the way.

 backends/Makefile.am                      |    2 +
 backends/dummy/Makefile.am                |   64 ++++
 backends/dummy/dummy-backend-factory.vala |   60 ++++
 backends/dummy/dummy-backend.vala         |  210 +++++++++++++
 backends/dummy/dummy-persona-store.vala   |  250 +++++++++++++++
 backends/dummy/dummy-persona.vala         |  471 +++++++++++++++++++++++++++++
 configure.ac                              |    8 +-
 tests/lib/Makefile.am                     |    2 +
 tests/lib/dummy/Makefile.am               |   59 ++++
 tests/lib/dummy/backend.vala              |   51 +++
 tests/lib/dummy/persona-store.vala        |   91 ++++++
 11 files changed, 1267 insertions(+), 1 deletions(-)
---
diff --git a/backends/Makefile.am b/backends/Makefile.am
index ec9659a..03628e2 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -1,4 +1,5 @@
 SUBDIRS = \
+       dummy \
        key-file \
        $(NULL)
 
@@ -23,6 +24,7 @@ SUBDIRS += ofono
 endif
 
 DIST_SUBDIRS = \
+       dummy \
        telepathy \
        key-file \
        tracker \
diff --git a/backends/dummy/Makefile.am b/backends/dummy/Makefile.am
new file mode 100644
index 0000000..428b06d
--- /dev/null
+++ b/backends/dummy/Makefile.am
@@ -0,0 +1,64 @@
+BACKEND_NAME = "dummy"
+
+backenddir = $(BACKEND_DIR)/dummy
+backend_LTLIBRARIES = dummy.la
+
+dummy_la_SOURCES = \
+       dummy-backend.vala \
+       dummy-backend-factory.vala \
+       dummy-persona-store.vala \
+       $(NULL)
+
+dummy_la_VALAFLAGS = \
+       $(AM_VALAFLAGS) \
+       $(ERROR_VALAFLAGS) \
+       --vapidir=. \
+       --vapidir=$(top_srcdir)/folks \
+       --pkg folks \
+       --pkg folks-internal \
+       --pkg gee-0.8 \
+       --pkg gio-2.0 \
+       --pkg gobject-2.0 \
+       $(NULL)
+
+dummy_la_CPPFLAGS = \
+       -I$(top_srcdir)/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) \
+       $(GIO_LIBS) \
+       $(GLIB_LIBS) \
+       $(GEE_LIBS) \
+       $(top_builddir)/folks/libfolks.la \
+       $(top_builddir)/folks/libfolks-internal.la \
+       $(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 $(top_srcdir)/git.mk
diff --git a/backends/dummy/dummy-backend-factory.vala b/backends/dummy/dummy-backend-factory.vala
new file mode 100644
index 0000000..98c46c6
--- /dev/null
+++ b/backends/dummy/dummy-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.
+ * Copyright (C) 2012 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: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *          Travis Reitter <travis reitter collabora co uk>
+ *          Philip Withnall <philip tecnocode co uk>
+ *
+ * This file was originally part of Rygel.
+ */
+
+using Folks;
+using Folks.Backends.Dummy;
+
+private BackendFactory _backend_factory = null;
+
+/**
+ * The backend module entry point.
+ */
+public void module_init (BackendStore backend_store)
+{
+  _backend_factory = new BackendFactory (backend_store);
+}
+
+/**
+ * The 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.Dummy.BackendFactory : Object
+{
+  /**
+   * { inheritDoc}
+   */
+  public BackendFactory (BackendStore backend_store)
+    {
+      backend_store.add_backend (new Backend ());
+    }
+}
diff --git a/backends/dummy/dummy-backend.vala b/backends/dummy/dummy-backend.vala
new file mode 100644
index 0000000..25c42a0
--- /dev/null
+++ b/backends/dummy/dummy-backend.vala
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2012 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 GLib;
+using Gee;
+using Folks;
+using Folks.Backends.Dummy;
+
+extern const string BACKEND_NAME;
+
+/* TODO: Had to fix DBusObjectManagerClient constructor in gio-2.0.vapi. */
+
+/**
+ * TODO A backend which loads { link Persona}s from a simple key file in
+ * (XDG_DATA_HOME/folks/) and presents them through a single
+ * { link PersonaStore}.
+ *
+ * @since UNRELEASED
+ */
+public class Folks.Backends.Dummy.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> _persona_stores;
+  private Map<string, PersonaStore> _persona_stores_ro;
+
+  /**
+   * 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._persona_stores_ro; }
+    }
+
+  construct
+    {
+      this._persona_stores = new HashMap<string, PersonaStore> ();
+      this._persona_stores_ro = this._persona_stores.read_only_view;
+    }
+
+  /**
+   * { 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;
+
+          DBusProxyTypeFunc f = null; /* TODO */
+
+          var connection = yield Bus  get (BusType.SESSION);
+          var client = yield DBusObjectManagerClient  new (connection, 
DBusObjectManagerClientFlags.DO_NOT_AUTO_START,
+              "org.freedesktop.Folks.DummyBackend", "/org/freedesktop/Folks/DummyBackend", (owned) f);
+
+          if (client.name_owner == null)
+            {
+              /* TODO: Manager doesn't exist. */
+              return;
+            }
+
+          /* Connect to signals and create the persona stores. */
+          client.object_added.connect (this._dbus_persona_store_added_cb);
+          client.object_removed.connect (this._dbus_persona_store_removed_cb);
+
+          this.freeze_notify ();
+
+          foreach (var persona_store in client.get_objects ())
+            {
+              this._dbus_persona_store_added_cb (persona_store);
+            }
+
+          this.thaw_notify ();
+
+          this._is_prepared = true;
+          this.notify_property ("is-prepared");
+
+          this._is_quiescent = true;
+          this.notify_property ("is-quiescent");
+        }
+      finally
+        {
+          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 == true)
+        {
+          return;
+        }
+
+      try
+        {
+          this._prepare_pending = true;
+
+          foreach (var persona_store in this._persona_stores.values)
+            {
+              this.persona_store_removed (persona_store);
+            }
+
+          this._persona_stores.clear ();
+          this.notify_property ("persona-stores");
+
+          this._is_quiescent = false;
+          this.notify_property ("is-quiescent");
+
+          this._is_prepared = false;
+          this.notify_property ("is-prepared");
+        }
+      finally
+        {
+          this._prepare_pending = false;
+        }
+    }
+
+  private void _dbus_persona_store_added_cb (DBusObject _dbus_store)
+    {
+      var dbus_store = _dbus_store as DummyPersonaStore;
+      assert (dbus_store != null);
+
+      var store = new Dummy.PersonaStore (dbus_store);
+
+      this._persona_stores.set (dbus_store.id, store);
+      this.notify_property ("persona-stores");
+      this.persona_store_added (store);
+    }
+
+  private void _dbus_persona_store_removed_cb (DBusObject _dbus_store)
+    {
+      var dbus_store = _dbus_store as DummyPersonaStore;
+      assert (dbus_store != null);
+
+      PersonaStore store;
+
+      if (this._persona_stores.unset (dbus_store.id, out store) == true)
+        {
+          this.notify_property ("persona-stores");
+          this.persona_store_removed (store);
+        }
+    }
+}
diff --git a/backends/dummy/dummy-persona-store.vala b/backends/dummy/dummy-persona-store.vala
new file mode 100644
index 0000000..1ceaf68
--- /dev/null
+++ b/backends/dummy/dummy-persona-store.vala
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2012 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 GLib;
+using Gee;
+using Folks;
+using Folks.Backends.Dummy;
+
+/* TODO: Share this between dummy-persona-store.vala and persona-store.vala? */
+[DBus (name = "org.freedesktop.Folks.DummyBackend.PersonaStore")]
+public interface DummyPersonaStore : Object
+{
+  public abstract string id { owned get; }
+  public abstract string display_name { owned get; }
+  public abstract PersonaStoreTrust trust_level { get; set; }
+
+  /* These are bool rather than MaybeBool because we never actually use MAYBE */
+  public abstract bool can_add_personas { get; }
+  public abstract bool can_alias_personas { get; }
+  public abstract bool can_group_personas { get; }
+  public abstract bool can_remove_personas { get; }
+
+  public abstract bool is_prepared { get; }
+  public abstract bool is_quiescent { get; }
+
+  public abstract string[] always_writeable_properties { owned get; }
+
+  public abstract async void prepare () throws GLib.Error;
+}
+
+/**
+ * TODO A persona store which is associated with a single simple key file. It will
+ * create a { link Persona} for each of the groups in the key file.
+ *
+ * @since UNRELEASED
+ */
+public class Folks.Backends.Dummy.PersonaStore : Folks.PersonaStore
+{
+  private DummyPersonaStore _dbus_store;
+
+  private HashMap<string, Persona> _personas;
+  private Map<string, Persona> _personas_ro;
+
+  /**
+   * { inheritDoc}
+   *
+   * @since UNRELEASED
+   */
+  public override string type_id { get { return BACKEND_NAME; } }
+
+  /**
+   * Whether this PersonaStore can add { link Folks.Persona}s.
+   *
+   * See { link Folks.PersonaStore.can_add_personas}.
+   *
+   * @since UNRELEASED
+   */
+  public override MaybeBool can_add_personas
+    {
+      get
+        {
+          return this._dbus_store.can_add_personas
+              ? MaybeBool.TRUE : MaybeBool.FALSE;
+        }
+    }
+
+  /**
+   * Whether this PersonaStore can set the alias of { link Folks.Persona}s.
+   *
+   * See { link Folks.PersonaStore.can_alias_personas}.
+   *
+   * @since UNRELEASED
+   */
+  public override MaybeBool can_alias_personas
+    {
+      get
+        {
+          return this._dbus_store.can_alias_personas
+              ? MaybeBool.TRUE : MaybeBool.FALSE;
+        }
+    }
+
+  /**
+   * Whether this PersonaStore can set the groups of { link Folks.Persona}s.
+   *
+   * See { link Folks.PersonaStore.can_group_personas}.
+   *
+   * @since UNRELEASED
+   */
+  public override MaybeBool can_group_personas
+    {
+      get
+        {
+          return this._dbus_store.can_group_personas
+              ? MaybeBool.TRUE : MaybeBool.FALSE;
+        }
+    }
+
+  /**
+   * Whether this PersonaStore can remove { link Folks.Persona}s.
+   *
+   * See { link Folks.PersonaStore.can_remove_personas}.
+   *
+   * @since UNRELEASED
+   */
+  public override MaybeBool can_remove_personas
+    {
+      get
+        {
+          return this._dbus_store.can_remove_personas
+              ? MaybeBool.TRUE : MaybeBool.FALSE;
+        }
+    }
+
+  /**
+   * Whether this PersonaStore has been prepared.
+   *
+   * See { link Folks.PersonaStore.is_prepared}.
+   *
+   * @since UNRELEASED
+   */
+  public override bool is_prepared
+    {
+      get { return this._dbus_store.is_prepared; }
+    }
+
+  /**
+   * Whether this PersonaStore has reached a quiescent state.
+   *
+   * See { link Folks.PersonaStore.is_quiescent}.
+   *
+   * @since UNRELEASED
+   */
+  public override bool is_quiescent
+    {
+      get { return this._dbus_store.is_quiescent; }
+    }
+
+  private string[] _always_writeable_properties;
+
+  /**
+   * { inheritDoc}
+   *
+   * @since UNRELEASED
+   */
+  public override string[] always_writeable_properties
+    {
+      get
+        {
+          this._always_writeable_properties =
+              this._dbus_store.always_writeable_properties;
+          return this._always_writeable_properties;
+        }
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since UNRELEASED
+   */
+  public override Map<string, Persona> personas
+    {
+      get { return this._personas_ro; }
+    }
+
+  construct
+    {
+      this._personas = new HashMap<string, Persona> ();
+      this._personas_ro = this._personas.read_only_view;
+    }
+
+  /**
+   * TODO
+   *
+   * @since UNRELEASED
+   */
+  public PersonaStore (DummyPersonaStore dbus_store)
+    {
+      this._dbus_store = dbus_store;
+
+      Object (id: dbus_store.id,
+          display_name: dbus_store.display_name,
+          trust_level: dbus_store.trust_level);
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since UNRELEASED
+   */
+  public override async void prepare () throws GLib.Error
+    {
+      Internal.profiling_start ("preparing Dummy.PersonaStore (ID: %s)",
+          this.id);
+
+      yield this._dbus_store.prepare ();
+
+      Internal.profiling_end ("preparing Dummy.PersonaStore (ID: %s)", this.id);
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since UNRELEASED
+   */
+  public override async void remove_persona (Folks.Persona persona)
+    {
+      debug ("Removing Persona '%s' (IID '%s', group '%s')", persona.uid,
+          persona.iid, persona.display_id);
+
+      /* TODO */
+    }
+
+  /**
+   * Add a new { link Persona} to the PersonaStore.
+   *
+   * Accepted keys for `details` are:
+   * - PersonaStore.detail_key (PersonaDetail.IM_ADDRESSES)
+   * - PersonaStore.detail_key (PersonaDetail.WEB_SERVICE_ADDRESSES)
+   *
+   * See { link Folks.PersonaStore.add_persona_from_details}.
+   *
+   * @throws Folks.PersonaStoreError.CREATE_FAILED if setting the persona’s
+   * properties failed
+   * @since UNRELEASED
+   */
+  public override async Folks.Persona? add_persona_from_details (
+      HashTable<string, Value?> details) throws Folks.PersonaStoreError
+    {
+      /* TODO */
+      return null;
+    }
+}
diff --git a/backends/dummy/dummy-persona.vala b/backends/dummy/dummy-persona.vala
new file mode 100644
index 0000000..05b471d
--- /dev/null
+++ b/backends/dummy/dummy-persona.vala
@@ -0,0 +1,471 @@
+/*
+ * 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:
+ *       Philip Withnall <philip withnall collabora co uk>
+ */
+
+using GLib;
+using Gee;
+using Folks;
+using Folks.Backends.Kf;
+
+/**
+ * A persona subclass which represents a single persona from a simple key file.
+ *
+ * @since 0.1.13
+ */
+public class Folks.Backends.Kf.Persona : Folks.Persona,
+    AliasDetails,
+    AntiLinkable,
+    ImDetails,
+    WebServiceDetails
+{
+  private HashMultiMap<string, ImFieldDetails> _im_addresses;
+  private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
+  private string _alias = ""; /* must not be null */
+  private const string[] _linkable_properties =
+    {
+      "im-addresses",
+      "web-service-addresses"
+    };
+  private const string[] _writeable_properties =
+    {
+      "alias",
+      "im-addresses",
+      "web-service-addresses",
+      "anti-links"
+    };
+
+  /**
+   * { inheritDoc}
+   */
+  public override string[] linkable_properties
+    {
+      get { return this._linkable_properties; }
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.6.0
+   */
+  public override string[] writeable_properties
+    {
+      get { return this._writeable_properties; }
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.1.15
+   */
+  [CCode (notify = false)]
+  public string alias
+    {
+      get { return this._alias; }
+      set { this.change_alias.begin (value); }
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.6.2
+   */
+  public async void change_alias (string alias) throws PropertyError
+    {
+      /* Deal with badly-behaved callers. */
+      if (alias == null)
+        {
+          alias = "";
+        }
+
+      if (this._alias == alias)
+        {
+          return;
+        }
+
+      debug ("Setting alias of Kf.Persona '%s' to '%s'.", this.uid, alias);
+
+      unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+      key_file.set_string (this.display_id, "__alias", alias);
+      yield ((Kf.PersonaStore) this.store).save_key_file ();
+
+      this._alias = alias;
+      this.notify_property ("alias");
+    }
+
+  /**
+   * { inheritDoc}
+   */
+  [CCode (notify = false)]
+  public MultiMap<string, ImFieldDetails> im_addresses
+    {
+      get { return this._im_addresses; }
+      set { this.change_im_addresses.begin (value); }
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.6.2
+   */
+  public async void change_im_addresses (
+      MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
+    {
+      unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+
+      /* Remove the current IM addresses from the key file */
+      foreach (var protocol1 in this._im_addresses.get_keys ())
+        {
+          try
+            {
+              key_file.remove_key (this.display_id, protocol1);
+            }
+          catch (KeyFileError e1)
+            {
+              /* Ignore the error, since it's just a group or key not found
+               * error. */
+            }
+        }
+
+      /* Add the new IM addresses to the key file and build a normalised
+       * table of them to set as the new property value */
+      var new_im_addresses = new HashMultiMap<string, ImFieldDetails> (
+          null, null,
+           AbstractFieldDetails<string>.hash_static,
+           AbstractFieldDetails<string>.equal_static);
+
+      foreach (var protocol2 in im_addresses.get_keys ())
+        {
+          var addresses = im_addresses.get (protocol2);
+          var normalised_addresses = new HashSet<string> ();
+
+          foreach (var im_fd in addresses)
+            {
+              string normalised_address;
+              try
+                {
+                  normalised_address = ImDetails.normalise_im_address (
+                      im_fd.value, protocol2);
+                }
+               catch (ImDetailsError e2)
+                {
+                  throw new PropertyError.INVALID_VALUE (
+                      /* Translators: this is an error message for if the user
+                       * provides an invalid IM address. The first parameter is
+                       * an IM address (e.g. “foo jabber org”), the second is
+                       * the name of a protocol (e.g. “jabber”) and the third is
+                       * an error message. */
+                      _("Invalid IM address ‘%s’ for protocol ‘%s’: %s"),
+                      im_fd.value, protocol2, e2.message);
+                }
+
+              normalised_addresses.add (normalised_address);
+              var new_im_fd = new ImFieldDetails (normalised_address);
+              new_im_addresses.set (protocol2, new_im_fd);
+            }
+
+          string[] addrs = (string[]) normalised_addresses.to_array ();
+          addrs.length = normalised_addresses.size;
+
+          key_file.set_string_list (this.display_id, protocol2, addrs);
+        }
+
+      /* Get the PersonaStore to save the key file */
+      yield ((Kf.PersonaStore) this.store).save_key_file ();
+
+      this._im_addresses = new_im_addresses;
+      this.notify_property ("im-addresses");
+    }
+
+  /**
+   * { inheritDoc}
+   */
+  [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 0.6.2
+   */
+  public async void change_web_service_addresses (
+      MultiMap<string, WebServiceFieldDetails> web_service_addresses)
+          throws PropertyError
+    {
+      unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+
+      /* Remove the current web service addresses from the key file */
+      foreach (var web_service1 in this._web_service_addresses.get_keys ())
+        {
+          try
+            {
+              key_file.remove_key (this.display_id,
+                  "web-service." + web_service1);
+            }
+          catch (KeyFileError e)
+            {
+              /* Ignore the error, since it's just a group or key not found
+               * error. */
+            }
+        }
+
+      /* Add the new web service addresses to the key file and build a
+       * table of them to set as the new property value */
+      var new_web_service_addresses =
+        new HashMultiMap<string, WebServiceFieldDetails> (
+            null, null,
+             AbstractFieldDetails<string>.hash_static,
+             AbstractFieldDetails<string>.equal_static);
+
+      foreach (var web_service2 in web_service_addresses.get_keys ())
+        {
+          var ws_fds = web_service_addresses.get (web_service2);
+
+          string[] addrs = new string[0];
+          foreach (var ws_fd1 in ws_fds)
+            addrs += ws_fd1.value;
+
+          key_file.set_string_list (this.display_id,
+              "web-service." + web_service2, addrs);
+
+          foreach (var ws_fd2 in ws_fds)
+            new_web_service_addresses.set (web_service2, ws_fd2);
+        }
+
+      /* Get the PersonaStore to save the key file */
+      yield ((Kf.PersonaStore) this.store).save_key_file ();
+
+      this._web_service_addresses = new_web_service_addresses;
+      this.notify_property ("web-service-addresses");
+    }
+
+  private HashSet<string> _anti_links;
+  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
+    {
+      if (Folks.Internal.equal_sets<string> (anti_links, this.anti_links))
+        {
+          return;
+        }
+
+      unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+
+      /* Skip the persona's UID; don't allow reflexive anti-links. */
+      anti_links.remove (this.uid);
+
+      key_file.set_string_list (this.display_id,
+          Kf.PersonaStore.anti_links_key_name, anti_links.to_array ());
+
+      /* Get the PersonaStore to save the key file */
+      yield ((Kf.PersonaStore) this.store).save_key_file ();
+
+      /* Update the stored anti-links. */
+      this._anti_links.clear ();
+      this._anti_links.add_all (anti_links);
+      this.notify_property ("anti-links");
+    }
+
+  /**
+   * Create a new persona.
+   *
+   * Create a new persona for the { link PersonaStore} `store`, representing
+   * the Persona given by the group `uid` in the key file `key_file`.
+   */
+  public Persona (string id, Folks.PersonaStore store)
+    {
+      var iid = store.id + ":" + id;
+      var uid = this.build_uid ("key-file", store.id, id);
+
+      Object (display_id: id,
+              iid: iid,
+              uid: uid,
+              store: store,
+              is_user: false);
+    }
+
+  construct
+    {
+      debug ("Adding key-file Persona '%s' (IID '%s', group '%s')", this.uid,
+          this.iid, this.display_id);
+
+      this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
+          null, null,  AbstractFieldDetails<string>.hash_static,
+           AbstractFieldDetails<string>.equal_static);
+      this._web_service_addresses =
+        new HashMultiMap<string, WebServiceFieldDetails> (
+            null, null,
+             AbstractFieldDetails<string>.hash_static,
+             AbstractFieldDetails<string>.equal_static);
+      this._anti_links = new HashSet<string> ();
+      this._anti_links_ro = this._anti_links.read_only_view;
+
+      /* Load the IM addresses from the key file */
+      unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+
+      try
+        {
+          var keys = key_file.get_keys (this.display_id);
+          foreach (unowned string key in keys)
+            {
+              /* Alias */
+              if (key == "__alias")
+                {
+                  this._alias = key_file.get_string (this.display_id, key);
+
+                  if (this._alias == null)
+                    {
+                      this._alias = "";
+                    }
+
+                  debug ("    Loaded alias '%s'.", this._alias);
+                  continue;
+                }
+
+              /* Anti-links. */
+              if (key == Kf.PersonaStore.anti_links_key_name)
+                {
+                  var anti_link_array =
+                      key_file.get_string_list (this.display_id, key);
+
+                  if (anti_link_array != null)
+                    {
+                      foreach (var anti_link in anti_link_array)
+                        {
+                          this._anti_links.add (anti_link);
+                        }
+
+                      debug ("    Loaded %u anti-links.",
+                          anti_link_array.length);
+                      continue;
+                    }
+                }
+
+              /* Web service addresses */
+              var decomposed_key = key.split(".", 2);
+              if (decomposed_key.length == 2 &&
+                  decomposed_key[0] == "web-service")
+                {
+                  unowned string web_service = decomposed_key[1];
+                  var web_service_addresses = key_file.get_string_list (
+                      this.display_id, web_service);
+
+                  foreach (var web_service_address in web_service_addresses)
+                    {
+                      this._web_service_addresses.set (web_service,
+                          new WebServiceFieldDetails (web_service_address));
+                    }
+
+                  continue;
+                }
+
+              /* IM addresses */
+              unowned string protocol = key;
+              var im_addresses = key_file.get_string_list (
+                  this.display_id, protocol);
+
+              foreach (var im_address in im_addresses)
+                {
+                  string address;
+                  try
+                    {
+                      address = ImDetails.normalise_im_address (im_address,
+                          protocol);
+                    }
+                  catch (ImDetailsError e)
+                    {
+                      /* Warn of and ignore any invalid IM addresses */
+                      warning (e.message);
+                      continue;
+                    }
+
+                  var im_fd = new ImFieldDetails (address);
+                  this._im_addresses.set (protocol, im_fd);
+                }
+            }
+        }
+      catch (KeyFileError e)
+        {
+          /* We get a GROUP_NOT_FOUND exception if we're creating a new
+           * Persona, since it doesn't yet exist in the key file. We shouldn't
+           * get any other exceptions, since we're iterating through a list of
+           * keys we've just retrieved. */
+          if (!(e is KeyFileError.GROUP_NOT_FOUND))
+            {
+              /* Translators: the parameter is an error message. */
+              warning (_("Couldn't load data from key file: %s"), e.message);
+            }
+        }
+    }
+
+  /**
+   * { inheritDoc}
+   */
+  public override void linkable_property_to_links (string prop_name,
+      Folks.Persona.LinkablePropertyCallback callback)
+    {
+      if (prop_name == "im-addresses")
+        {
+          foreach (var protocol in this._im_addresses.get_keys ())
+            {
+              var im_addresses = this._im_addresses.get (protocol);
+
+              foreach (var im_fd in im_addresses)
+                  callback (protocol + ":" + im_fd.value);
+            }
+        }
+      else if (prop_name == "web-service-addresses")
+        {
+          foreach (var web_service in this.web_service_addresses.get_keys ())
+            {
+              var web_service_addresses =
+                  this._web_service_addresses.get (web_service);
+
+              foreach (var ws_fd in web_service_addresses)
+                  callback (web_service + ":" + ws_fd.value);
+            }
+        }
+      else
+        {
+          /* Chain up */
+          base.linkable_property_to_links (prop_name, callback);
+        }
+    }
+}
diff --git a/configure.ac b/configure.ac
index aadec2e..229e294 100644
--- a/configure.ac
+++ b/configure.ac
@@ -263,6 +263,7 @@ AS_IF([test \
         ! -e ${sd}tests/folks/backend_loading_vala.stamp -o \
         ! -e ${sd}tests/key-file/individual_retrieval_vala.stamp -o \
         ! -e ${sd}tests/lib/folks-test.vapi -o \
+        ! -e ${sd}tests/lib/dummy/dummy-test.vapi -o \
         ! -e ${sd}tests/lib/key-file/kf-test.vapi -o \
         ! -e ${sd}tests/lib/key-file/libkf_test_la_vala.stamp -o \
         ! -e ${sd}tests/lib/telepathy/contactlist/tp-test-contactlist.gir -o \
@@ -272,6 +273,7 @@ AS_IF([test \
         ! -e ${sd}tests/telepathy/individual_retrieval_vala.stamp -o \
         ! -e ${sd}tests/telepathy/individual_properties_vala.stamp -o \
         ! -e ${sd}tests/folks/backend_loading_vala.stamp -o \
+        ! -e ${sd}backends/dummy/dummy_la_vala.stamp -o \
         ! -e ${sd}backends/key-file/key_file_la_vala.stamp -o \
         ! -e ${sd}backends/telepathy/telepathy_la_vala.stamp \
                 -o \
@@ -352,6 +354,8 @@ AC_DEFINE([MODULE_VERSION], "folks_module_version", [Module interface version])
 BACKEND_DIR='$(libdir)/folks/$(FOLKS_MODULE_VERSION)/backends'
 AC_SUBST([BACKEND_DIR])
 
+BACKEND_DUMMY='$(top_builddir)/backends/dummy/.libs/dummy.so'
+AC_SUBST([BACKEND_DUMMY])
 BACKEND_KF='$(top_builddir)/backends/key-file/.libs/key-file.so'
 AC_SUBST([BACKEND_KF])
 BACKEND_TP='$(top_builddir)/backends/telepathy/.libs/telepathy.so'
@@ -378,7 +382,7 @@ if test x$enable_ofono_backend = xyes; then
 fi
 
 # 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_DUMMY):$(BACKEND_KF):$(BACKEND_TP)'
 AS_IF([test x$have_libsocialweb_backend = xyes], [
   LIBSOCIALWEB_BACKEND_UNINST_PATH='$(BACKEND_LIBSOCIALWEB)'
   BACKEND_UNINST_PATH="$BACKEND_UNINST_PATH:$LIBSOCIALWEB_BACKEND_UNINST_PATH"
@@ -634,6 +638,7 @@ AC_CONFIG_FILES([
     folks/org.freedesktop.folks.gschema.xml
     Makefile
     backends/Makefile
+    backends/dummy/Makefile
     backends/key-file/Makefile
     backends/libsocialweb/Makefile
     backends/libsocialweb/lib/Makefile
@@ -659,6 +664,7 @@ AC_CONFIG_FILES([
     tests/tracker/Makefile
     tests/lib/Makefile
     tests/lib/folks-test-uninstalled.pc
+    tests/lib/dummy/Makefile
     tests/lib/eds/Makefile
     tests/lib/key-file/Makefile
     tests/lib/libsocialweb/Makefile
diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am
index bcaf81d..f11ed70 100644
--- a/tests/lib/Makefile.am
+++ b/tests/lib/Makefile.am
@@ -1,4 +1,5 @@
 SUBDIRS = \
+       dummy \
        key-file \
        $(NULL)
 
@@ -19,6 +20,7 @@ SUBDIRS += eds
 endif
 
 DIST_SUBDIRS = \
+       dummy \
        key-file \
        telepathy \
        eds \
diff --git a/tests/lib/dummy/Makefile.am b/tests/lib/dummy/Makefile.am
new file mode 100644
index 0000000..581dd0e
--- /dev/null
+++ b/tests/lib/dummy/Makefile.am
@@ -0,0 +1,59 @@
+noinst_LTLIBRARIES = libdummy-test.la
+
+libdummy_test_la_SOURCES = \
+       backend.vala \
+       persona-store.vala \
+       $(NULL)
+
+libdummy_test_la_VALAFLAGS = \
+       $(AM_VALAFLAGS) \
+       $(ERROR_VALAFLAGS) \
+       --library=dummy-test \
+       --header=dummy-test.h \
+       --vapidir=. \
+       --vapidir=$(top_srcdir)/folks \
+       --pkg folks \
+       --pkg folks-internal \
+       --pkg gee-0.8 \
+       --pkg gio-2.0 \
+       --pkg gobject-2.0 \
+       $(NULL)
+
+libdummy_test_la_CPPFLAGS = \
+       -I$(top_srcdir)/folks \
+       -include $(CONFIG_HEADER) \
+       -DPACKAGE_DATADIR=\"$(pkgdatadir)\" \
+       -DG_LOG_DOMAIN=\"$(BACKEND_NAME)\" \
+       $(NULL)
+
+libdummy_test_la_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(GIO_CFLAGS) \
+       $(GLIB_CFLAGS) \
+       $(GEE_CFLAGS) \
+       $(NULL)
+
+libdummy_test_la_LIBADD = \
+       $(AM_LIBADD) \
+       $(GIO_LIBS) \
+       $(GLIB_LIBS) \
+       $(GEE_LIBS) \
+       $(top_builddir)/folks/libfolks.la \
+       $(NULL)
+
+MAINTAINERCLEANFILES = \
+       dummy-test.vapi \
+       dummy-test.h \
+       $(NULL)
+
+EXTRA_DIST = \
+       dummy-test.vapi \
+       dummy-test.h \
+       $(NULL)
+
+GITIGNOREFILES = \
+       $(libdummy_test_la_SOURCES:.vala=.c) \
+       libdummy_test_la_vala.stamp \
+       $(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/tests/lib/dummy/backend.vala b/tests/lib/dummy/backend.vala
new file mode 100644
index 0000000..c7bd3d2
--- /dev/null
+++ b/tests/lib/dummy/backend.vala
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012 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>
+ */
+
+public class DummyTest.Backend
+{
+  private DBusObjectManagerServer _manager;
+
+  public async void set_up () throws GLib.Error
+    {
+      /* Create a new D-Bus object manager for our persona stores. */
+      this._manager =
+          new DBusObjectManagerServer ("/org/freedesktop/Folks/DummyBackend");
+      var connection = yield Bus  get (BusType.SESSION);
+
+      /* Export the manager on the bus. */
+      this._manager.connection = connection;
+    }
+
+  public async void tear_down () throws GLib.Error
+    {
+      /* Remove the manager from the bus. */
+      this._manager.connection = null;
+    }
+
+  public void export_persona_store (PersonaStore persona_store)
+    {
+      this._manager.export (persona_store);
+    }
+
+  public void unexport_persona_store (PersonaStore persona_store)
+    {
+      this._manager.unexport (persona_store.g_object_path);
+    }
+}
diff --git a/tests/lib/dummy/persona-store.vala b/tests/lib/dummy/persona-store.vala
new file mode 100644
index 0000000..045e44b
--- /dev/null
+++ b/tests/lib/dummy/persona-store.vala
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2012 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 GLib;
+using Gee;
+using Folks;
+
+[DBus (name = "org.freedesktop.Folks.DummyBackend.PersonaStore")]
+public interface DummyPersonaStore : Object
+{
+  public abstract string id { owned get; }
+  public abstract string display_name { owned get; }
+  public abstract PersonaStoreTrust trust_level { get; set; }
+
+  /* These are bool rather than MaybeBool because we never actually use MAYBE */
+  public abstract bool can_add_personas { get; }
+  public abstract bool can_alias_personas { get; }
+  public abstract bool can_group_personas { get; }
+  public abstract bool can_remove_personas { get; }
+
+  public abstract bool is_prepared { get; }
+  public abstract bool is_quiescent { get; }
+
+  public abstract string[] always_writeable_properties { owned get; }
+
+  public abstract async void prepare () throws GLib.Error;
+}
+
+[DBus (name = "org.freedesktop.Folks.DummyBackend.PersonaStore")]
+public class DummyTest.PersonaStore : DBusObjectSkeleton
+{
+  private string _id;
+  public string id { owned get { return this._id; } }
+
+  private string _display_name;
+  public string display_name { owned get { return this._display_name; } }
+
+  public PersonaStoreTrust trust_level { get; set; }
+
+  /* These are bool rather than MaybeBool because we never actually use MAYBE */
+  private bool _can_add_personas;
+  public bool can_add_personas { get { return this._can_add_personas; } }
+
+  private bool _can_alias_personas;
+  public bool can_alias_personas { get { return this._can_alias_personas; } }
+
+  private bool _can_group_personas;
+  public bool can_group_personas { get { return this._can_group_personas; } }
+
+  private bool _can_remove_personas;
+  public bool can_remove_personas { get { return this._can_remove_personas; } }
+
+  private bool _is_prepared;
+  public bool is_prepared { get { return this._is_prepared; } }
+
+  private bool _is_quiescent;
+  public bool is_quiescent { get { return this._is_quiescent; } }
+
+  private string[] _always_writeable_properties;
+  public string[] always_writeable_properties { owned get { return this._always_writeable_properties /* TODO 
*/; } }
+
+  public PersonaStore (string id, string display_name)
+    {
+      Object (g_object_path: "/org/freedesktop/Folks/DummyBackend/" + id);
+
+      this._id = id;
+      this._display_name = display_name;
+    }
+
+  public async void prepare () throws GLib.Error
+    {
+      message ("TODO");
+    }
+}



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