[folks/wip/arbitrary-field-interface] Add ExtendedInfo interface and implementation in EDS backend



commit 6223e5c7781ab9f6bb1c8cba5354563c3f3e3850
Author: Rodrigo Moya <rodrigo gnome-db org>
Date:   Wed Jul 3 12:57:25 2013 +0200

    Add ExtendedInfo interface and implementation in EDS backend
    
    Implement ExtendedInfo in Individual
    
    Add ExtendedInfo interface EDS test
    
    https://bugzilla.gnome.org/show_bug.cgi?id=641211

 backends/eds/lib/edsf-persona-store.vala |   48 +++++++++++
 backends/eds/lib/edsf-persona.vala       |   22 +++++
 folks/Makefile.am                        |    1 +
 folks/extended-info.vala                 |  114 ++++++++++++++++++++++++++
 folks/individual.vala                    |   77 +++++++++++++++++
 folks/persona-store.vala                 |   10 ++-
 po/POTFILES.in                           |    1 +
 po/POTFILES.skip                         |    1 +
 tests/eds/Makefile.am                    |    5 +
 tests/eds/extended-info.vala             |  131 ++++++++++++++++++++++++++++++
 10 files changed, 409 insertions(+), 1 deletions(-)
---
diff --git a/backends/eds/lib/edsf-persona-store.vala b/backends/eds/lib/edsf-persona-store.vala
index 9f9a400..c439b1c 100644
--- a/backends/eds/lib/edsf-persona-store.vala
+++ b/backends/eds/lib/edsf-persona-store.vala
@@ -888,6 +888,9 @@ public class Edsf.PersonaStore : Folks.PersonaStore
                     }
                 }
 
+              /* Add extended_info field */
+              prop_set.add ((!) Folks.PersonaStore.detail_key (PersonaDetail.EXTENDED_INFO));
+
               /* Convert the property set to an array. We can't use .to_array()
                * here because it fails to null-terminate the array. Sigh. */
               this._always_writeable_properties = new string[prop_set.size];
@@ -1597,6 +1600,51 @@ public class Edsf.PersonaStore : Folks.PersonaStore
       yield this._commit_modified_property (persona, "email-addresses");
     }
 
+  internal ExtendedFieldDetails? _get_extended_field (Edsf.Persona persona, string name)
+    {
+      unowned VCardAttribute? attr = persona.contact.get_attribute (name);
+      if (attr != null)
+        {
+          ExtendedFieldDetails details = new ExtendedFieldDetails (attr.get_value (), null);
+
+          foreach (unowned E.VCardAttributeParam param in attr.get_params ())
+            {
+              string param_name = param.get_name ();
+              foreach (unowned string param_value in param.get_values ())
+                {
+                  details.add_parameter (param_name, param_value);
+                }
+            }
+
+           return details;
+        }
+
+      return null;
+    }
+
+  internal async void _change_extended_field (Edsf.Persona persona,
+      string name, ExtendedFieldDetails details) throws PropertyError
+    {
+      var vcard = (E.VCard) persona.contact;
+      unowned E.VCardAttribute? prev_attr = vcard.get_attribute (name);
+
+      if (prev_attr != null)
+        persona.contact.remove_attribute (prev_attr);
+
+      E.VCardAttribute new_attr = new E.VCardAttribute ("", name);
+      new_attr.add_value (details.value);
+
+      persona.contact.add_attribute (new_attr);
+
+      try
+        {
+          yield ((!) this._addressbook).modify_contact (persona.contact, null);
+        }
+      catch (GLib.Error e)
+        {
+        }
+    }
+
   internal async void _set_phones (Edsf.Persona persona,
       Set<PhoneFieldDetails> phones) throws PropertyError
     {
diff --git a/backends/eds/lib/edsf-persona.vala b/backends/eds/lib/edsf-persona.vala
index 4a8d505..ef7266a 100644
--- a/backends/eds/lib/edsf-persona.vala
+++ b/backends/eds/lib/edsf-persona.vala
@@ -40,6 +40,7 @@ public class Edsf.Persona : Folks.Persona,
     AvatarDetails,
     BirthdayDetails,
     EmailDetails,
+    ExtendedInfo,
     FavouriteDetails,
     GenderDetails,
     GroupDetails,
@@ -362,6 +363,27 @@ public class Edsf.Persona : Folks.Persona,
           email_addresses);
     }
 
