=?utf-8?q?=5Bfolks=5D_Bug_665692_=E2=80=94_Use_constructors_correctly?=



commit 2247cdd5f71a65f330343da2438e68fb8a3ecfd3
Author: Philip Withnall <philip tecnocode co uk>
Date:   Tue Dec 6 23:09:14 2011 +0000

    Bug 665692 â Use constructors correctly
    
    In order to allow libfolks to be used from introspected languages (such as
    Python) properly, we need to correctly use the GObject construction process,
    rather than generating code which does all object initialisation inside
    a *_new() function. This involves moving lots of code into construct{} blocks.
    
    There are some complications; mostly the need for various private variables to
    now be exposed as construct-only properties. Most of them should've been
    anyway.
    
    Other complications arose from the fact that moving code to a construct{}
    block can subtly change the execution order of the code if the Object() call
    lists properties which are non-construct properties (e.g. the âaliasâ property
    of a Persona). The setters for these properties will now be called _after_ the
    construct{} code, whereas previously they would've been called beforehand.
    This rears its head in Tpf.Persona, but hopefully nowhere else.
    
    Closes: bgo#665692

 NEWS                                               |   18 +++
 backends/eds/eds-backend.vala                      |    5 +
 backends/eds/lib/edsf-persona-store.vala           |   28 ++++--
 backends/eds/lib/edsf-persona.vala                 |   31 ++++--
 backends/eds/lib/memory-icon.vala                  |    4 +
 backends/key-file/kf-backend.vala                  |    5 +
 backends/key-file/kf-persona-store.vala            |   41 +++++---
 backends/key-file/kf-persona.vala                  |   37 ++++---
 backends/libsocialweb/lib/swf-persona-store.vala   |   26 +++--
 backends/libsocialweb/lib/swf-persona.vala         |   20 ++--
 backends/libsocialweb/sw-backend.vala              |    5 +
 backends/telepathy/lib/tpf-logger.vala             |   11 ++-
 .../telepathy/lib/tpf-persona-store-cache.vala     |   13 ++-
 backends/telepathy/lib/tpf-persona-store.vala      |    5 +-
 backends/telepathy/lib/tpf-persona.vala            |  110 +++++++++++---------
 backends/telepathy/tp-backend.vala                 |    5 +
 backends/tracker/lib/trf-persona-store.vala        |   39 ++++---
 backends/tracker/lib/trf-persona.vala              |   47 +++++++--
 backends/tracker/tr-backend.vala                   |    5 +
 folks/abstract-field-details.vala                  |    4 +-
 folks/avatar-cache.vala                            |    5 +
 folks/backend-store.vala                           |    5 +
 folks/debug.vala                                   |    4 +
 folks/individual-aggregator.vala                   |    5 +
 folks/individual.vala                              |  107 +++++++++----------
 folks/note-details.vala                            |    9 +-
 folks/object-cache.vala                            |   55 +++++++---
 folks/phone-details.vala                           |    5 +-
 folks/postal-address-details.vala                  |   13 ++-
 folks/potential-match.vala                         |   18 ++--
 folks/role-details.vala                            |   13 ++-
 folks/url-details.vala                             |    5 +-
 folks/web-service-details.vala                     |    5 +-
 33 files changed, 452 insertions(+), 256 deletions(-)
---
diff --git a/NEWS b/NEWS
index 879c982..17e0d0a 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,24 @@ Bugs fixed:
 * Bug 665039 â Crash in folks_backends_sw_backend_add_service
 * Bug 665728 â TpfPersonaStore: prepare() isn't mutually exclusive inside a
   single thread
+* Bug 665692 â Use constructors correctly
+
+API changes:
+* Add Edsf.PersonaStore.source
+* Make Edsf.Persona.contact writeable on construct (previously private setter)
+* Make Edsf.Persona.contact_id writeable on construct (previously private
+  setter)
+* Add Swf.PersonaStore.service
+* Make Swf.Persona.lsw_contact writeable on construct (previously private
+  setter)
+* Add Trf.Persona.tracker_id
+* Add Trf.Persona.cursor
+* Make AbstractFieldDetails.value writeable on construct (previously just a
+  normal setter)
+* Make AbstractFieldDetails.parameters writeable on construct (previously just a
+  normal setter)
+* Add ObjectCache.type_id
+* Add ObjectCache.id
 
 Overview of changes from libfolks 0.6.4.1 to libfolks 0.6.5
 =============================================================
diff --git a/backends/eds/eds-backend.vala b/backends/eds/eds-backend.vala
index 9886222..b1f3b12 100644
--- a/backends/eds/eds-backend.vala
+++ b/backends/eds/eds-backend.vala
@@ -61,6 +61,11 @@ public class Folks.Backends.Eds.Backend : Folks.Backend
    */
   public Backend ()
     {
+      Object ();
+    }
+
+  construct
+    {
       this._persona_stores = new HashMap<string, PersonaStore> ();
       this._persona_stores_ro = this._persona_stores.read_only_view;
     }
diff --git a/backends/eds/lib/edsf-persona-store.vala b/backends/eds/lib/edsf-persona-store.vala
index 6f3dc84..41982a5 100644
--- a/backends/eds/lib/edsf-persona-store.vala
+++ b/backends/eds/lib/edsf-persona-store.vala
@@ -42,7 +42,6 @@ public class Edsf.PersonaStore : Folks.PersonaStore
   private E.BookClient _addressbook;
   private E.BookClientView _ebookview;
   private E.SourceList? _source_list = null;
-  private E.Source _source;
   private string _query_str;
 
   /* The timeout after which we consider a property change to have failed if we
@@ -192,6 +191,16 @@ public class Edsf.PersonaStore : Folks.PersonaStore
     }
 
   /**
+   * The EDS { link E.Source} associated with this persona store.
+   *
+   * @since UNRELEASED
+   */
+  public E.Source source
+    {
+      get; construct;
+    }
+
+  /**
    * Create a new PersonaStore.
    *
    * Create a new persona store to store the { link Persona}s for the contacts
@@ -203,12 +212,17 @@ public class Edsf.PersonaStore : Folks.PersonaStore
   public PersonaStore (E.Source s)
     {
       string eds_uid = s.peek_uid ();
-      Object (id: eds_uid, display_name: eds_uid);
-      this._source = s;
+      Object (id: eds_uid,
+              display_name: eds_uid,
+              source: s);
+    }
+
+  construct
+    {
       this._personas = new HashMap<string, Persona> ();
       this._personas_ro = this._personas.read_only_view;
       this._query_str = "(contains \"x-evolution-any-field\" \"\")";
-      this._source.changed.connect (this._source_changed_cb);
+      this.source.changed.connect (this._source_changed_cb);
       this._notify_if_default ();
     }
 
@@ -550,7 +564,7 @@ public class Edsf.PersonaStore : Folks.PersonaStore
               this._source_list.changed.connect (this._source_list_changed_cb);
 
               /* Connect to the address book. */
-              this._addressbook = new E.BookClient (this._source);
+              this._addressbook = new E.BookClient (this.source);
 
               this._addressbook.notify["readonly"].connect (
                   this._address_book_notify_read_only_cb);
