Commit 6ffb9775 authored by Philip Withnall's avatar Philip Withnall
Browse files

Bug 652660 — Make Individual.id more stable and well-defined

parent daf213dc
......@@ -9,6 +9,7 @@ Bugs fixed:
* Bug 648071 — Add support for presence status from Telepathy
* Bug 652472 — Handle failure in getting the logger service better
* Bug 629716 — Migrate Folks to GDBus
* Bug 652660 — Make Individual.id more stable and well-defined
API changes:
* Swf.Persona retains and exposes its libsocialweb Contact
......
......@@ -163,9 +163,17 @@ public class Folks.Individual : Object,
* A unique identifier for the Individual.
*
* This uniquely identifies the Individual, and persists across
* {@link IndividualAggregator} instances.
* {@link IndividualAggregator} instances. It may not persist across linking
* the Individual with other Individuals.
*
* FIXME: Will this.id actually be the persistent ID for storage?
* This is an opaque string and has no structure.
*
* If an identifier is required which will be used for a long-lived link
* between different stored data, it may be more desirable to use the
* {@link Persona.uid} of the most relevant {@link Persona} in the Individual
* instead. For example, if storing references to Individuals who are tagged
* in a photo, it may be safer to store the UID of the Persona whose backend
* provided the photo (e.g. Facebook).
*/
public string id { get; private set; }
......@@ -1517,14 +1525,54 @@ public class Folks.Individual : Object,
return;
}
/* TODO: Base this upon our ID in permanent storage, once we have that. */
if (this.id == null && this._persona_set.size > 0)
/* Update the ID. We choose the most interesting Persona in the
* Individual and hash their UID. This is guaranteed to be globally
* unique, and may not change (for one of the two Individuals) if we link
* two Individuals together, which is nice though we can't rely on this
* behaviour.
*
* This method of constructing an ID ensures that it'll be unique and
* stable for a given Individual once the IndividualAggregator reaches
* a quiescent state after startup. It guarantees that the ID will be
* the same every time folks is used, until the Individual is linked
* or unlinked to another Individual.
*
* We choose the most interesting Persona by ranking all the Personas
* in the Individual by:
* 1. store.is-writeable
* 2. store.trust-level
* 3. store.id (alphabetically)
*
* Note that this heuristic shouldn't be changed without careful thought,
* since stored references to IDs may be broken by the change.
*/
if (this._persona_set.size > 0)
{
Persona? chosen_persona = null;
foreach (var persona in this._persona_set)
{
this.id = persona.uid;
break;
if (chosen_persona == null ||
(chosen_persona.store.is_writeable == false &&
persona.store.is_writeable == true) ||
(chosen_persona.store.is_writeable ==
persona.store.is_writeable &&
chosen_persona.store.trust_level >
persona.store.trust_level) ||
(chosen_persona.store.is_writeable ==
persona.store.is_writeable &&
chosen_persona.store.trust_level ==
persona.store.trust_level &&
chosen_persona.store.id > persona.store.id)
)
{
chosen_persona = persona;
}
}
// Hash the chosen persona's UID
this.id = Checksum.compute_for_string (ChecksumType.SHA1,
chosen_persona.uid);
}
/* Update our aggregated fields and notify the changes */
......
......@@ -6,7 +6,7 @@ public class AggregationTests : Folks.TestCase
{
private KfTest.Backend _kf_backend;
private TpTest.Backend _tp_backend;
private HashSet<string> _default_individuals;
private HashSet<string> _default_personas;
private int _test_timeout = 3;
public AggregationTests ()
......@@ -17,16 +17,16 @@ public class AggregationTests : Folks.TestCase
this._tp_backend = new TpTest.Backend ();
/* Create a set of the individuals we expect to see */
this._default_individuals = new HashSet<string> (str_hash, str_equal);
this._default_personas = new HashSet<string> (str_hash, str_equal);
this._default_individuals.add ("travis@example.com");
this._default_individuals.add ("olivier@example.com");
this._default_individuals.add ("guillaume@example.com");
this._default_individuals.add ("sjoerd@example.com");
this._default_individuals.add ("christian@example.com");
this._default_individuals.add ("wim@example.com");
this._default_individuals.add ("helen@example.com");
this._default_individuals.add ("geraldine@example.com");
this._default_personas.add ("travis@example.com");
this._default_personas.add ("olivier@example.com");
this._default_personas.add ("guillaume@example.com");
this._default_personas.add ("sjoerd@example.com");
this._default_personas.add ("christian@example.com");
this._default_personas.add ("wim@example.com");
this._default_personas.add ("helen@example.com");
this._default_personas.add ("geraldine@example.com");
/* Set up the tests */
this.add_test ("IID", this.test_iid);
......@@ -69,9 +69,37 @@ public class AggregationTests : Folks.TestCase
void* account2_handle = this._tp_backend.add_account ("protocol",
"me2@example.com", "cm", "account2");
/* IDs of the individuals we expect to see.
* These are externally opaque, but internally are SHA-1 hashes of the
* concatenated UIDs of the Personas in the Individual. In these cases,
* each default_individual contains two Personas with the same IID.
* e.g.
* telepathy:/org/freedesktop/Telepathy/Account/cm/protocol/account2:sjoerd@example.com
* and
* telepathy:/org/freedesktop/Telepathy/Account/cm/protocol/account:sjoerd@example.com
* in a single Individual. */
var default_individuals = new HashSet<string> ();
/* guillaume@example.com */
default_individuals.add ("6380b17dc511b21a1defd4811f1add97b278f92c");
/* sjoerd@example.com */
default_individuals.add ("6b08188cb2ef8cbaca140b277780069b5af8add6");
/* travis@example.com */
default_individuals.add ("60c91326018f6a60604f8d260fc24a60a5b8512c");
/* olivier@example.com */
default_individuals.add ("0e46c5e74f61908f49550d241f2a1651892a1695");
/* christian@example.com */
default_individuals.add ("07b913b8977c04d2f2011e26a46ea3e3dcfe3e3d");
/* geraldine@example.com */
default_individuals.add ("f948d4d2af79085ab860f0ef67bf0c201c4602d4");
/* helen@example.com */
default_individuals.add ("f34529a442577b840a75271b464e90666c38c464");
/* wim@example.com */
default_individuals.add ("467d13f955e62bf30ebf9620fa052aaee2160260");
/* Work on a copy of the set of individuals so we can mangle it */
HashSet<string> expected_individuals = new HashSet<string> ();
foreach (var id in this._default_individuals)
foreach (var id in default_individuals)
{
expected_individuals.add (id);
}
......@@ -84,14 +112,11 @@ public class AggregationTests : Folks.TestCase
* individuals (if they were originally on it) */
foreach (Individual i in removed)
{
var parts = i.id.split (":");
var id = parts[2];
if (!i.is_user &&
i.personas.size == 2 &&
this._default_individuals.contains (id))
default_individuals.contains (i.id))
{
expected_individuals.add (id);
expected_individuals.add (i.id);
}
}
......@@ -99,15 +124,12 @@ public class AggregationTests : Folks.TestCase
* from the set of expected individuals. */
foreach (Individual i in added)
{
var parts = i.id.split (":");
var id = parts[2];
var personas = i.personas;
/* We're not testing the user here */
if (!i.is_user && personas.size == 2)
{
assert (expected_individuals.remove (id));
assert (expected_individuals.remove (i.id));
string iid = null;
foreach (var persona in personas)
......@@ -441,7 +463,7 @@ public class AggregationTests : Folks.TestCase
* from a different persona store. */
var expected_personas1 = new HashSet<string> ();
var expected_personas2 = new HashSet<string> ();
foreach (var id in this._default_individuals)
foreach (var id in this._default_personas)
{
expected_personas1.add (id);
expected_personas2.add (id);
......
......@@ -9,8 +9,6 @@ public class IndividualPropertiesTests : Folks.TestCase
{
private TpTest.Backend tp_backend;
private void* _account_handle;
private string individual_id_prefix =
"telepathy:/org/freedesktop/Telepathy/Account/cm/protocol/account:";
private int _test_timeout = 3;
public IndividualPropertiesTests ()
......@@ -53,8 +51,9 @@ public class IndividualPropertiesTests : Folks.TestCase
{
foreach (Individual i in added)
{
/* We only check one */
if (i.id != (this.individual_id_prefix + "olivier@example.com"))
/* We only check one (singleton Individual containing just
* olivier@example.com) */
if (i.id != "0e46c5e74f61908f49550d241f2a1651892a1695")
{
continue;
}
......@@ -104,8 +103,9 @@ public class IndividualPropertiesTests : Folks.TestCase
foreach (Individual i in added)
{
/* We only check one */
if (i.id != (this.individual_id_prefix + "olivier@example.com"))
/* We only check one (singleton Individual containing just
* olivier@example.com) */
if (i.id != "0e46c5e74f61908f49550d241f2a1651892a1695")
{
continue;
}
......@@ -172,8 +172,9 @@ public class IndividualPropertiesTests : Folks.TestCase
foreach (Individual i in added)
{
/* We only check one */
if (i.id != (this.individual_id_prefix + "olivier@example.com"))
/* We only check one (singleton Individual containing just
* olivier@example.com) */
if (i.id != "0e46c5e74f61908f49550d241f2a1651892a1695")
{
continue;
}
......
......@@ -10,8 +10,6 @@ public class IndividualRetrievalTests : Folks.TestCase
private TpTest.Backend tp_backend;
private void* _account_handle;
private HashSet<string> default_individuals;
private string individual_id_prefix =
"telepathy:/org/freedesktop/Telepathy/Account/cm/protocol/account:";
private int _test_timeout = 3;
public IndividualRetrievalTests ()
......@@ -20,18 +18,33 @@ public class IndividualRetrievalTests : Folks.TestCase
this.tp_backend = new TpTest.Backend ();
/* Create a set of the individuals we expect to see */
/* IDs of the individuals we expect to see.
* These are externally opaque, but internally are SHA-1 hashes of the
* concatenated UIDs of the Personas in the Individual. In these cases,
* each default_individual contains one Persona.
* e.g.
* telepathy:/org/freedesktop/Telepathy/Account/cm/protocol/account:me@example.com
* only in each Individual. */
this.default_individuals = new HashSet<string> (str_hash, str_equal);
var prefix = this.individual_id_prefix;
default_individuals.add (prefix + "travis@example.com");
default_individuals.add (prefix + "olivier@example.com");
default_individuals.add (prefix + "guillaume@example.com");
default_individuals.add (prefix + "sjoerd@example.com");
default_individuals.add (prefix + "christian@example.com");
default_individuals.add (prefix + "wim@example.com");
default_individuals.add (prefix + "helen@example.com");
default_individuals.add (prefix + "geraldine@example.com");
/* me@example.com */
default_individuals.add ("48fa372a81026063187255e3f5c184665d2ed7ce");
/* travis@example.com */
default_individuals.add ("60c91326018f6a60604f8d260fc24a60a5b8512c");
/* guillaume@example.com */
default_individuals.add ("6380b17dc511b21a1defd4811f1add97b278f92c");
/* olivier@example.com */
default_individuals.add ("0e46c5e74f61908f49550d241f2a1651892a1695");
/* sjoerd@example.com */
default_individuals.add ("6b08188cb2ef8cbaca140b277780069b5af8add6");
/* geraldine@example.com */
default_individuals.add ("f948d4d2af79085ab860f0ef67bf0c201c4602d4");
/* helen@example.com */
default_individuals.add ("f34529a442577b840a75271b464e90666c38c464");
/* wim@example.com */
default_individuals.add ("467d13f955e62bf30ebf9620fa052aaee2160260");
/* christian@example.com */
default_individuals.add ("07b913b8977c04d2f2011e26a46ea3e3dcfe3e3d");
this.add_test ("aggregator", this.test_aggregator);
this.add_test ("aggregator:add", this.test_aggregator_add);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment