[folks] support geo location information



commit 75ff3f87dc89ed760382267d76823aaaebf7a718
Author: Patrick Ohly <patrick ohly intel com>
Date:   Fri Feb 8 15:23:13 2013 +0100

    support geo location information
    
    This adds "Location" class with latitude and longitude and
    single-valued "LocationDetails" which exposes that information for a
    contact. The logic for choosing "the best" among many locations from
    different personas is left out for now. Instead folks picks according
    to the default ordering of single values, which prefers values from
    the primary store.
    
    The EDS backend provides, creates and writes the GEO property as
    location.

 backends/eds/lib/edsf-persona-store.vala |   37 ++++++++-
 backends/eds/lib/edsf-persona.vala       |   61 ++++++++++++++
 folks/Makefile.am                        |    1 +
 folks/individual.vala                    |   49 +++++++++++-
 folks/location-details.vala              |  131 ++++++++++++++++++++++++++++++
 folks/persona-store.vala                 |    8 ++
 po/POTFILES.in                           |    1 +
 7 files changed, 285 insertions(+), 3 deletions(-)
---
diff --git a/backends/eds/lib/edsf-persona-store.vala b/backends/eds/lib/edsf-persona-store.vala
index 6452230..3d6d37b 100644
--- a/backends/eds/lib/edsf-persona-store.vala
+++ b/backends/eds/lib/edsf-persona-store.vala
@@ -386,6 +386,7 @@ public class Edsf.PersonaStore : Folks.PersonaStore
    * - PersonaStore.detail_key (PersonaDetail.ROLES)
    * - PersonaStore.detail_key (PersonaDetail.STRUCTURED_NAME)
    * - PersonaStore.detail_key (PersonaDetail.LOCAL_IDS)
+   * - PersonaStore.detail_key (PersonaDetail.LOCATION)
    * - PersonaStore.detail_key (PersonaDetail.WEB_SERVICE_ADDRESSES)
    * - PersonaStore.detail_key (PersonaDetail.NOTES)
    * - PersonaStore.detail_key (PersonaDetail.URLS)
@@ -503,6 +504,11 @@ public class Edsf.PersonaStore : Folks.PersonaStore
               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.LOCATION))
+            {
+              var location = (Location?) v.get_object ();
+              this._set_contact_location (contact, location);
+            }
           else if (k == Folks.PersonaStore.detail_key
               (PersonaDetail.WEB_SERVICE_ADDRESSES))
             {
@@ -1162,6 +1168,8 @@ public class Edsf.PersonaStore : Folks.PersonaStore
           case ContactField.FAMILY_NAME:
           case ContactField.NAME:
             return PersonaDetail.STRUCTURED_NAME;
+          case ContactField.GEO:
+            return PersonaDetail.LOCATION;
           case ContactField.NICKNAME:
             return PersonaDetail.NICKNAME;
           case ContactField.EMAIL_1:
@@ -1304,7 +1312,6 @@ public class Edsf.PersonaStore : Folks.PersonaStore
           case ContactField.LIST_SHOW_ADDRESSES:
           case ContactField.ANNIVERSARY:
           case ContactField.X509_CERT:
-          case ContactField.GEO:
           default:
             debug ("Unsupported/Unknown EDS field name '%s'.", eds_field_name);
             return PersonaDetail.INVALID;
@@ -2246,6 +2253,34 @@ public class Edsf.PersonaStore : Folks.PersonaStore
         }
     }
 