+  /**
+   * { inheritDoc}
+   *
+   * @since UNRELEASED
+   */
+  public ExtendedFieldDetails? get_extended_field (string name)
+    {
+      return ((Edsf.PersonaStore) this.store)._get_extended_field (this, name);
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since UNRELEASED
+   */
+  public async void change_extended_field (
+      string name, ExtendedFieldDetails value) throws PropertyError
+    {
+      yield ((Edsf.PersonaStore) this.store)._change_extended_field (this, name, value);
+    }
+
   private SmallSet<NoteFieldDetails>? _notes = null;
   private Set<NoteFieldDetails>? _notes_ro = null;
 
diff --git a/folks/Makefile.am b/folks/Makefile.am
index a4af6aa..cfcc200 100644
--- a/folks/Makefile.am
+++ b/folks/Makefile.am
@@ -77,6 +77,7 @@ libfolks_la_SOURCES = \
        backend-store.vala \
        birthday-details.vala \
        email-details.vala \
+       extended-info.vala \
        favourite-details.vala \
        folks-namespace.vala \
        gender-details.vala \
diff --git a/folks/extended-info.vala b/folks/extended-info.vala
new file mode 100644
index 0000000..4652c48
--- /dev/null
+++ b/folks/extended-info.vala
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2013 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:
+ *       Rodrigo Moya <rodrigo gnome org>
+ */
+
+using GLib;
+using Gee;
+
+/**
+ * Object representing an arbitrary field that can have some parameters
+ * associated with it.
+ *
+ * See { link Folks.AbstractFieldDetails} for details on common parameter names
+ * and values.
+ *
+ * @since UNRELEASED
+ */
+public class Folks.ExtendedFieldDetails : AbstractFieldDetails<string>
+{
+  /**
+   * Create a new ExtendedFieldDetails.
+   *
+   * @param value the value of the field
+   * @param parameters initial parameters. See
+   * { link AbstractFieldDetails.parameters}. A ``null`` value is equivalent to
+   * an empty map of parameters.
+   *
+   * @return a new ExtendedFieldDetails
+   *
+   * @since UNRELEASED
+   */
+  public ExtendedFieldDetails (string value,
+                               MultiMap<string, string>? parameters = null)
+    {
+      if (value == "")
+        {
+          warning ("Empty value passed to ExtendedFieldDetails.");
+        }
+
+      this.value = value;
+      if (parameters != null)
+        this.parameters = (!) parameters;
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.6.0
+   */
+  public override bool equal (AbstractFieldDetails<string> that)
+    {
+      return base.equal (that);
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since 0.6.0
+   */
+  public override uint hash ()
+    {
+      return base.hash ();
+    }
+}
+
+/**
+ * Arbitrary field interface.
+ *
+ * This interface allows clients to store arbitrary fields for contacts in backends
+ * that support it.
+ *
+ * @since UNRELEASED
+ */
+public interface Folks.ExtendedInfo : Object
+{
+  /**
+   * Retrieve the value for an arbitrary field.
+   *
+   * @since UNRELEASED
+   */
+  public abstract ExtendedFieldDetails? get_extended_field (string name);
+
+  /**
+   * Change the value of an arbitrary field.
+   *
+   * @param name name of the arbitrary field to change value
+   * @param value new value for the arbitrary field
+   * @throws PropertyError if setting the value failed
+   *
+   * @since UNRELEASED
+   */
+  public virtual async void change_extended_field (
+      string name, ExtendedFieldDetails value) throws PropertyError
+    {
+      /* Default implementation */
+      throw new PropertyError.NOT_WRITEABLE (
+          _("Extended fields are not writeable on this contact."));
+    }
+}
diff --git a/folks/individual.vala b/folks/individual.vala
index d090cbd..a35c742 100644
--- a/folks/individual.vala
+++ b/folks/individual.vala
@@ -86,6 +86,7 @@ public class Folks.Individual : Object,
     AvatarDetails,
     BirthdayDetails,
     EmailDetails,
+    ExtendedInfo,
     FavouriteDetails,
     GenderDetails,
     GroupDetails,
@@ -984,6 +985,82 @@ public class Folks.Individual : Object,
     }
 
   /**
+   * { inheritDoc}
+   *
+   * @since UNRELEASED
+   */
+  public ExtendedFieldDetails? get_extended_field (string name)
+    {
+      debug ("Getting extended field '%s' on '%s'ā€¦", name, this.id);
+
+      /* Try to get it from the writeable Personas which have "extended-info"
+       * as a writeable property. */
+      foreach (var p in this._persona_set)
+        {
+          if ("extended-info" in p.writeable_properties)
+            {
+              var e = p as ExtendedInfo;
+              return e.get_extended_field (name);
+            }
+        }
+
+      return null;
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since UNRELEASED
+   */
+  public async void change_extended_field (
+      string name, ExtendedFieldDetails value) throws PropertyError
+    {
+      debug ("Setting extended field '%s' on '%s'ā€¦", name, this.id);
+
+      PropertyError? persona_error = null;
+      var prop_changed = false;
+
+      /* Try to write it to only the writeable Personas which have "extended-info"
+       * as a writeable property. */
+      foreach (var p in this._persona_set)
+        {
+          if ("extended-info" in p.writeable_properties)
+            {
+              var e = p as ExtendedInfo;
+              try
+                {
+                  yield e.change_extended_field (name, value);
+                  debug ("    written to writeable persona '%s'", p.uid);
+                  prop_changed = true;
+                }
+              catch (PropertyError e)
+                {
+                  /* Store the first error so we can throw it if setting the
+                   * nickname fails on every other persona. */
+                  if (persona_error == null)
+                    {
+                      persona_error = e;
+                    }
+                }
+            }
+        }
+
+      /* Failure? Changing the property failed on every suitable persona found
+       * (and potentially zero suitable personas were found). */
+      if (prop_changed == false)
+        {
+          if (persona_error == null)
+            {
+              persona_error = new PropertyError.NOT_WRITEABLE (
+                  _("Failed to change property ā€˜%sā€™: No suitable personas were found."),
+                  "extended_info");
+            }
+
+          throw persona_error;
+        }
+    }
+
+  /**
    * The set of { link Persona}s encapsulated by this Individual.
    *
    * There must always be at least one Persona in this set.
diff --git a/folks/persona-store.vala b/folks/persona-store.vala
index 400e3d7..bf437f1 100644
--- a/folks/persona-store.vala
+++ b/folks/persona-store.vala
@@ -312,6 +312,13 @@ public enum Folks.PersonaDetail
    * @since 0.7.3
    */
   ANTI_LINKS,
+
+  /**
+   * Field for { link ExtendedFieldDetails}.
+   *
+   * @since UNRELEASED
+   */
+  EXTENDED_INFO,
 }
 
 /**
@@ -372,7 +379,8 @@ public abstract class Folks.PersonaStore : Object
     "last-im-interaction-datetime",
     "call-interaction-count",
     "last-call-interaction-datetime",
-    "anti-links"
+    "anti-links",
+    "extended-info"
   };
 
   /**
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 8d8e71a..17ab450 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -17,6 +17,7 @@ folks/avatar-details.vala
 folks/backend-store.vala
 folks/birthday-details.vala
 folks/email-details.vala
+folks/extended-info.vala
 folks/favourite-details.vala
 folks/gender-details.vala
 folks/group-details.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index d7a74b0..cb7fa2a 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -16,6 +16,7 @@ folks/avatar-details.c
 folks/backend-store.c
 folks/birthday-details.c
 folks/email-details.c
+folks/extended-info.c
 folks/favourite-details.c
 folks/gender-details.c
 folks/group-details.c
diff --git a/tests/eds/Makefile.am b/tests/eds/Makefile.am
index 1f2d346..fcafa97 100644
--- a/tests/eds/Makefile.am
+++ b/tests/eds/Makefile.am
@@ -76,6 +76,7 @@ TESTS = \
        enable-disable-stores \
        set-is-favourite \
        perf \
+       extended-info \
        $(NULL)
 
 noinst_PROGRAMS = \
@@ -225,6 +226,10 @@ perf_SOURCES = \
        perf.vala \
        $(NULL)
 
+extended_info_SOURCES = \
+       extended-info.vala
+       $(NULL)
+
 helper_create_many_contacts_SOURCES = \
        helper-create-many-contacts.vala \
        $(NULL)
diff --git a/tests/eds/extended-info.vala b/tests/eds/extended-info.vala
new file mode 100644
index 0000000..fa3e122
--- /dev/null
+++ b/tests/eds/extended-info.vala
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2013 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: Rodrigo Moya <rodrigo moya collabora co uk>
+ *
+ */
+
+using EdsTest;
+using Folks;
+using Gee;
+
+public class ExtendedInfoTests : EdsTest.TestCase
+{
+  private GLib.MainLoop _main_loop;
+  private IndividualAggregator _aggregator;
+  private string _full_name;
+  private bool _found_field_1;
+  private bool _found_field_2;
+
+  public ExtendedInfoTests ()
+    {
+      base ("ExtendedInfo");
+
+      this.add_test ("extended info interface", this.test_extended_info);
+    }
+
+  public void test_extended_info ()
+    {
+      this._main_loop = new GLib.MainLoop (null, false);
+      this._full_name = "persona #1";
+      Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
+      Value? v;
+
+      this.eds_backend.reset ();
+
+      v = Value (typeof (string));
+      v.set_string (this._full_name);
+      c1.set ("full_name", (owned) v);
+      v = Value (typeof (string));
+      v.set_string ("X-FIELD-1=value1,X-FIELD-2=value2");
+      c1.set ("extended_info", (owned) v);
+
+      this.eds_backend.add_contact (c1);
+
+      this._found_field_1 = false;
+      this._found_field_2 = false;
+
+      this._test_extended_info_async.begin ();
+
+      TestUtils.loop_run_with_timeout (this._main_loop);
+
+      assert (this._found_field_1 == true);
+      assert (this._found_field_2 == true);
+    }
+
+  private async void _test_extended_info_async ()
+    {
+      yield this.eds_backend.commit_contacts_to_addressbook ();
+
+      var store = BackendStore.dup ();
+      yield store.prepare ();
+      this._aggregator = new IndividualAggregator ();
+      this._aggregator.individuals_changed_detailed.connect
+          (this._individuals_changed_cb);
+      try
+        {
+          yield this._aggregator.prepare ();
+        }
+      catch (GLib.Error e)
+        {
+          GLib.warning ("Error when calling prepare: %s\n", e.message);
+        }
+    }
+
+  private void _individuals_changed_cb (
+       MultiMap<Individual?, Individual?> changes)
+    {
+      var added = changes.get_values ();
+      var removed = changes.get_keys ();
+
+      foreach (var i in added)
+        {
+          assert (i != null);
+
+          string full_name = i.full_name;
+          if (full_name == this._full_name);
+            {
+              if (i.get_extended_field ("X-FIELD-1") != null)
+                this._found_field_1 = true;
+              if (i.get_extended_field ("X-FIELD-2") != null)
+                this._found_field_2 = true;
+            }
+        }
+
+      assert (removed.size == 1);
+
+      foreach (var i in removed)
+        {
+          assert (i == null);
+        }
+
+      if (this._found_field_1 == true &&
+         this._found_field_2 == true)
+        this._main_loop.quit ();
+    }
+}
+
+public int main (string[] args)
+{
+  Test.init (ref args);
+
+  var tests = new ExtendedInfoTests ();
+  tests.register ();
+  Test.run ();
+  tests.final_tear_down ();
+
+  return 0;
+}


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