[folks] Add linking support to the IndividualAggregator



commit 4e73c105f32f683cdd51e269fb15262c7f345402
Author: Philip Withnall <philip withnall collabora co uk>
Date:   Mon Jul 19 16:37:46 2010 +0100

    Add linking support to the IndividualAggregator
    
    Personas are linked together to form Individuals using a link map containing
    mappings from UIDs and linkable properties to Individual instances. This
    takes PersonaStore trust levels into account.

 backends/key-file/kf-persona.vala |   25 +++++++++
 folks/individual-aggregator.vala  |  108 +++++++++++++++++++++++++++++-------
 folks/persona.vala                |   34 ++++++++++++
 3 files changed, 146 insertions(+), 21 deletions(-)
---
diff --git a/backends/key-file/kf-persona.vala b/backends/key-file/kf-persona.vala
index 54b0a7e..a40dd0a 100644
--- a/backends/key-file/kf-persona.vala
+++ b/backends/key-file/kf-persona.vala
@@ -121,4 +121,29 @@ public class Folks.Backends.Kf.Persona : Folks.Persona,
           GLib.assert_not_reached ();
         }
     }
+
+  public override void linkable_property_to_links (string prop_name,
+      Folks.Persona.LinkablePropertyCallback callback)
+    {
+      if (prop_name == "im-addresses")
+        {
+          this.im_addresses.foreach ((k, v) =>
+            {
+              unowned string protocol = (string) k;
+              unowned GenericArray<string> im_addresses =
+                  (GenericArray<string>) v;
+
+              im_addresses.foreach ((v) =>
+                {
+                  unowned string address = (string) v;
+                  callback (protocol + ":" + address);
+                });
+            });
+        }
+      else
+        {
+          /* Chain up */
+          base.linkable_property_to_links (prop_name, callback);
+        }
+    }
 }
diff --git a/folks/individual-aggregator.vala b/folks/individual-aggregator.vala
index 498b651..9f900e0 100644
--- a/folks/individual-aggregator.vala
+++ b/folks/individual-aggregator.vala
@@ -48,6 +48,7 @@ public class Folks.IndividualAggregator : Object
   private BackendStore backend_store;
   private HashMap<string, PersonaStore> stores;
   private HashSet<Backend> backends;
+  private HashTable<string, Individual> link_map;
 
   /**
    * A table mapping { link Individual.id}s to their { link Individual}s.
@@ -101,6 +102,7 @@ public class Folks.IndividualAggregator : Object
       this.stores = new HashMap<string, PersonaStore> ();
       this.individuals = new HashTable<string, Individual> (str_hash,
           str_equal);
+      this.link_map = new HashTable<string, Individual> (str_hash, str_equal);
 
       this.backends = new HashSet<Backend> ();
 
@@ -200,35 +202,99 @@ public class Folks.IndividualAggregator : Object
       Persona? actor,
       Groups.ChangeReason reason)
     {
-      var individuals = new GLib.List<Individual> ();
-      added.foreach ((persona) =>
+      GLib.List<Individual> new_individuals = new GLib.List<Individual> ();
+
+      added.foreach ((p) =>
         {
-          unowned Persona p = (Persona) persona;
+          unowned Persona persona = (Persona) p;
+          PersonaStoreTrust trust_level = persona.store.trust_level;
+          Individual candidate_ind = null;
+
+          /* If we don't trust the PersonaStore at all, we can't link the
+           * Persona to any existing Individual */
+          if (trust_level != PersonaStoreTrust.NONE)
+            candidate_ind = this.link_map.lookup (persona.uid);
 
-          /* FIXME: correlate the new personas with each other and
-            * the existing personas and existing Individuals;
-            * update existing Individuals and create new ones as
-            * necessary */
+          if (candidate_ind != null)
+            {
+              /* The Persona's UID matches a linkable field which is already in
+               * the link map, so we add the new Persona to that Individual. */
+              GLib.List<unowned Persona> personas =
+                  candidate_ind.personas.copy ();
+              personas.append (persona);
+              candidate_ind.personas = personas;
+            }
+          else
+            {
+              /* The Persona doesn't match anything in the link map, so we
+               * create a new Individual for the Persona. */
+              GLib.List<Persona> personas = new GLib.List<Persona> ();
+              personas.prepend (persona);
+              candidate_ind = new Individual (personas);
+
+              /* Add the new Individual to the aggregator */
+              candidate_ind.removed.connect (this.individual_removed_cb);
+              new_individuals.prepend (candidate_ind);
+              this.individuals.insert (candidate_ind.id, candidate_ind);
+
+              /* Only add the Persona to the link map if we trust its UID. */
+              if (trust_level != PersonaStoreTrust.NONE)
+                this.link_map.insert (persona.uid, candidate_ind);
+            }
 