@@ -1989,7 +2003,7 @@ public class Edsf.PersonaStore : Folks.PersonaStore
    */
   private void _update_trust_level ()
     {
-      unowned SourceGroup? group = (SourceGroup?) this._source.peek_group ();
+      unowned SourceGroup? group = (SourceGroup?) this.source.peek_group ();
       if (group != null)
         {
           var base_uri = group.peek_base_uri ();
@@ -2025,7 +2039,7 @@ public class Edsf.PersonaStore : Folks.PersonaStore
           E.BookClient.get_sources (out sources);
           var default_source = sources.peek_default_source ();
           if (default_source != null &&
-              this._source.peek_uid () == default_source.peek_uid ())
+              this.source.peek_uid () == default_source.peek_uid ())
             {
               is_default = true;
             }
diff --git a/backends/eds/lib/edsf-persona.vala b/backends/eds/lib/edsf-persona.vala
index 17a31f3..a295b8d 100644
--- a/backends/eds/lib/edsf-persona.vala
+++ b/backends/eds/lib/edsf-persona.vala
@@ -140,13 +140,15 @@ public class Edsf.Persona : Folks.Persona,
 
   private bool _is_favourite;
 
+  private E.Contact _contact; /* should be set on construct */
+
   /**
    * The e-d-s contact represented by this Persona
    */
   public E.Contact contact
     {
-      get;
-      private set;
+      get { return this._contact; }
+      construct { this._contact = value; }
     }
 
   /**
@@ -371,7 +373,7 @@ public class Edsf.Persona : Folks.Persona,
    *
    * @since 0.6.0
    */
-  public string contact_id { get; private set; }
+  public string contact_id { get; construct; }
 
   private string _full_name = "";
   /**
@@ -703,16 +705,20 @@ public class Edsf.Persona : Folks.Persona,
       if (full_name == null)
         full_name = "";
 
-      debug ("Creating new Edsf.Persona with IID '%s'", iid);
-
       Object (display_id: full_name,
               uid: uid,
               iid: iid,
               store: store,
-              is_user: is_user);
+              is_user: is_user,
+              contact_id: contact_id,
+              contact: contact);
+    }
+
+  construct
+    {
+      debug ("Creating new Edsf.Persona with IID '%s'", this.iid);
 
       this._gender = Gender.UNSPECIFIED;
-      this.contact_id = contact_id;
       this._phone_numbers = new HashSet<PhoneFieldDetails> (
           (GLib.HashFunc) PhoneFieldDetails.hash,
           (GLib.EqualFunc) PhoneFieldDetails.equal);
@@ -747,7 +753,7 @@ public class Edsf.Persona : Folks.Persona,
           (GLib.EqualFunc) RoleFieldDetails.equal);
       this._roles_ro = this._roles.read_only_view;
 
-      this._update (contact);
+      this._update (this._contact);
     }
 
   /**
@@ -805,12 +811,15 @@ public class Edsf.Persona : Folks.Persona,
   /**
    * Update attribs of the persona.
    */
-  internal void _update (E.Contact contact)
+  internal void _update (E.Contact updated_contact)
     {
-      this.contact = contact;
-
       this.freeze_notify ();
 
+      /* We get a new E.Contact instance from EDS containing all the updates,
+       * so replace our existing contact with it. */
+      this._contact = updated_contact;
+      this.notify_property ("contact");
+
       this._update_names ();
       this._update_avatar ();
       this._update_urls ();
diff --git a/backends/eds/lib/memory-icon.vala b/backends/eds/lib/memory-icon.vala
index 544339f..5a06e0d 100644
--- a/backends/eds/lib/memory-icon.vala
+++ b/backends/eds/lib/memory-icon.vala
@@ -41,6 +41,10 @@ internal class Edsf.MemoryIcon : Object, Icon, LoadableIcon
    */
   public MemoryIcon (string? image_type, uint8[] image_data)
     {
+      /* Note: To be correct, these should both be properties of the object
+       * and this constructor should just call Object(â). However, this is an
+       * internal class, so we can skip all the pain of making a uint8[] object
+       * for this class only. */
       this._image_data = image_data;
       this._image_type = image_type;
     }
diff --git a/backends/key-file/kf-backend.vala b/backends/key-file/kf-backend.vala
index 73fc11d..912c176 100644
--- a/backends/key-file/kf-backend.vala
+++ b/backends/key-file/kf-backend.vala
@@ -82,6 +82,11 @@ public class Folks.Backends.Kf.Backend : Folks.Backend
    */
   public Backend ()
     {
+      Object ();
+    }
+
+  construct
+    {
       this._persona_stores = new HashMap<string, PersonaStore> ();
       this._persona_stores_ro = this._persona_stores.read_only_view;
     }
diff --git a/backends/key-file/kf-persona-store.vala b/backends/key-file/kf-persona-store.vala
index b33f5e2..616c27a 100644
--- a/backends/key-file/kf-persona-store.vala
+++ b/backends/key-file/kf-persona-store.vala
@@ -34,7 +34,6 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
 {
   private HashMap<string, Persona> _personas;
   private Map<string, Persona> _personas_ro;
-  private File _file;
   private GLib.KeyFile _key_file;
   private unowned Cancellable _save_key_file_cancellable = null;
   private bool _is_prepared = false;
@@ -144,6 +143,15 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
     }
 
   /**
+   * File containing the persona store data.
+   *
+   * This must be in GLib key file format.
+   *
+   * @since UNRELEASED
+   */
+  public File file { get; construct; }
+
+  /**
    * Create a new PersonaStore.
    *
    * Create a new persona store to expose the { link Persona}s provided by the
@@ -154,10 +162,13 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
       var id = key_file.get_basename ();
 
       Object (id: id,
-              display_name: id);
+              display_name: id,
+              file: key_file);
+    }
 
+  construct
+    {
       this.trust_level = PersonaStoreTrust.FULL;
-      this._file = key_file;
       this._personas = new HashMap<string, Persona> ();
       this._personas_ro = this._personas.read_only_view;
     }
@@ -178,7 +189,7 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
             {
               this._prepare_pending = true;
 
-              var filename = this._file.get_path ();
+              var filename = this.file.get_path ();
               this._key_file = new GLib.KeyFile ();
 
               /* Load or create the file */
@@ -191,7 +202,7 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
                     {
                       uint8 *contents = null;
 
-                      yield this._file.load_contents_async (null, out contents,
+                      yield this.file.load_contents_async (null, out contents,
                           null);
                       var contents_s = (string) contents;
 
@@ -218,7 +229,7 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
                     }
 
                   /* Ensure the parent directory tree exists for the new file */
-                  File parent_dir = this._file.get_parent ();
+                  File parent_dir = this.file.get_parent ();
 
                   try
                     {
@@ -245,7 +256,7 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
                   try
                     {
                       /* Create the file */
-                      FileOutputStream stream = yield this._file.create_async (
+                      FileOutputStream stream = yield this.file.create_async (
                           FileCreateFlags.PRIVATE, Priority.DEFAULT);
                       yield stream.close_async (Priority.DEFAULT);
                     }
@@ -271,8 +282,7 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
               var added_personas = new HashSet<Persona> ();
               foreach (var persona_id in groups)
                 {
-                  Persona persona = new Kf.Persona (this._key_file, persona_id,
-                      this);
+                  Persona persona = new Kf.Persona (persona_id, this);
                   this._personas.set (persona.iid, persona);
                   added_personas.add (persona);
                 }
@@ -379,7 +389,7 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
 
       /* Create a new persona and set its addresses property to update the
        * key file */
-      Persona persona = new Kf.Persona (this._key_file, persona_id, this);
+      Persona persona = new Kf.Persona (persona_id, this);
       this._personas.set (persona.iid, persona);
 
       try
@@ -409,12 +419,17 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
       return persona;
     }
 
+  internal unowned KeyFile get_key_file ()
+    {
+      return this._key_file;
+    }
+
   internal async void save_key_file ()
     {
       var key_file_data = this._key_file.to_data ();
       var cancellable = new Cancellable ();
 
-      debug ("Saving key file '%s'.", this._file.get_path ());
+      debug ("Saving key file '%s'.", this.file.get_path ());
 
       /* There's no point in having two competing file write operations.
        * We can ensure that only one is running by just checking if a
@@ -435,7 +450,7 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
            * Vala <= 0.10, it returned the character length). FIXME: We need to
            * take this into account until we depend explicitly on
            * Vala >= 0.11. */
-          yield this._file.replace_contents_async (key_file_data,
+          yield this.file.replace_contents_async (key_file_data,
               key_file_data.length, null, false, FileCreateFlags.PRIVATE,
               cancellable);
         }
@@ -446,7 +461,7 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
               /* Translators: the first parameter is a filename, the second is
                * an error message. */
               warning (_("Could not write updated key file '%s': %s"),
-                  this._file.get_path (), e.message);
+                  this.file.get_path (), e.message);
             }
         }
 
diff --git a/backends/key-file/kf-persona.vala b/backends/key-file/kf-persona.vala
index 488e8f3..854d14c 100644
--- a/backends/key-file/kf-persona.vala
+++ b/backends/key-file/kf-persona.vala
@@ -33,7 +33,6 @@ public class Folks.Backends.Kf.Persona : Folks.Persona,
     ImDetails,
     WebServiceDetails
 {
-  private unowned GLib.KeyFile _key_file;
   private HashMultiMap<string, ImFieldDetails> _im_addresses;
   private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
   private string _alias = ""; /* must not be null */
@@ -99,7 +98,8 @@ public class Folks.Backends.Kf.Persona : Folks.Persona,
 
       debug ("Setting alias of Kf.Persona '%s' to '%s'.", this.uid, alias);
 
-      this._key_file.set_string (this.display_id, "__alias", 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;
@@ -124,12 +124,14 @@ public class Folks.Backends.Kf.Persona : Folks.Persona,
   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
             {
-              this._key_file.remove_key (this.display_id, protocol1);
+              key_file.remove_key (this.display_id, protocol1);
             }
           catch (KeyFileError e1)
             {
@@ -178,7 +180,7 @@ public class Folks.Backends.Kf.Persona : Folks.Persona,
           string[] addrs = (string[]) normalised_addresses.to_array ();
           addrs.length = normalised_addresses.size;
 
-          this._key_file.set_string_list (this.display_id, protocol2, addrs);
+          key_file.set_string_list (this.display_id, protocol2, addrs);
         }
 
       /* Get the PersonaStore to save the key file */
@@ -207,12 +209,14 @@ public class Folks.Backends.Kf.Persona : Folks.Persona,
       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
             {
-              this._key_file.remove_key (this.display_id,
+              key_file.remove_key (this.display_id,
                   "web-service." + web_service1);
             }
           catch (KeyFileError e)
@@ -238,7 +242,7 @@ public class Folks.Backends.Kf.Persona : Folks.Persona,
           foreach (var ws_fd1 in ws_fds)
             addrs += ws_fd1.value;
 
-          this._key_file.set_string_list (this.display_id,
+          key_file.set_string_list (this.display_id,
               "web-service." + web_service2, addrs);
 
           foreach (var ws_fd2 in ws_fds)
@@ -258,7 +262,7 @@ public class Folks.Backends.Kf.Persona : Folks.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 (KeyFile key_file, string id, Folks.PersonaStore store)
+  public Persona (string id, Folks.PersonaStore store)
     {
       var iid = store.id + ":" + id;
       var uid = this.build_uid ("key-file", store.id, id);
@@ -268,11 +272,13 @@ public class Folks.Backends.Kf.Persona : Folks.Persona,
               uid: uid,
               store: store,
               is_user: false);
+    }
 
-      debug ("Adding key-file Persona '%s' (IID '%s', group '%s')", uid, iid,
-          id);
+  construct
+    {
+      debug ("Adding key-file Persona '%s' (IID '%s', group '%s')", this.uid,
+          this.iid, this.display_id);
 
-      this._key_file = key_file;
       this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
           null, null, ImFieldDetails.hash, (EqualFunc) ImFieldDetails.equal);
       this._web_service_addresses =
@@ -282,16 +288,17 @@ public class Folks.Backends.Kf.Persona : Folks.Persona,
             (GLib.EqualFunc) WebServiceFieldDetails.equal);
 
       /* Load the IM addresses from the key file */
+      unowned KeyFile key_file = ((Kf.PersonaStore) this.store).get_key_file ();
+
       try
         {
-          var keys = this._key_file.get_keys (this.display_id);
+          var keys = key_file.get_keys (this.display_id);
           foreach (unowned string key in keys)
             {
               /* Alias */
               if (key == "__alias")
                 {
-                  this._alias = this._key_file.get_string (this.display_id,
-                      key);
+                  this._alias = key_file.get_string (this.display_id, key);
 
                   if (this._alias == null)
                     {
@@ -308,7 +315,7 @@ public class Folks.Backends.Kf.Persona : Folks.Persona,
                   decomposed_key[0] == "web-service")
                 {
                   unowned string web_service = decomposed_key[1];
-                  var web_service_addresses = this._key_file.get_string_list (
+                  var web_service_addresses = key_file.get_string_list (
                       this.display_id, web_service);
 
                   foreach (var web_service_address in web_service_addresses)
@@ -322,7 +329,7 @@ public class Folks.Backends.Kf.Persona : Folks.Persona,
 
               /* IM addresses */
               unowned string protocol = key;
-              var im_addresses = this._key_file.get_string_list (
+              var im_addresses = key_file.get_string_list (
                   this.display_id, protocol);
 
               foreach (var im_address in im_addresses)
diff --git a/backends/libsocialweb/lib/swf-persona-store.vala b/backends/libsocialweb/lib/swf-persona-store.vala
index c078d76..ba3b872 100644
--- a/backends/libsocialweb/lib/swf-persona-store.vala
+++ b/backends/libsocialweb/lib/swf-persona-store.vala
@@ -39,7 +39,6 @@ public class Swf.PersonaStore : Folks.PersonaStore
   private bool _is_prepared = false;
   private bool _prepare_pending = false;
   private bool _is_quiescent = false;
-  private ClientService _service;
   private ClientContactView _contact_view;
 
   /* No writeable properties
@@ -150,6 +149,14 @@ public class Swf.PersonaStore : Folks.PersonaStore
     }
 
   /**
+   * The libsocialweb { link SocialWebClient.ClientService} associated with the
+   * persona store.
+   *
+   * @since UNRELEASED
+   */
+  public ClientService service { get; construct; }
+
+  /**
    * Create a new PersonaStore.
    *
    * Create a new persona store to store the { link Persona}s for the contacts
@@ -157,11 +164,14 @@ public class Swf.PersonaStore : Folks.PersonaStore
    */
   public PersonaStore (ClientService service)
     {
-      Object (display_name: service.get_display_name(),
-              id: service.get_name ());
+      Object (display_name: service.get_display_name (),
+              id: service.get_name (),
+              service: service);
+    }
 
+  construct
+    {
       this.trust_level = PersonaStoreTrust.PARTIAL;
-      this._service = service;
       this._personas = new HashMap<string, Persona> ();
       this._personas_ro = this._personas.read_only_view;
     }
@@ -219,7 +229,7 @@ public class Swf.PersonaStore : Folks.PersonaStore
                * async call to return. See: bgo#665039. */
               this.ref ();
 
-              this._service.get_static_capabilities (
+              this.service.get_static_capabilities (
                   (service, caps, error) =>
                     {
                       if (caps == null)
@@ -244,7 +254,7 @@ public class Swf.PersonaStore : Folks.PersonaStore
                       /* Take another ref for this async call. */
                       this.ref ();
 
-                      this._service.contacts_query_open_view
+                      this.service.contacts_query_open_view
                           ("people", parameters, (query, contact_view) =>
                         {
                           /* The D-Bus call could return an error. In this
@@ -325,7 +335,7 @@ public class Swf.PersonaStore : Folks.PersonaStore
     {
       foreach (var contact in contacts)
         {
-          if (this._service.get_name () != contact.service)
+          if (this.service.get_name () != contact.service)
             {
               continue;
             }
@@ -341,7 +351,7 @@ public class Swf.PersonaStore : Folks.PersonaStore
       var removed_personas = new HashSet<Persona> ();
       foreach (var contact in contacts)
         {
-          if (this._service.get_name () != contact.service)
+          if (this.service.get_name () != contact.service)
             {
               continue;
             }
diff --git a/backends/libsocialweb/lib/swf-persona.vala b/backends/libsocialweb/lib/swf-persona.vala
index 929ecd4..f4fedce 100644
--- a/backends/libsocialweb/lib/swf-persona.vala
+++ b/backends/libsocialweb/lib/swf-persona.vala
@@ -185,7 +185,7 @@ public class Swf.Persona : Folks.Persona,
   public Contact lsw_contact
     {
       get { return this._lsw_contact; }
-      private set
+      construct
         {
           if (_lsw_contact != null && _lsw_contact != value)
             {
@@ -253,7 +253,6 @@ public class Swf.Persona : Folks.Persona,
   public Persona (PersonaStore store, Contact contact)
     {
       var id = get_contact_id (contact);
-      var service = contact.service.dup();
       var uid = this.build_uid (BACKEND_NAME, store.id, id);
       var iid = this._build_iid (store.id, id);
 
@@ -261,13 +260,17 @@ public class Swf.Persona : Folks.Persona,
               uid: uid,
               iid: iid,
               store: store,
-              is_user: false);
-      this.lsw_contact = contact;
+              is_user: false,
+              lsw_contact: contact);
+    }
 
+  construct
+    {
       debug ("Creating new Sw.Persona '%s' for %s UID '%s': %p",
-          uid, store.display_name, id, this);
+          this.uid, this.store.display_name, this.display_id, this);
 
-      var facebook_jid = this._build_facebook_jid (store.id, id);
+      var facebook_jid =
+          this._build_facebook_jid (this.store.id, this.display_id);
       if (facebook_jid != null)
         {
           try
@@ -286,10 +289,11 @@ public class Swf.Persona : Folks.Persona,
             }
         }
 
+      var service = this.lsw_contact.service.dup ();
       this._web_service_addresses.set (service,
-          new WebServiceFieldDetails (id));
+          new WebServiceFieldDetails (this.display_id));
 
-      update (contact);
+      this.update (this.lsw_contact);
     }
 
   ~Persona ()
diff --git a/backends/libsocialweb/sw-backend.vala b/backends/libsocialweb/sw-backend.vala
index f4b60f4..e85dc67 100644
--- a/backends/libsocialweb/sw-backend.vala
+++ b/backends/libsocialweb/sw-backend.vala
@@ -59,6 +59,11 @@ public class Folks.Backends.Sw.Backend : Folks.Backend
    */
   public Backend ()
     {
+      Object ();
+    }
+
+  construct
+    {
       this._persona_stores = new HashMap<string, PersonaStore> ();
       this._persona_stores_ro = this._persona_stores.read_only_view;
     }
diff --git a/backends/telepathy/lib/tpf-logger.vala b/backends/telepathy/lib/tpf-logger.vala
index a114913..80058e9 100644
--- a/backends/telepathy/lib/tpf-logger.vala
+++ b/backends/telepathy/lib/tpf-logger.vala
@@ -46,16 +46,23 @@ private interface LoggerIface : Object
 internal class Logger : GLib.Object
 {
   private static LoggerIface _logger;
-  private string _account_path;
   private uint _logger_watch_id;
 
   public signal void invalidated ();
   public signal void favourite_contacts_changed (string[] added,
       string[] removed);
 
+  /**
+   * D-Bus object path of the { link TelepathyGLib.Account} to watch for
+   * favourite contacts.
+   *
+   * @since UNRELEASED
+   */
+  public string account_path { get; construct; }
+
   public Logger (string account_path)
     {
-      this._account_path = account_path;
+      Object (account_path: account_path);
     }
 
   ~Logger ()
diff --git a/backends/telepathy/lib/tpf-persona-store-cache.vala b/backends/telepathy/lib/tpf-persona-store-cache.vala
index 352a5f5..4e88945 100644
--- a/backends/telepathy/lib/tpf-persona-store-cache.vala
+++ b/backends/telepathy/lib/tpf-persona-store-cache.vala
@@ -45,7 +45,12 @@ using Folks;
  */
 internal class Tpf.PersonaStoreCache : Folks.ObjectCache<Tpf.Persona>
 {
-  private weak PersonaStore _store;
+  /**
+   * The { link Tpf.PersonaStore} associated with this cache.
+   *
+   * @since UNRELEASED
+   */
+  public weak PersonaStore store { get; construct; }
 
   /* Version number of the variant type returned by
    * get_serialised_object_type(). This must be modified whenever that variant
@@ -55,9 +60,9 @@ internal class Tpf.PersonaStoreCache : Folks.ObjectCache<Tpf.Persona>
 
   internal PersonaStoreCache (PersonaStore store)
     {
-      base ("tpf-persona-stores", store.id);
-
-      this._store = store;
+      Object (type_id: "tpf-persona-stores",
+              id: store.id,
+              store: store);
     }
 
   protected override VariantType? get_serialised_object_type (
diff --git a/backends/telepathy/lib/tpf-persona-store.vala b/backends/telepathy/lib/tpf-persona-store.vala
index 76caec8..d5c2dde 100644
--- a/backends/telepathy/lib/tpf-persona-store.vala
+++ b/backends/telepathy/lib/tpf-persona-store.vala
@@ -250,9 +250,12 @@ public class Tpf.PersonaStore : Folks.PersonaStore
       Object (account: account,
               display_name: account.display_name,
               id: account.get_object_path ());
+    }
 
+  construct
+    {
       debug ("Creating new Tpf.PersonaStore %p ('%s') for TpAccount %p.",
-          this, this.id, account);
+          this, this.id, this.account);
 
       this._debug = Debug.dup ();
       this._debug.print_status.connect (this._debug_print_status);
diff --git a/backends/telepathy/lib/tpf-persona.vala b/backends/telepathy/lib/tpf-persona.vala
index b25ee32..bfe1783 100644
--- a/backends/telepathy/lib/tpf-persona.vala
+++ b/backends/telepathy/lib/tpf-persona.vala
@@ -40,12 +40,6 @@ public class Tpf.Persona : Folks.Persona,
     PresenceDetails,
     UrlDetails
 {
-  private HashSet<string> _groups;
-  private Set<string> _groups_ro;
-  private bool _is_favourite;
-  private string _alias; /* must never be null */
-  private string _full_name; /* must never be null */
-  private HashMultiMap<string, ImFieldDetails> _im_addresses;
   private const string[] _linkable_properties = { "im-addresses" };
   private const string[] _always_writeable_properties =
     {
@@ -101,6 +95,8 @@ public class Tpf.Persona : Folks.Persona,
       set { this.change_structured_name.begin (value); } /* not writeable */
     }
 
+  private string _full_name = ""; /* must never be null */
+
   /**
    * { inheritDoc}
    *
@@ -276,6 +272,8 @@ public class Tpf.Persona : Folks.Persona,
         }
     }
 
+  private string _alias = ""; /* must never be null */
+
   /**
    * An alias for the Persona.
    *
@@ -309,6 +307,8 @@ public class Tpf.Persona : Folks.Persona,
       this.notify_property ("alias");
     }
 
+  private bool _is_favourite = false;
+
   /**
    * Whether this Persona is a user-defined favourite.
    *
@@ -343,7 +343,10 @@ public class Tpf.Persona : Folks.Persona,
       this.notify_property ("is-favourite");
     }
 
-  private HashSet<EmailFieldDetails> _email_addresses;
+  private HashSet<EmailFieldDetails> _email_addresses =
+      new HashSet<EmailFieldDetails> (
+          (GLib.HashFunc) EmailFieldDetails.hash,
+          (GLib.EqualFunc) EmailFieldDetails.equal);
   private Set<EmailFieldDetails> _email_addresses_ro;
 
   /**
@@ -370,6 +373,11 @@ public class Tpf.Persona : Folks.Persona,
           this._email_addresses, "email");
     }
 
+  private HashMultiMap<string, ImFieldDetails> _im_addresses =
+      new HashMultiMap<string, ImFieldDetails> (null, null,
+          (GLib.HashFunc) ImFieldDetails.hash,
+          (GLib.EqualFunc) ImFieldDetails.equal);
+
   /**
    * A mapping of IM protocol to an (unordered) set of IM addresses.
    *
@@ -382,6 +390,9 @@ public class Tpf.Persona : Folks.Persona,
       set { this.change_im_addresses.begin (value); }
     }
 
+  private HashSet<string> _groups = new HashSet<string> ();
+  private Set<string> _groups_ro;
+
   /**
    * A mapping of group ID to whether the contact is a member.
    *
@@ -464,7 +475,10 @@ public class Tpf.Persona : Folks.Persona,
    */
   public Contact? contact { get; construct; }
 
-  private HashSet<PhoneFieldDetails> _phone_numbers;
+  private HashSet<PhoneFieldDetails> _phone_numbers =
+      new HashSet<PhoneFieldDetails> (
+          (GLib.HashFunc) PhoneFieldDetails.hash,
+          (GLib.EqualFunc) PhoneFieldDetails.equal);
   private Set<PhoneFieldDetails> _phone_numbers_ro;
 
   /**
@@ -491,7 +505,9 @@ public class Tpf.Persona : Folks.Persona,
           this._phone_numbers, "tel");
     }
 
-  private HashSet<UrlFieldDetails> _urls;
+  private HashSet<UrlFieldDetails> _urls = new HashSet<UrlFieldDetails> (
+      (GLib.HashFunc) UrlFieldDetails.hash,
+      (GLib.EqualFunc) UrlFieldDetails.equal);
   private Set<UrlFieldDetails> _urls_ro;
 
   /**
@@ -570,8 +586,7 @@ public class Tpf.Persona : Folks.Persona,
       var account = this._account_for_connection (connection);
       var uid = this.build_uid (store.type_id, store.id, id);
 
-      Object (alias: contact.get_alias (),
-              contact: contact,
+      Object (contact: contact,
               display_id: id,
               /* FIXME: This IID format should be moved out to the ImDetails
                * interface along with the code in
@@ -582,10 +597,28 @@ public class Tpf.Persona : Folks.Persona,
               store: store,
               is_user: contact.handle == connection.self_handle);
 
+      debug ("Created new Tpf.Persona '%s' for service-specific UID '%s': %p",
+          uid, id, this);
+    }
+
+  construct
+    {
+      this._groups_ro = this._groups.read_only_view;
+      this._email_addresses_ro = this._email_addresses.read_only_view;
+      this._phone_numbers_ro = this._phone_numbers.read_only_view;
+      this._urls_ro = this._urls.read_only_view;
+
+      /* Contact can be null if we've been created from the cache. All the code
+       * below this point is for non-cached personas. */
+      if (this.contact == null)
+        {
+          return;
+        }
 
-      this._full_name = "";
+      /* Set our alias. */
+      this._alias = this.contact.get_alias ();
 
-      contact.notify["alias"].connect ((s, p) =>
+      this.contact.notify["alias"].connect ((s, p) =>
           {
             /* Tp guarantees that aliases are always non-null. */
             assert (this.contact.alias != null);
@@ -597,19 +630,13 @@ public class Tpf.Persona : Folks.Persona,
               }
           });
 
-      debug ("Creating new Tpf.Persona '%s' for service-specific UID '%s': %p",
-          uid, id, this);
-      this._is_constructed = true;
-
       /* Set our single IM address */
-      this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
-          null, null,
-          (GLib.HashFunc) ImFieldDetails.hash,
-          (GLib.EqualFunc) ImFieldDetails.equal);
+      var connection = this.contact.connection;
+      var account = this._account_for_connection (connection);
 
       try
         {
-          var im_addr = ImDetails.normalise_im_address (id,
+          var im_addr = ImDetails.normalise_im_address (this.display_id,
               account.get_protocol ());
           var im_fd = new ImFieldDetails (im_addr);
           this._im_addresses.set (account.get_protocol (), im_fd);
@@ -620,38 +647,21 @@ public class Tpf.Persona : Folks.Persona,
           warning (e.message);
         }
 
-      /* Groups */
-      this._groups = new HashSet<string> ();
-      this._groups_ro = this._groups.read_only_view;
-
-      this._email_addresses = new HashSet<EmailFieldDetails> (
-          (GLib.HashFunc) EmailFieldDetails.hash,
-          (GLib.EqualFunc) EmailFieldDetails.equal);
-      this._email_addresses_ro = this._email_addresses.read_only_view;
-      this._phone_numbers = new HashSet<PhoneFieldDetails> (
-          (GLib.HashFunc) PhoneFieldDetails.hash,
-          (GLib.EqualFunc) PhoneFieldDetails.equal);
-      this._phone_numbers_ro = this._phone_numbers.read_only_view;
-      this._urls = new HashSet<UrlFieldDetails> (
-          (GLib.HashFunc) UrlFieldDetails.hash,
-          (GLib.EqualFunc) UrlFieldDetails.equal);
-      this._urls_ro = this._urls.read_only_view;
-
-      contact.notify["avatar-file"].connect ((s, p) =>
+      this.contact.notify["avatar-file"].connect ((s, p) =>
         {
           this._contact_notify_avatar ();
         });
       this._contact_notify_avatar ();
 
-      contact.notify["presence-message"].connect ((s, p) =>
+      this.contact.notify["presence-message"].connect ((s, p) =>
         {
           this._contact_notify_presence_message ();
         });
-      contact.notify["presence-type"].connect ((s, p) =>
+      this.contact.notify["presence-type"].connect ((s, p) =>
         {
           this._contact_notify_presence_type ();
         });
-      contact.notify["presence-status"].connect ((s, p) =>
+      this.contact.notify["presence-status"].connect ((s, p) =>
         {
           this._contact_notify_presence_status ();
         });
@@ -659,7 +669,7 @@ public class Tpf.Persona : Folks.Persona,
       this._contact_notify_presence_type ();
       this._contact_notify_presence_status ();
 
-      contact.notify["contact-info"].connect ((s, p) =>
+      this.contact.notify["contact-info"].connect ((s, p) =>
         {
           this._contact_notify_contact_info ();
         });
@@ -701,6 +711,12 @@ public class Tpf.Persona : Folks.Persona,
         }
     }
 
+  /* Called after all construction-time properties have been set. */
+  public override void constructed ()
+    {
+      this._is_constructed = true;
+    }
+
   private void _store_notify_supported_fields ()
     {
       var tpf_store = this.store as Tpf.PersonaStore;
@@ -901,13 +917,9 @@ public class Tpf.Persona : Folks.Persona,
               store: store,
               is_user: is_user);
 
-      debug ("Creating new Tpf.Persona '%s' from cache: %p", uid, this);
+      debug ("Created new Tpf.Persona '%s' from cache: %p", uid, this);
 
       // IM addresses
-      this._im_addresses = new HashMultiMap<string, ImFieldDetails> (null, null,
-          (GLib.HashFunc) ImFieldDetails.hash,
-          (GLib.EqualFunc) ImFieldDetails.equal);
-
       var im_fd = new ImFieldDetails (im_address);
       this._im_addresses.set (protocol, im_fd);
 
diff --git a/backends/telepathy/tp-backend.vala b/backends/telepathy/tp-backend.vala
index 37c7481..3f770b1 100644
--- a/backends/telepathy/tp-backend.vala
+++ b/backends/telepathy/tp-backend.vala
@@ -57,6 +57,11 @@ public class Folks.Backends.Tp.Backend : Folks.Backend
    */
   public Backend ()
     {
+      Object ();
+    }
+
+  construct
+    {
       this._persona_stores = new HashMap<string, PersonaStore> ();
       this._persona_stores_ro = this._persona_stores.read_only_view;
     }
diff --git a/backends/tracker/lib/trf-persona-store.vala b/backends/tracker/lib/trf-persona-store.vala
index 66f5a53..a326ef8 100644
--- a/backends/tracker/lib/trf-persona-store.vala
+++ b/backends/tracker/lib/trf-persona-store.vala
@@ -414,7 +414,12 @@ public class Trf.PersonaStore : Folks.PersonaStore
    */
   public PersonaStore ()
     {
-      Object (id: BACKEND_NAME, display_name: BACKEND_NAME);
+      Object (id: BACKEND_NAME,
+              display_name: BACKEND_NAME);
+    }
+
+  construct
+    {
       this._personas = new HashMap<string, Persona> ();
       this._personas_ro = this._personas.read_only_view;
       debug ("Initial query : \n%s\n", this._INITIAL_QUERY);
@@ -1929,8 +1934,8 @@ public class Trf.PersonaStore : Folks.PersonaStore
         "FILTER (tracker:id(?p) = %s) " +
         "} ";
 
-      string query = query_t.printf (persona.tracker_id (), nickname,
-          persona.tracker_id ());
+      string query = query_t.printf (persona.tracker_id, nickname,
+          persona.tracker_id);
 
       yield this._tracker_update (query, "change_nickname");
     }
@@ -1974,8 +1979,8 @@ public class Trf.PersonaStore : Folks.PersonaStore
       " ?p a nco:PersonContact . " +
       "FILTER (tracker:id(?p) = %s) } ";
 
-      string query = query_t.printf (persona.tracker_id (), prop_name,
-          prop_name, prop_value, persona.tracker_id ());
+      string query = query_t.printf (persona.tracker_id, prop_name,
+          prop_name, prop_value, persona.tracker_id);
       yield this._tracker_update (query, callers_name);
     }
 
@@ -2002,11 +2007,11 @@ public class Trf.PersonaStore : Folks.PersonaStore
 
       if (is_favourite)
         {
-          query = ins_q.printf (((Trf.Persona) persona).tracker_id ());
+          query = ins_q.printf (((Trf.Persona) persona).tracker_id);
         }
       else
         {
-          query = del_q.printf (((Trf.Persona) persona).tracker_id ());
+          query = del_q.printf (((Trf.Persona) persona).tracker_id);
         }
 
       yield this._tracker_update (query, "change_is_favourite");
@@ -2030,7 +2035,7 @@ public class Trf.PersonaStore : Folks.PersonaStore
       Set<AbstractFieldDetails<string>> properties, Trf.Attrib attrib)
     {
       string? query_name = null;
-      var p_id = ((Trf.Persona) persona).tracker_id ();
+      var p_id = ((Trf.Persona) persona).tracker_id;
       var builder = new Tracker.Sparql.Builder.update ();
       builder.insert_open (null);
 
@@ -2110,7 +2115,7 @@ public class Trf.PersonaStore : Folks.PersonaStore
         " FILTER(tracker:id(?p) = %s) " +
         "} ";
 
-      var p_id = ((Trf.Persona) persona).tracker_id ();
+      var p_id = ((Trf.Persona) persona).tracker_id;
       string del_q = del_t.printf (p_id);
 
       var builder = new Tracker.Sparql.Builder.update ();
@@ -2159,7 +2164,7 @@ public class Trf.PersonaStore : Folks.PersonaStore
         " FILTER(tracker:id(?p) = %s)" +
         "}";
 
-      var p_id = ((Trf.Persona) persona).tracker_id ();
+      var p_id = ((Trf.Persona) persona).tracker_id;
       string del_q = del_t.printf (p_id);
 
       var builder = new Tracker.Sparql.Builder.update ();
@@ -2203,7 +2208,7 @@ public class Trf.PersonaStore : Folks.PersonaStore
          " FILTER (tracker:id(?p) = %s) " +
          "} ";
 
-      var p_id = ((Trf.Persona) persona).tracker_id ();
+      var p_id = ((Trf.Persona) persona).tracker_id;
       TimeVal tv;
       bday.to_timeval (out tv);
       string query = q_t.printf (p_id, tv.to_iso8601 (), p_id);
@@ -2230,7 +2235,7 @@ public class Trf.PersonaStore : Folks.PersonaStore
         " FILTER (tracker:id(?p) = %s) " +
         "} ";
 
-      var p_id = ((Trf.Persona) persona).tracker_id ();
+      var p_id = ((Trf.Persona) persona).tracker_id;
       string query;
 
       if (gender == Gender.UNSPECIFIED)
@@ -2275,7 +2280,7 @@ public class Trf.PersonaStore : Folks.PersonaStore
         " FILTER(tracker:id(?c) = %s) " +
         "}";
 
-      var p_id = ((Trf.Persona) persona).tracker_id ();
+      var p_id = ((Trf.Persona) persona).tracker_id;
 
       var image_urn = yield this._get_property (int.parse (p_id),
           Trf.OntologyDefs.NCO_PHOTO);
@@ -2345,7 +2350,7 @@ public class Trf.PersonaStore : Folks.PersonaStore
         " FILTER (tracker:id(?p) = %s) " +
         "} ";
 
-      var p_id = ((Trf.Persona) persona).tracker_id ();
+      var p_id = ((Trf.Persona) persona).tracker_id;
 
       string query = query_d.printf (p_id);
       if (sname != null)
@@ -2376,7 +2381,7 @@ public class Trf.PersonaStore : Folks.PersonaStore
         " FILTER (tracker:id(?p) = %s) " +
         "} ";
 
-      var p_id = ((Trf.Persona) persona).tracker_id ();
+      var p_id = ((Trf.Persona) persona).tracker_id;
       string query = query_t.printf (p_id, full_name, p_id);
       yield this._tracker_update (query, "_set_full_name");
     }
@@ -2388,7 +2393,7 @@ public class Trf.PersonaStore : Folks.PersonaStore
   private async void _set_attrib_set (Folks.Persona persona,
       Set<Object> attribs, Trf.Attrib what)
     {
-      var p_id = ((Trf.Persona) persona).tracker_id ();
+      var p_id = ((Trf.Persona) persona).tracker_id;
 
       unowned string? related_attrib = null;
       unowned string? related_prop = null;
@@ -2664,7 +2669,7 @@ public class Trf.PersonaStore : Folks.PersonaStore
 
   private async string _urn_from_persona (Folks.Persona persona)
     {
-      var id = ((Trf.Persona) persona).tracker_id ();
+      var id = ((Trf.Persona) persona).tracker_id;
       return yield this._urn_from_tracker_id (id);
     }
 
diff --git a/backends/tracker/lib/trf-persona.vala b/backends/tracker/lib/trf-persona.vala
index 45c674a..a03d197 100644
--- a/backends/tracker/lib/trf-persona.vala
+++ b/backends/tracker/lib/trf-persona.vala
@@ -537,17 +537,25 @@ public class Trf.Persona : Folks.Persona,
             }
         }
 
-      debug ("Creating new Trf.Persona with iid '%s'", iid);
-
       Object (display_id: fullname,
               uid: uid,
               iid: iid,
               store: store,
-              is_user: is_user);
+              is_user: is_user,
+              tracker_id: tracker_id,
+              /* Ideally we wouldn't have to do this, since passing iterators
+               * around is ugly. However, we can't fix the Tracker backend to
+               * not pass Cursors from PersonaStore to Personas without breaking
+               * API. */
+              cursor: cursor);
+    }
+
+  construct
+    {
+      debug ("Creating new Trf.Persona with iid '%s'", this.iid);
 
       this._gender = Gender.UNSPECIFIED;
-      this._full_name = fullname;
-      this._tracker_id = tracker_id;
+      this._full_name = "";
       this._structured_name = null;
       this._phone_numbers = new HashSet<PhoneFieldDetails> (
           (GLib.HashFunc) PhoneFieldDetails.hash,
@@ -577,10 +585,8 @@ public class Trf.Persona : Folks.Persona,
       this._local_ids_ro = this._local_ids.read_only_view;
 
       /* Set the initial property values if we have a results cursor. */
-      if (cursor != null)
+      if (this._cursor != null)
         {
-          this._cursor = cursor;
-
           this._update_names ();
           this._update_avatar ();
           this._update_im_addresses ();
@@ -597,9 +603,30 @@ public class Trf.Persona : Folks.Persona,
         }
     }
 
-  internal string tracker_id ()
+  /**
+   * ID of the { link Trf.Persona} in Tracker.
+   *
+   * @since UNRELEASED
+   */
+  public string tracker_id
+    {
+      get { return this._tracker_id; }
+      construct { this._tracker_id = value; }
+    }
+
+  /**
+   * A { link Sparql.Cursor} representing the persona in a set of query results.
+   *
+   * This is an internal (read: horrible) API which shouldn't be used by client
+   * code. It's only exposed publicly due to the design of libfolksâ Tracker
+   * backend.
+   *
+   * @since UNRELEASED
+   */
+  public Sparql.Cursor? cursor
     {
-      return this._tracker_id;
+      get { return this._cursor; }
+      construct { this._cursor = value; }
     }
 
   /**
diff --git a/backends/tracker/tr-backend.vala b/backends/tracker/tr-backend.vala
index 40ab4df..44bca1a 100644
--- a/backends/tracker/tr-backend.vala
+++ b/backends/tracker/tr-backend.vala
@@ -56,6 +56,11 @@ public class Folks.Backends.Tr.Backend : Folks.Backend
    */
   public Backend ()
     {
+      Object ();
+    }
+
+  construct
+    {
       this._persona_stores = new HashMap<string, PersonaStore> ();
       this._persona_stores_ro = this._persona_stores.read_only_view;
     }
diff --git a/folks/abstract-field-details.vala b/folks/abstract-field-details.vala
index 555a7e7..a0819af 100644
--- a/folks/abstract-field-details.vala
+++ b/folks/abstract-field-details.vala
@@ -98,7 +98,7 @@ public abstract class Folks.AbstractFieldDetails<T> : Object
   public virtual T @value
     {
       get { return this._value; }
-      set { this._value = value; }
+      construct set { this._value = value; }
     }
 
   /**
@@ -148,7 +148,7 @@ public abstract class Folks.AbstractFieldDetails<T> : Object
   public virtual MultiMap<string, string> parameters
     {
       get { return this._parameters; }
-      set
+      construct set
         {
           if (value == null)
             this._parameters.clear ();
diff --git a/folks/avatar-cache.vala b/folks/avatar-cache.vala
index 4be9ef3..3dc0e39 100644
--- a/folks/avatar-cache.vala
+++ b/folks/avatar-cache.vala
@@ -40,6 +40,11 @@ public class Folks.AvatarCache : Object
    */
   private AvatarCache ()
     {
+      Object ();
+    }
+
+  construct
+    {
       this._cache_directory =
           File.new_for_path (Environment.get_user_cache_dir ())
               .get_child ("folks")
diff --git a/folks/backend-store.vala b/folks/backend-store.vala
index c71a676..9832bd4 100644
--- a/folks/backend-store.vala
+++ b/folks/backend-store.vala
@@ -130,6 +130,11 @@ public class Folks.BackendStore : Object {
 
   private BackendStore ()
     {
+      Object ();
+    }
+
+  construct
+    {
       /* Treat this as a library init function */
       var debug_no_colour = Environment.get_variable ("FOLKS_DEBUG_NO_COLOUR");
       this._debug =
diff --git a/folks/debug.vala b/folks/debug.vala
index cb8f652..060401a 100644
--- a/folks/debug.vala
+++ b/folks/debug.vala
@@ -255,7 +255,11 @@ public class Folks.Debug : Object
   private Debug ()
     {
       /* Private constructor for singleton */
+      Object ();
+    }
 
+  construct
+    {
       this._domains_handled = new HashSet<string> ();
 
       /* Install a log handler for log messages emitted as a result of
diff --git a/folks/individual-aggregator.vala b/folks/individual-aggregator.vala
index b349ce4..2956174 100644
--- a/folks/individual-aggregator.vala
+++ b/folks/individual-aggregator.vala
@@ -273,6 +273,11 @@ public class Folks.IndividualAggregator : Object
    */
   public IndividualAggregator ()
     {
+      Object ();
+    }
+
+  construct
+    {
       this._stores = new HashMap<string, PersonaStore> ();
       this._individuals = new HashMap<string, Individual> ();
       this._individuals_ro = this._individuals.read_only_view;
diff --git a/folks/individual.vala b/folks/individual.vala
index 0eae4b5..a101133 100644
--- a/folks/individual.vala
+++ b/folks/individual.vala
@@ -99,24 +99,19 @@ public class Folks.Individual : Object,
     UrlDetails,
     WebServiceDetails
 {
-  private bool _is_favourite;
-  private string _alias;
-  private HashSet<string> _groups;
-  private Set<string> _groups_ro;
   /* Stores the Personas contained in this Individual. */
-  private HashSet<Persona> _persona_set;
+  private HashSet<Persona> _persona_set =
+      new HashSet<Persona> (direct_hash, direct_equal);
   /* Read-only view of the above set */
   private Set<Persona> _persona_set_ro;
   /* Mapping from PersonaStore -> number of Personas from that store contained
    * in this Individual. There shouldn't be any entries with a number < 1.
    * This is used for working out when to disconnect from store signals. */
-  private HashMap<PersonaStore, uint> _stores;
+  private HashMap<PersonaStore, uint> _stores =
+      new HashMap<PersonaStore, uint> (null, null);
   /* The number of Personas in this Individual which have
    * Persona.is_user == true. Iff this is > 0, Individual.is_user == true. */
   private uint _persona_user_count = 0;
-  private HashMultiMap<string, ImFieldDetails> _im_addresses;
-  private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
-  private string _nickname = "";
 
   /**
    * The trust level of the Individual.
@@ -282,6 +277,8 @@ public class Folks.Individual : Object,
    */
   public signal void removed (Individual? replacement_individual);
 
+  private string _alias = "";
+
   /**
    * { inheritDoc}
    */
@@ -370,6 +367,8 @@ public class Folks.Individual : Object,
       set { this.change_full_name.begin (value); } /* not writeable */
     }
 
+  private string _nickname = "";
+
   /**
    * { inheritDoc}
    */
@@ -451,7 +450,9 @@ public class Folks.Individual : Object,
       set { this.change_gender.begin (value); } /* not writeable */
     }
 
-  private HashSet<UrlFieldDetails> _urls;
+  private HashSet<UrlFieldDetails> _urls = new HashSet<UrlFieldDetails> (
+      (GLib.HashFunc) UrlFieldDetails.hash,
+      (GLib.EqualFunc) UrlFieldDetails.equal);
   private Set<UrlFieldDetails> _urls_ro;
 
   /**
@@ -464,7 +465,10 @@ public class Folks.Individual : Object,
       set { this.change_urls.begin (value); } /* not writeable */
     }
 
-  private HashSet<PhoneFieldDetails> _phone_numbers;
+  private HashSet<PhoneFieldDetails> _phone_numbers =
+      new HashSet<PhoneFieldDetails> (
+          (GLib.HashFunc) PhoneFieldDetails.hash,
+          (GLib.EqualFunc) PhoneFieldDetails.equal);
   private Set<PhoneFieldDetails> _phone_numbers_ro;
 
   /**
@@ -477,7 +481,10 @@ public class Folks.Individual : Object,
       set { this.change_phone_numbers.begin (value); } /* not writeable */
     }
 
-  private HashSet<EmailFieldDetails> _email_addresses;
+  private HashSet<EmailFieldDetails> _email_addresses =
+      new HashSet<EmailFieldDetails> (
+          (GLib.HashFunc) EmailFieldDetails.hash,
+          (GLib.EqualFunc) EmailFieldDetails.equal);
   private Set<EmailFieldDetails> _email_addresses_ro;
 
   /**
@@ -490,7 +497,9 @@ public class Folks.Individual : Object,
       set { this.change_email_addresses.begin (value); } /* not writeable */
     }
 
-  private HashSet<RoleFieldDetails> _roles;
+  private HashSet<RoleFieldDetails> _roles = new HashSet<RoleFieldDetails> (
+      (GLib.HashFunc) RoleFieldDetails.hash,
+      (GLib.EqualFunc) RoleFieldDetails.equal);
   private Set<RoleFieldDetails> _roles_ro;
 
   /**
@@ -503,7 +512,7 @@ public class Folks.Individual : Object,
       set { this.change_roles.begin (value); } /* not writeable */
     }
 
-  private HashSet<string> _local_ids;
+  private HashSet<string> _local_ids = new HashSet<string> ();
   private Set<string> _local_ids_ro;
 
   /**
@@ -540,7 +549,9 @@ public class Folks.Individual : Object,
       set { this.change_calendar_event_id.begin (value); } /* not writeable */
     }
 
-  private HashSet<NoteFieldDetails> _notes;
+  private HashSet<NoteFieldDetails> _notes = new HashSet<NoteFieldDetails> (
+      (GLib.HashFunc) NoteFieldDetails.hash,
+      (GLib.EqualFunc) NoteFieldDetails.equal);
   private Set<NoteFieldDetails> _notes_ro;
 
   /**
@@ -553,7 +564,10 @@ public class Folks.Individual : Object,
       set { this.change_notes.begin (value); } /* not writeable */
     }
 
-  private HashSet<PostalAddressFieldDetails> _postal_addresses;
+  private HashSet<PostalAddressFieldDetails> _postal_addresses =
+      new HashSet<PostalAddressFieldDetails> (
+          (GLib.HashFunc) PostalAddressFieldDetails.hash,
+          (GLib.EqualFunc) PostalAddressFieldDetails.equal);
   private Set<PostalAddressFieldDetails> _postal_addresses_ro;
 
   /**
@@ -566,6 +580,8 @@ public class Folks.Individual : Object,
       set { this.change_postal_addresses.begin (value); } /* not writeable */
     }
 
+  private bool _is_favourite = false;
+
   /**
    * Whether this Individual is a user-defined favourite.
    *
@@ -638,6 +654,9 @@ public class Folks.Individual : Object,
       this.notify_property ("is-favourite");
     }
 
+  private HashSet<string> _groups = new HashSet<string> ();
+  private Set<string> _groups_ro;
+
   /**
    * { inheritDoc}
    */
@@ -696,6 +715,10 @@ public class Folks.Individual : Object,
       this._update_groups ();
     }
 
+  private HashMultiMap<string, ImFieldDetails> _im_addresses =
+      new HashMultiMap<string, ImFieldDetails> (
+          null, null, ImFieldDetails.hash, (EqualFunc) ImFieldDetails.equal);
+
   /**
    * { inheritDoc}
    */
@@ -706,6 +729,11 @@ public class Folks.Individual : Object,
       set { this.change_im_addresses.begin (value); } /* not writeable */
     }
 
+  private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses =
+      new HashMultiMap<string, WebServiceFieldDetails> (null, null,
+          (GLib.HashFunc) WebServiceFieldDetails.hash,
+          (GLib.EqualFunc) WebServiceFieldDetails.equal);
+
   /**
    * { inheritDoc}
    */
@@ -889,49 +917,23 @@ public class Folks.Individual : Object,
    */
   public Individual (Set<Persona>? personas)
     {
+      Object (personas: personas);
+    }
+
+  construct
+    {
       debug ("Creating new Individual with %u Personas: %p",
-          (personas != null ? personas.size : 0), this);
+          (this.personas != null ? this.personas.size : 0), this);
 
-      this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
-          null, null, ImFieldDetails.hash, (EqualFunc) ImFieldDetails.equal);
-      this._web_service_addresses =
-        new HashMultiMap<string, WebServiceFieldDetails> (
-            null, null,
-            (GLib.HashFunc) WebServiceFieldDetails.hash,
-            (GLib.EqualFunc) WebServiceFieldDetails.equal);
-      this._persona_set =
-          new HashSet<Persona> (direct_hash, direct_equal);
       this._persona_set_ro = this._persona_set.read_only_view;
-      this._stores = new HashMap<PersonaStore, uint> (null, null);
-      this._gender = Gender.UNSPECIFIED;
-      this._urls = new HashSet<UrlFieldDetails> (
-          (GLib.HashFunc) UrlFieldDetails.hash,
-          (GLib.EqualFunc) UrlFieldDetails.equal);
       this._urls_ro = this._urls.read_only_view;
-      this._phone_numbers = new HashSet<PhoneFieldDetails> (
-          (GLib.HashFunc) PhoneFieldDetails.hash,
-          (GLib.EqualFunc) PhoneFieldDetails.equal);
       this._phone_numbers_ro = this._phone_numbers.read_only_view;
-      this._email_addresses = new HashSet<EmailFieldDetails> (
-          (GLib.HashFunc) EmailFieldDetails.hash,
-          (GLib.EqualFunc) EmailFieldDetails.equal);
       this._email_addresses_ro = this._email_addresses.read_only_view;
-      this._roles = new HashSet<RoleFieldDetails> (
-          (GLib.HashFunc) RoleFieldDetails.hash,
-          (GLib.EqualFunc) RoleFieldDetails.equal);
       this._roles_ro = this._roles.read_only_view;
-      this._local_ids = new HashSet<string> ();
       this._local_ids_ro = this._local_ids.read_only_view;
-      this._postal_addresses = new HashSet<PostalAddressFieldDetails> (
-          (GLib.HashFunc) PostalAddressFieldDetails.hash,
-          (GLib.EqualFunc) PostalAddressFieldDetails.equal);
       this._postal_addresses_ro = this._postal_addresses.read_only_view;
-      this._notes = new HashSet<NoteFieldDetails> (
-          (GLib.HashFunc) NoteFieldDetails.hash,
-          (GLib.EqualFunc) NoteFieldDetails.equal);
       this._notes_ro = this._notes.read_only_view;
-
-      this.personas = personas;
+      this._groups_ro = this._groups.read_only_view;
     }
 
   ~Individual ()
@@ -1151,13 +1153,6 @@ public class Folks.Individual : Object,
     {
       var new_groups = new HashSet<string> ();
 
-      /* this._groups is null during initial construction */
-      if (this._groups == null)
-        {
-          this._groups = new HashSet<string> ();
-          this._groups_ro = this._groups.read_only_view;
-        }
-
       /* FIXME: this should partition the personas by store (maybe we should
        * keep that mapping in general in this class), and execute
        * "groups-changed" on the store (with the set of personas), to allow the
diff --git a/folks/note-details.vala b/folks/note-details.vala
index 9acb2f3..838191a 100644
--- a/folks/note-details.vala
+++ b/folks/note-details.vala
@@ -69,12 +69,9 @@ public class Folks.NoteFieldDetails : AbstractFieldDetails<string>
       MultiMap<string, string>? parameters = null,
       string? uid = null)
     {
-      this.value = value;
-      if (parameters != null)
-        this.parameters = parameters;
-
-      /* These are kept the same value now */
-      this.id = uid;
+      Object (value: value,
+              id: uid,
+              parameters: parameters);
     }
 
   /**
diff --git a/folks/object-cache.vala b/folks/object-cache.vala
index 9ead921..de06200 100644
--- a/folks/object-cache.vala
+++ b/folks/object-cache.vala
@@ -50,8 +50,6 @@ public abstract class Folks.ObjectCache<T> : Object
    * are used (for version numbers). */
   private static const size_t _HEADER_WIDTH = 8; /* bytes */
 
-  private string _type_id;
-  private string _id;
   private File _cache_directory;
   private File _cache_file;
 
@@ -108,6 +106,31 @@ public abstract class Folks.ObjectCache<T> : Object
       uint8 object_version);
 
   /**
+   * A string identifying the type of object being cached.
+   *
+   * This has to be suitable for use as a directory name; i.e. lower case,
+   * hyphen-separated tokens.
+   *
+   * @since UNRELEASED
+   */
+  public string type_id { get; construct; }
+
+  /**
+   * A string identifying the particular cache instance.
+   *
+   * This will form the file name of the cache file, but will be escaped
+   * beforehand, so can be an arbitrary non-empty string.
+   *
+   * @since UNRELEASED
+   */
+  public string id
+    {
+      get { return this._id; }
+      construct { assert (value != ""); this._id = value; }
+    }
+  private string _id;
+
+  /**
    * Create a new cache instance using the given type ID and ID. This is
    * protected as the `type_id` will typically be set statically by subclasses.
    *
@@ -123,20 +146,22 @@ public abstract class Folks.ObjectCache<T> : Object
    */
   protected ObjectCache (string type_id, string id)
     {
-      assert (id != "");
-
-      this._type_id = type_id;
-      this._id = id;
+      Object (type_id: type_id,
+              id: id);
+    }
 
+  construct
+    {
       debug ("Creating object cache for type ID '%s' with ID '%s'.",
-          type_id, id);
+          this.type_id, this.id);
 
       this._cache_directory =
           File.new_for_path (Environment.get_user_cache_dir ())
               .get_child ("folks")
-              .get_child (type_id);
+              .get_child (this.type_id);
       this._cache_file =
-          this._cache_directory.get_child (Uri.escape_string (id, "", false));
+          this._cache_directory.get_child (Uri.escape_string (this.id,
+              "", false));
     }
 
   /**
@@ -159,7 +184,7 @@ public abstract class Folks.ObjectCache<T> : Object
   public async Set<T>? load_objects (Cancellable? cancellable = null)
     {
       debug ("Loading cache (type ID '%s', ID '%s') from file '%s'.",
-          this._type_id, this._id, this._cache_file.get_path ());
+          this.type_id, this._id, this._cache_file.get_path ());
 
       // Read in the file
       uint8[] data;
@@ -247,11 +272,11 @@ public abstract class Folks.ObjectCache<T> : Object
       // Unpack the stored data
       var type_id = variant.get_child_value (0).get_string ();
 
-      if (type_id != this._type_id)
+      if (type_id != this.type_id)
         {
           warning ("Cache file '%s' had type ID '%s', but '%s' was expected." +
               "The file was deleted.", this._cache_file.get_path (), type_id,
-              this._type_id);
+              this.type_id);
           yield this.clear_cache ();
 
           return null;
@@ -303,7 +328,7 @@ public abstract class Folks.ObjectCache<T> : Object
       Cancellable? cancellable = null)
     {
       debug ("Storing cache (type ID '%s', ID '%s') to file '%s'.",
-          this._type_id, this._id, this._cache_file.get_path ());
+          this.type_id, this._id, this._cache_file.get_path ());
 
       var child_type = this.get_serialised_object_type (uint8.MAX);
       assert (child_type != null); // uint8.MAX should always be supported
@@ -321,7 +346,7 @@ public abstract class Folks.ObjectCache<T> : Object
       var object_version = this.get_serialised_object_version ();
 
       var variant = new Variant.tuple ({
-        new Variant.string (this._type_id), // Type ID
+        new Variant.string (this.type_id), // Type ID
         new Variant.string (this._id), // ID
         new Variant.array (child_type, children) // Array of objects
       });
@@ -387,7 +412,7 @@ public abstract class Folks.ObjectCache<T> : Object
   public async void clear_cache ()
     {
       debug ("Clearing cache (type ID '%s', ID '%s'); deleting file '%s'.",
-          this._type_id, this._id, this._cache_file.get_path ());
+          this.type_id, this._id, this._cache_file.get_path ());
 
       try
         {
diff --git a/folks/phone-details.vala b/folks/phone-details.vala
index 120b7a7..f71f34f 100644
--- a/folks/phone-details.vala
+++ b/folks/phone-details.vala
@@ -66,9 +66,8 @@ public class Folks.PhoneFieldDetails : AbstractFieldDetails<string>
   public PhoneFieldDetails (string value,
       MultiMap<string, string>? parameters = null)
     {
-      this.value = value;
-      if (parameters != null)
-        this.parameters = parameters;
+      Object (value: value,
+              parameters: parameters);
     }
 
   /**
diff --git a/folks/postal-address-details.vala b/folks/postal-address-details.vala
index 3cd3bd9..6a6c8cc 100644
--- a/folks/postal-address-details.vala
+++ b/folks/postal-address-details.vala
@@ -259,13 +259,14 @@ public class Folks.PostalAddressFieldDetails :
   public PostalAddressFieldDetails (PostalAddress value,
       MultiMap<string, string>? parameters = null)
     {
-      this.value = value;
-      if (parameters != null)
-        this.parameters = parameters;
-
-      /* We keep these sync'd both directions */
-      this.id = this.value.uid;
+      /* We keep id and value.uid synchronised in both directions. */
+      Object (value: value,
+              parameters: parameters,
+              id: value.uid);
+    }
 
+  construct
+    {
       /* Keep the PostalAddress.uid sync'd to our id */
       this.value.notify["uid"].connect ((s, p) =>
         {
diff --git a/folks/potential-match.vala b/folks/potential-match.vala
index 44ac2d2..82e2e13 100644
--- a/folks/potential-match.vala
+++ b/folks/potential-match.vala
@@ -83,21 +83,17 @@ public class Folks.PotentialMatch : Object
    *
    * @since 0.5.1
    */
-  public static Set<string> known_email_aliases = null;
+  public static Set<string> known_email_aliases =
+      new Gee.HashSet<string> (str_hash, str_equal);
 
   private static double _DIST_THRESHOLD = 0.70;
   private const string _SEPARATORS = "._-+";
 
-  public PotentialMatch ()
+  static construct
     {
-      if (this.known_email_aliases == null)
-        {
-          this.known_email_aliases = new Gee.HashSet<string> (str_hash,
-              str_equal);
-          this.known_email_aliases.add ("admin");
-          this.known_email_aliases.add ("abuse");
-          this.known_email_aliases.add ("webmaster");
-        }
+      PotentialMatch.known_email_aliases.add ("admin");
+      PotentialMatch.known_email_aliases.add ("abuse");
+      PotentialMatch.known_email_aliases.add ("webmaster");
     }
 
   /**
@@ -265,7 +261,7 @@ public class Folks.PotentialMatch : Object
 
               if (fd_a.value == fd_b.value)
                 {
-                  if (this.known_email_aliases.contains
+                  if (PotentialMatch.known_email_aliases.contains
                       (email_split_a[0]) == true)
                     {
                       if (this._result < MatchResult.HIGH)
diff --git a/folks/role-details.vala b/folks/role-details.vala
index c1fd042..a910c92 100644
--- a/folks/role-details.vala
+++ b/folks/role-details.vala
@@ -177,13 +177,14 @@ public class Folks.RoleFieldDetails : AbstractFieldDetails<Role>
   public RoleFieldDetails (Role value,
       MultiMap<string, string>? parameters = null)
     {
-      this.value = value;
-      if (parameters != null)
-        this.parameters = parameters;
-
-      /* We keep these sync'd both directions */
-      this.id = this.value.uid;
+      /* We keep id and value.uid synchronised in both directions. */
+      Object (value: value,
+              parameters: parameters,
+              id: value.uid);
+    }
 
+  construct
+    {
       /* Keep the Role.uid sync'd to our id */
       this.value.notify["uid"].connect ((s, p) =>
         {
diff --git a/folks/url-details.vala b/folks/url-details.vala
index 494b843..501588d 100644
--- a/folks/url-details.vala
+++ b/folks/url-details.vala
@@ -86,9 +86,8 @@ public class Folks.UrlFieldDetails : AbstractFieldDetails<string>
   public UrlFieldDetails (string value,
       MultiMap<string, string>? parameters = null)
     {
-      this.value = value;
-      if (parameters != null)
-        this.parameters = parameters;
+      Object (value: value,
+              parameters: parameters);
     }
 
   /**
diff --git a/folks/web-service-details.vala b/folks/web-service-details.vala
index 3b6f405..4717183 100644
--- a/folks/web-service-details.vala
+++ b/folks/web-service-details.vala
@@ -47,9 +47,8 @@ public class Folks.WebServiceFieldDetails : AbstractFieldDetails<string>
   public WebServiceFieldDetails (string value,
       MultiMap<string, string>? parameters = null)
     {
-      this.value = value;
-      if (parameters != null)
-        this.parameters = parameters;
+      Object (value: value,
+              parameters: parameters);
     }
 
   /**



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