+  internal async void _set_location (Edsf.Persona persona,
+      Location? location) throws PropertyError
+    {
+      if (!("location" in this._always_writeable_properties))
+        {
+          throw new PropertyError.NOT_WRITEABLE (
+              _("Location is not writeable on this contact."));
+        }
+
+      this._set_contact_location (persona.contact, location);
+      yield this._commit_modified_property (persona, "location");
+    }
+
+  private void _set_contact_location (E.Contact contact, Location? location)
+    {
+      if (location == null)
+        {
+          this._remove_attribute (contact, "GEO");
+        }
+      else
+        {
+          E.ContactGeo geo = new E.ContactGeo ();
+          geo.latitude = location.latitude;
+          geo.longitude = location.longitude;
+          contact.set (ContactField.GEO, geo);
+        }
+    }
+
   private void _contacts_added_cb (GLib.List<E.Contact> contacts)
     {
       HashSet<Persona> added_personas;
diff --git a/backends/eds/lib/edsf-persona.vala b/backends/eds/lib/edsf-persona.vala
index b082ae0..f6c02b2 100644
--- a/backends/eds/lib/edsf-persona.vala
+++ b/backends/eds/lib/edsf-persona.vala
@@ -45,6 +45,7 @@ public class Edsf.Persona : Folks.Persona,
     GroupDetails,
     ImDetails,
     LocalIdDetails,
+    LocationDetails,
     NameDetails,
     NoteDetails,
     PhoneDetails,
@@ -225,6 +226,29 @@ public class Edsf.Persona : Folks.Persona,
       yield ((Edsf.PersonaStore) this.store)._set_local_ids (this, local_ids);
     }
 
+  private Location? _location = null;
+  /**
+   * { inheritDoc}
+   *
+   * @since UNRELEASED
+   */
+  [CCode (notify = false)]
+  public Location? location
+    {
+      get { return this._location; }
+      set { this.change_location.begin (value); }
+    }
+
+  /**
+   * { inheritDoc}
+   *
+   * @since UNRELEASED
+   */
+  public async void change_location (Location? location) throws PropertyError
+    {
+      yield ((Edsf.PersonaStore) this.store)._set_location (this, location);
+    }
+
   private HashSet<PostalAddressFieldDetails>? _postal_addresses = null;
   private Set<PostalAddressFieldDetails>? _postal_addresses_ro = null;
 
@@ -958,6 +982,7 @@ public class Edsf.Persona : Folks.Persona,
       this._postal_addresses_ro = this._postal_addresses.read_only_view;
       this._local_ids = new HashSet<string> ();
       this._local_ids_ro = this._local_ids.read_only_view;
+      this._location = null;
       this._web_service_addresses =
         new HashMultiMap<string, WebServiceFieldDetails> (
           null, null, AbstractFieldDetails<string>.hash_static,
@@ -1060,6 +1085,7 @@ public class Edsf.Persona : Folks.Persona,
       this._update_groups (false);
       this._update_notes (false);
       this._update_local_ids ();
+      this._update_location ();
       this._update_web_services_addresses ();
       this._update_gender ();
       this._update_birthday ();
@@ -2104,6 +2130,41 @@ public class Edsf.Persona : Folks.Persona,
         }
     }
 
+  private void _update_location ()
+    {
+      var _geo = this._get_property<E.ContactGeo> ("geo");
+
+      if (_geo != null)
+        {
+          if (this._location == null ||
+              // Report all changes to the location because someone must
+              // have changed the values intentionally.
+              !this._location.equal_coordinates (_geo.latitude, _geo.longitude))
+            {
+              if (this._location != null)
+                {
+                  // Update existing instance instead of destroying it
+                  // and creating a new one. Minimizes memory thrashing.
+                  this._location.latitude = _geo.latitude;
+                  this._location.longitude = _geo.longitude;
+                }
+              else
+                {
+                  this._location = new Location (_geo.latitude, _geo.longitude);
+                }
+              this.notify_property ("location");
+            }
+        }
+      else
+        {
+          if (this._location != null)
+            {
+              this._location = null;
+              this.notify_property ("location");
+            }
+        }
+    }
+
   private void _update_favourite ()
     {
       bool is_fav = false;
diff --git a/folks/Makefile.am b/folks/Makefile.am
index d24c7f5..0e3da46 100644
--- a/folks/Makefile.am
+++ b/folks/Makefile.am
@@ -73,6 +73,7 @@ libfolks_la_SOURCES = \
        im-details.vala \
        interaction-details.vala \
        local-id-details.vala \
+       location-details.vala \
        name-details.vala \
        note-details.vala \
        phone-details.vala \
diff --git a/folks/individual.vala b/folks/individual.vala
index d3fafed..a6b8e6c 100644
--- a/folks/individual.vala
+++ b/folks/individual.vala
@@ -92,6 +92,7 @@ public class Folks.Individual : Object,
     ImDetails,
     InteractionDetails,
     LocalIdDetails,
+    LocationDetails,
     NameDetails,
     NoteDetails,
     PresenceDetails,
@@ -579,6 +580,17 @@ public class Folks.Individual : Object,
       set { this.change_local_ids.begin (value); } /* not writeable */
     }
 