-          var grouped_personas = new GLib.List<Persona> ();
-          grouped_personas.prepend (p);
-          var individual = new Individual (grouped_personas);
-          individuals.prepend (individual);
+          /* Only allow linking on non-UID properties of the Persona if we fully
+           * trust the PersonaStore it came from. */
+          if (persona.store.trust_level == PersonaStoreTrust.FULL)
+            {
+              /* Insert maps from the Persona's linkable properties to the
+               * Individual. */
+              foreach (string prop_name in persona.linkable_properties)
+                {
+                  unowned ObjectClass pclass = persona.get_class ();
+                  if (pclass.find_property (prop_name) == null)
+                    {
+                      warning ("Unknown property '%s' in linkable property " +
+                          "list.", prop_name);
+                      continue;
+                    }
+
+                  persona.linkable_property_to_links (prop_name, (l) =>
+                    {
+                      this.link_map.insert ((string) l, candidate_ind);
+                    });
+                }
+            }
         });
 
-      /* For each of the individuals constructed from the newly added personas,
-       * if they don't exist in the aggregator's list of member individuals,
-       * add them to it. */
-      GLib.List<Individual> new_individuals = null;
-      foreach (var i in individuals)
+      removed.foreach ((p) =>
         {
-          if (this.individuals.lookup (i.id) == null)
+          unowned Persona persona = (Persona) p;
+          PersonaStoreTrust trust_level = persona.store.trust_level;
+
+          if (trust_level != PersonaStoreTrust.NONE)
+            this.link_map.remove (persona.uid);
+
+          if (trust_level == PersonaStoreTrust.FULL)
             {
-              i.removed.connect (this.individual_removed_cb);
-              new_individuals.prepend (i);
-              this.individuals.insert (i.id, i);
+              /* Remove maps from the Persona's linkable properties to the
+               * Individual. */
+              foreach (string prop_name in persona.linkable_properties)
+                {
+                  unowned ObjectClass pclass = persona.get_class ();
+                  if (pclass.find_property (prop_name) == null)
+                    {
+                      warning ("Unknown property '%s' in linkable property " +
+                          "list.", prop_name);
+                      continue;
+                    }
+
+                  persona.linkable_property_to_links (prop_name, (l) =>
+                    {
+                     this.link_map.remove ((string) l);
+                   });
+                }
             }
-        }
+        });
 
       /* Signal the addition of new individuals to the aggregator */
       if (new_individuals != null)
diff --git a/folks/persona.vala b/folks/persona.vala
index cb0d0f0..f79b1dd 100644
--- a/folks/persona.vala
+++ b/folks/persona.vala
@@ -67,4 +67,38 @@ public abstract class Folks.Persona : Object
    * level is not { link PersonaStoreTrust.FULL}.
    */
   public string[] linkable_properties { get; protected set; }
+
+  /**
+   * Callback into the aggregator to manipulate a link mapping.
+   *
+   * This is a callback provided by the { link IndividualAggregator} whenever
+   * a { link Persona.linkable_property_to_links} method is called, which should
+   * be called by the `linkable_property_to_links` implementation for each
+   * linkable-property-to-individual mapping it wants to add or remove in the
+   * aggregator.
+   */
+  public delegate void LinkablePropertyCallback (string link);
+
+  /* FIXME: This code should move to the IMable interface as a concrete
+   * method of the interface. However, that depends on bgo#624842 */
+  /**
+   * Produce one or more mapping strings for the given property's value.
+   *
+   * This is a virtual method, to be overridden by subclasses of { link Persona}
+   * who have linkable properties. Each of their linkable properties should be
+   * handled by their implementation of this function, examining the current
+   * value of the property and calling `callback` with one or more mapping
+   * strings for the property's value. Each of these mapping strings will be
+   * added to the { link IndividualAggregator}'s link map, related to the
+   * { link Individual} instance which contains this { link Persona}.
+   *
+   * @see Persona.linkable_properties
+   */
+  public virtual void linkable_property_to_links (string prop_name,
+      LinkablePropertyCallback callback)
+    {
+      /* Backend-specific Persona subclasses should override this if they have
+       * any linkable properties */
+      assert_not_reached ();
+    }
 }



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