+  private Location? _location = null;
+  /**
+   * { inheritDoc}
+   */
+  [CCode (notify = false)]
+  public Location? location
+    {
+      get { return this._location; }
+      set { this.change_location.begin (value); } /* not writeable */
+    }
+
   private DateTime? _birthday = null;
 
   /**
@@ -1053,6 +1065,11 @@ public class Folks.Individual : Object,
       this._update_local_ids (false);
     }
 
+  private void _notify_location_cb ()
+    {
+      this._update_location ();
+    }
+
   /**
    * Add or remove the Individual from the specified group.
    *
@@ -1257,6 +1274,7 @@ public class Folks.Individual : Object,
       this._update_notes (false);
       this._update_postal_addresses (false);
       this._update_local_ids (false);
+      this._update_location ();
     }
 
   /* Delegate to update the value of a property on this individual from the
@@ -1841,7 +1859,7 @@ public class Folks.Individual : Object,
           (this._notify_postal_addresses_cb);
       persona.notify["local-ids"].connect
           (this._notify_local_ids_cb);
-
+      persona.notify["location"].connect (this._notify_location_cb);
 
       if (persona is GroupDetails)
         {
@@ -1981,7 +1999,7 @@ public class Folks.Individual : Object,
       persona.notify["postal-addresses"].disconnect
           (this._notify_postal_addresses_cb);
       persona.notify["local-ids"].disconnect (this._notify_local_ids_cb);
-
+      persona.notify["location"].disconnect (this._notify_location_cb);
 
       if (persona is GroupDetails)
         {
@@ -2303,6 +2321,33 @@ public class Folks.Individual : Object,
             }, emit_notification, force_update);
     }
 
+  private void _update_location ()
+    {
+      this._update_single_valued_property (typeof (LocationDetails), (p) =>
+        {
+          return ((LocationDetails) p).location != null;
+        }, (a, b) =>
+        {
+          // TODO (https://bugzilla.gnome.org/show_bug.cgi?id=627400): pick the "better" location 
information. For now, pick more or less randomly.
+          return 0;
+        }, "location", (p) =>
+        {
+          unowned Location? new_location = null;
+
+          if (p != null)
+            {
+              new_location = ((LocationDetails) p).location;
+            }
+
+          if ((new_location == null) != (this.location == null) /* adding or removing a location? */ ||
+              new_location != null && !new_location.equal (this.location) /* different value? */)
+            {
+              this._location = new_location;
+              this.notify_property ("location");
+            }
+        });
+    }
+
   private void _update_postal_addresses (bool create_if_not_exist, bool emit_notification = true, bool 
force_update = true)
     {
       /* FIXME: Detect duplicates somehow? */
diff --git a/folks/location-details.vala b/folks/location-details.vala
new file mode 100644
index 0000000..c913e23
--- /dev/null
+++ b/folks/location-details.vala
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2013 Intel Corp
+ *
+ * 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:
+ *       Patrick Ohly <patrick ohly intel com>
+ */
+
+using GLib;
+
+/**
+ * A location. Typically latitude and longitude will
+ * be based on WGS84. However, folks often has no
+ * way of verifying that and just has to assume
+ * that.
+ *
+ * @since UNRELEASED
+ */
+public class Folks.Location : Object
+{
+  /**
+   * The latitude.
+   *
+   * @since UNRELEASED
+   */
+ public double latitude;
+  /**
+   * The longitude.
+   *
+   * @since UNRELEASED
+   */
+ public double longitude;
+
+  /**
+   * Constructs a new instance with the given coordinates.
+   * @param latitude latitude of the new instance
+   * @param longitude longitude of the new instance
+   * @since UNRELEASED
+   */
+ public Location (double latitude, double longitude)
+ {
+   this.latitude = latitude;
+   this.longitude = longitude;
+ }
+
+  /**
+   * Compare this location to another by geographical position.
+   *
+   * @param other the instance to compare against
+   * @returns true iff the coordinates are exactly the same
+   * @since UNRELEASED
+   */
+ public bool equal (Location other)
+ {
+   return this.latitude == other.latitude &&
+          this.longitude == other.longitude;
+ }
+
+  /**
+   * Compare the geographical position of this location against
+   * another position.
+   *
+   * @param latitude latitude of the other position
+   * @param longitude longitude of the other position
+   * @returns true iff the coordinates are exactly the same
+   * @since UNRELEASED
+   */
+  public bool equal_coordinates (double latitude, double longitude)
+  {
+    return this.latitude == latitude &&
+          this.longitude == longitude;
+  }
+}
+
+/**
+ * Location of a contact. folks tries to keep track of
+ * the current location and thus favors live data (say,
+ * as advertised by a chat service) over static data (from
+ * an address book).
+ *
+ * Backends are expected to report only relevant changes
+ * in a persona's location. For storage backends like EDS,
+ * all changes must have been triggered by a person and thus
+ * all are relevant.
+ *
+ * A backend pulling in live data, for example from a GPS,
+ * is expected to filter the data to minimize noise.
+ *
+ * The folks itself then will apply all changes coming
+ * from backends, without further filtering.
+ *
+ * @since UNRELEASED
+ */
+public interface Folks.LocationDetails : Object
+{
+  /**
+   * The current location of the contact. Null if the contact’s
+   * current location isn’t known, or they’re keeping it private.
+   */
+  public abstract Location? location { get; set; }
+
+  /**
+   * Set or remove the contact's currently advertised location.
+   *
+   * It's preferred to call this rather than setting
+   * { link LocationDetails.location} directly, as this method gives error
+   * notification and will only return once the location has been written to the
+   * relevant backing store (or the operation's failed).
+   *
+   * @param location the contact's location, null to remove the information
+   * @throws PropertyError if setting the location failed
+   */
+  public virtual async void change_location (Location? location) throws PropertyError
+    {
+      /* Default implementation. */
+      throw new PropertyError.NOT_WRITEABLE (
+          _("Location is not writeable on this contact."));
+    }
+}
diff --git a/folks/persona-store.vala b/folks/persona-store.vala
index 46984db..f59bf6a 100644
--- a/folks/persona-store.vala
+++ b/folks/persona-store.vala
@@ -206,6 +206,13 @@ public enum Folks.PersonaDetail
   LOCAL_IDS,
 
   /**
+   * Field for { link LocationDetails.location}.
+   *
+   * @since UNRELEASED
+   */
+  LOCATION,
+
+  /**
    * Field for { link NameDetails.nickname}.
    *
    * @since 0.5.0
@@ -348,6 +355,7 @@ public abstract class Folks.PersonaStore : Object
     "im-addresses",
     "is-favourite",
     "local-ids",
+    "location",
     "nickname",
     "notes",
     "phone-numbers",
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f9845e8..c6b1d5c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -21,6 +21,7 @@ folks/im-details.vala
 folks/individual-aggregator.vala
 folks/individual.vala
 folks/local-id-details.vala
+folks/location-details.vala
 folks/name-details.vala
 folks/note-details.vala
 [type: gettext/gsettings]folks/org.freedesktop.folks.gschema.xml.in


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