[folks] core: Add core anti-linking support
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [folks] core: Add core anti-linking support
- Date: Sat, 7 Jul 2012 16:28:49 +0000 (UTC)
commit 1441ae9ddda3dfba8a67d18ecc6a75e16d389553
Author: Philip Withnall <philip tecnocode co uk>
Date: Mon Jul 25 20:27:29 2011 +0100
core: Add core anti-linking support
This adds the core of the anti-linking support, based around a new
AntiLinkable interface. This will be implemented by Persona subclasses which
can store anti-linking information (in the form of a set of Persona UIDs
which the given Persona should never be linked to).
This approach allows anti-linking information to be stored with the personas
(presumably in the primary persona store) and thus it should be network
transparent. i.e. Using folks on two different computers with a Google
Contacts address book as primary should cause the anti-linking data to be
shared.
This also includes the necessary IndividualAggregator changes.
Sadly, no unit tests are included.
Closes: https://bugzilla.gnome.org/show_bug.cgi?id=629537
NEWS | 3 +-
folks/Makefile.am | 1 +
folks/anti-linkable.vala | 157 ++++++++++++++++++++++++++++++++++++++
folks/individual-aggregator.vala | 119 ++++++++++++++++++++++++-----
folks/individual.vala | 60 ++++++++++++++
folks/persona-store.vala | 12 +++-
folks/potential-match.vala | 10 ++-
tools/inspect/utils.vala | 3 +-
8 files changed, 340 insertions(+), 25 deletions(-)
---
diff --git a/NEWS b/NEWS
index 336890a..79462fb 100644
--- a/NEWS
+++ b/NEWS
@@ -6,9 +6,10 @@ Dependencies:
Bugs fixed:
â Bug 673918 â Port to newer libgee
+â Bug 629537 â Support anti-linking
API changes:
-
+â Add AntiLinkable interface and implement it on Kf.Persona and Edsf.Persona
Overview of changes from libfolks 0.7.1 to libfolks 0.7.2
=========================================================
diff --git a/folks/Makefile.am b/folks/Makefile.am
index d283eb6..534ad09 100644
--- a/folks/Makefile.am
+++ b/folks/Makefile.am
@@ -90,6 +90,7 @@ libfolks_la_SOURCES = \
potential-match.vala \
avatar-cache.vala \
object-cache.vala \
+ anti-linkable.vala \
$(NULL)
if ENABLE_EDS
diff --git a/folks/anti-linkable.vala b/folks/anti-linkable.vala
new file mode 100644
index 0000000..e90851e
--- /dev/null
+++ b/folks/anti-linkable.vala
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2011, 2012 Philip Withnall
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Philip Withnall <philip tecnocode co uk>
+ */
+
+using Gee;
+using GLib;
+
+/**
+ * Interface for { link Persona} subclasses from backends which support storage
+ * of, anti-linking data.
+ *
+ * Anti-links are stored as a set of { link Persona.uid}s with each
+ * { link Persona} (A), specifying that A must not be linked into an
+ * { link Individual} with any of the personas in its anti-links set.
+ *
+ * @since UNRELEASED
+ */
+public interface Folks.AntiLinkable : Folks.Persona
+{
+ /**
+ * UIDs of anti-linked { link Persona}s.
+ *
+ * The { link Persona}s identified by their UIDs in this set are guaranteed to
+ * not be linked to this { link Persona}, even if their linkable properties
+ * match.
+ *
+ * No UIDs may be `null`. Well-formed but non-existent UIDs (i.e. UIDs which
+ * can be successfully parsed, but which don't currently correspond to a
+ * { link Persona} instance) are permitted, as personas may appear and
+ * disappear over time.
+ *
+ * It is expected, but not guaranteed, that anti-links made between personas
+ * will be reciprocal. That is, if persona A lists persona B's UID in its
+ * { link AntiLinkable.anti_links} set, persona B will typically also list
+ * persona A in its anti-links set.
+ *
+ * @since UNRELEASED
+ */
+ public abstract Set<string> anti_links { get; set; }
+
+ /**
+ * Change the { link Persona}'s set of anti-links.
+ *
+ * It's preferred to call this rather than setting
+ * { link AntiLinkable.anti_links} directly, as this method gives error
+ * notification and will only return once the anti-links have been written
+ * to the relevant backing store (or the operation's failed).
+ *
+ * It should be noted that { link IndividualAggregator.link_personas} and
+ * { link IndividualAggregator.unlink_individual} will modify the anti-links
+ * sets of the personas they touch, in order to remove and add anti-links,
+ * respectively. It is expected that these { link IndividualAggregator}
+ * methods will be used to modify anti-links indirectly, rather than calling
+ * { link AntiLinkable.change_anti_links} directly.
+ *
+ * @param anti_links the new set of anti-links from this persona
+ * @throws PropertyError if setting the anti-links failed
+ * @since UNRELEASED
+ */
+ public virtual async void change_anti_links (Set<string> anti_links)
+ throws PropertyError
+ {
+ /* Default implementation. */
+ throw new PropertyError.NOT_WRITEABLE (
+ _("Anti-links are not writeable on this contact."));
+ }
+
+ /**
+ * Check for an anti-link with another persona.
+ *
+ * This will return `true` if `other_persona`'s UID is listed in this
+ * persona's anti-links set. Note that this check is not symmetric.
+ *
+ * @param other_persona the persona to check is anti-linked
+ * @return `true` if an anti-link exists, `false` otherwise
+ * @since UNRELEASED
+ */
+ public bool has_anti_link_with_persona (Persona other_persona)
+ {
+ return (other_persona.uid in this.anti_links);
+ }
+
+ /**
+ * Add anti-links to other personas.
+ *
+ * The UIDs of all personas in `other_personas` will be added to this
+ * persona's anti-links set and the changes propagated to backends.
+ *
+ * Any attempt to anti-link a persona with itself is not an error, but is
+ * ignored.
+ *
+ * @param other_personas the personas to anti-link to this one
+ * @throws PropertyError if setting the anti-links failed
+ * @since UNRELEASED
+ */
+ public async void add_anti_links (Set<Persona> other_personas)
+ throws PropertyError
+ {
+ var new_anti_links = new HashSet<string> ();
+ new_anti_links.add_all (this.anti_links);
+
+ foreach (var p in other_personas)
+ {
+ /* Don't anti-link ourselves. */
+ if (p == this)
+ {
+ continue;
+ }
+
+ new_anti_links.add (p.uid);
+ }
+
+ yield this.change_anti_links (new_anti_links);
+ }
+
+ /**
+ * Remove anti-links to other personas.
+ *
+ * The UIDs of all personas in `other_personas` will be removed from this
+ * persona's anti-links set and the changes propagated to backends.
+ *
+ * @param other_personas the personas to remove anti-links from this one
+ * @throws PropertyError if setting the anti-links failed
+ * @since UNRELEASED
+ */
+ public async void remove_anti_links (Set<Persona> other_personas)
+ throws PropertyError
+ {
+ var new_anti_links = new HashSet<string> ();
+ new_anti_links.add_all (this.anti_links);
+
+ foreach (var p in other_personas)
+ {
+ new_anti_links.remove (p.uid);
+ }
+
+ yield this.change_anti_links (new_anti_links);
+ }
+}
+
+/* vim: filetype=vala textwidth=80 tabstop=2 expandtab: */
diff --git a/folks/individual-aggregator.vala b/folks/individual-aggregator.vala
index 4bc1554..7774928 100644
--- a/folks/individual-aggregator.vala
+++ b/folks/individual-aggregator.vala
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2010 Collabora Ltd.
+ * Copyright (C) 2012 Philip Withnall
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -16,6 +17,7 @@
*
* Authors:
* Travis Reitter <travis reitter collabora co uk>
+ * Philip Withnall <philip tecnocode co uk>
*/
using Gee;
@@ -976,7 +978,8 @@ public class Folks.IndividualAggregator : Object
/* If the Persona is the user, we *always* want to link it to the
* existing this.user. */
- if (persona.is_user == true && user != null)
+ if (persona.is_user == true && user != null &&
+ ((!) user).has_anti_link_with_persona (persona) == false)
{
debug (" Found candidate individual '%s' as user.",
((!) user).id);
@@ -994,6 +997,8 @@ public class Folks.IndividualAggregator : Object
{
if (candidate_ind != null &&
((!) candidate_ind).trust_level != TrustLevel.NONE &&
+ ((!) candidate_ind).has_anti_link_with_persona (
+ persona) == false &&
candidate_inds.add ((!) candidate_ind))
{
debug (" Found candidate individual '%s' by " +
@@ -1038,6 +1043,9 @@ public class Folks.IndividualAggregator : Object
if (candidate_ind != null &&
((!) candidate_ind).trust_level !=
TrustLevel.NONE &&
+ ((!) candidate_ind).
+ has_anti_link_with_persona (
+ persona) == false &&
candidate_inds.add ((!) candidate_ind))
{
debug (" Found candidate individual '%s'" +
@@ -1165,6 +1173,24 @@ public class Folks.IndividualAggregator : Object
null, null, GroupDetails.ChangeReason.NONE);
}
+ private void _persona_anti_links_changed_cb (Object obj, ParamSpec pspec)
+ {
+ var persona = obj as Persona;
+
+ /* The anti-links associated with the persona has changed, so that persona
+ * might require re-linking. We do this in a simplistic and hacky way
+ * (which should work) by simply treating the persona as if it's been
+ * removed and re-added. */
+ debug ("Anti-links changed for persona '%s' (is user: %s, IID: %s).",
+ persona.uid, persona.is_user ? "yes" : "no", persona.iid);
+
+ var persona_set = new HashSet<Persona> ();
+ persona_set.add (persona);
+
+ this._personas_changed_cb (persona.store, persona_set, persona_set,
+ null, null, GroupDetails.ChangeReason.NONE);
+ }
+
private void _connect_to_persona (Persona persona)
{
foreach (var prop_name in persona.linkable_properties)
@@ -1172,10 +1198,23 @@ public class Folks.IndividualAggregator : Object
persona.notify[prop_name].connect (
this._persona_linkable_property_changed_cb);
}
+
+ var al = persona as AntiLinkable;
+ if (al != null)
+ {
+ al.notify["anti-links"].connect (this._persona_anti_links_changed_cb);
+ }
}
private void _disconnect_from_persona (Persona persona)
{
+ var al = persona as AntiLinkable;
+ if (al != null)
+ {
+ al.notify["anti-links"].disconnect (
+ this._persona_anti_links_changed_cb);
+ }
+
foreach (var prop_name in persona.linkable_properties)
{
persona.notify[prop_name].disconnect (
@@ -1787,6 +1826,25 @@ public class Folks.IndividualAggregator : Object
return;
}
+ /* Remove all edges in the connected graph between the personas from the
+ * anti-link map to ensure that linking the personas actually succeeds. */
+ foreach (var p in personas)
+ {
+ var al = p as AntiLinkable;
+ if (al != null)
+ {
+ try
+ {
+ yield ((!) al).remove_anti_links (personas);
+ }
+ catch (PropertyError e)
+ {
+ throw new IndividualAggregatorError.PROPERTY_NOT_WRITEABLE (
+ _("Anti-links can't be removed between personas being linked."));
+ }
+ }
+ }
+
/* Create a new persona in the primary store which links together the
* given personas */
assert (((!) this._primary_store).type_id ==
@@ -1921,29 +1979,50 @@ public class Folks.IndividualAggregator : Object
return;
}
- debug ("Unlinking Individual '%s', deleting Personas:", individual.id);
+ debug ("Unlinking Individual '%s':", individual.id);
- /* Remove all the Personas from writeable PersonaStores.
+ /* Add all edges in the connected graph between the personas to the
+ * anti-link map to ensure that unlinking the personas actually succeeds,
+ * and that they aren't immediately re-linked.
*
- * We have to take a copy of the Persona list before removing the
- * Personas, as _personas_changed_cb() (which is called as a result of
- * calling _primary_store.remove_persona()) messes around with Persona
- * lists. */
- var personas = new HashSet<Persona> ();
- foreach (var p in individual.personas)
- {
- personas.add (p);
- }
+ * Perversely, this requires that we ensure the anti-links property is
+ * writeable on all personas before continuing. Ignore errors from it in
+ * the hope that everything works anyway.
+ *
+ * In the worst case, this will double the number of personas, since if
+ * none of the personas have anti-links writeable, each will have to be
+ * linked with a new writeable persona. */
+ var individual_personas = new HashSet<Persona> (); /* as we modify it */
+ individual_personas.add_all (individual.personas);
- foreach (var persona in personas)
+ debug (" Inserting anti-links:");
+ foreach (var pers in individual_personas)
{
- /* Since persona.store != null, we know that
- * this._primary_store != null. */
- if (persona.store == this._primary_store)
+ try
+ {
+ var personas = new HashSet<Persona> ();
+ personas.add (pers);
+ message ("Anti-linking persona '%s' (%p)", pers.uid, pers);
+
+ var writeable_persona =
+ yield this._ensure_personas_property_writeable (personas,
+ "anti-links");
+ message ("Writeable persona '%s' (%p)", writeable_persona.uid, writeable_persona);
+
+ /* Make sure not to anti-link the new persona to pers. */
+ var anti_link_personas = new HashSet<Persona> ();
+ anti_link_personas.add_all (individual_personas);
+ anti_link_personas.remove (pers);
+
+ var al = writeable_persona as AntiLinkable;
+ assert (al != null);
+ yield ((!) al).add_anti_links (anti_link_personas);
+ message ("");
+ }
+ catch (IndividualAggregatorError e1)
{
- debug (" %s (is user: %s, IID: %s)", persona.uid,
- persona.is_user ? "yes" : "no", persona.iid);
- yield ((!) this._primary_store).remove_persona (persona);
+ debug (" Failed to ensure anti-links property is writeable " +
+ "(continuing anyway): %s", e1.message);
}
}
}
diff --git a/folks/individual.vala b/folks/individual.vala
index 8a612a7..06bcc32 100644
--- a/folks/individual.vala
+++ b/folks/individual.vala
@@ -2213,4 +2213,64 @@ public class Folks.Individual : Object,
{
this._set_personas (null, replacement_individual);
}
+
+ /**
+ * Anti-linked with a persona?
+ *
+ * Check whether this individual is anti-linked to { link Persona} `p` at all.
+ * If so, `true` will be returned â `false` will be returned otherwise.
+ *
+ * Note that this will check for anti-links in either direction, since
+ * anti-links are not necessarily symmetric.
+ *
+ * @param p persona to check for anti-links with
+ * @return `true` if this individual is anti-linked with persona `p`; `false`
+ * otherwise
+ * @since UNRELEASED
+ */
+ public bool has_anti_link_with_persona (Persona p)
+ {
+ var al = p as AntiLinkable;
+
+ foreach (var persona in this._persona_set)
+ {
+ var pl = persona as AntiLinkable;
+
+ if ((al != null && ((!) al).has_anti_link_with_persona (persona)) ||
+ (pl != null && ((!) pl).has_anti_link_with_persona (p)))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Anti-linked with an individual?
+ *
+ * Check whether this individual is anti-linked to any of the { link Persona}s
+ * in { link Individual} `i`. If so, `true` will be returned â `false` will be
+ * returned otherwise.
+ *
+ * Note that this will check for anti-links in either direction, since
+ * anti-links are not necessarily symmetric.
+ *
+ * @param i individual to check for anti-links with
+ * @return `true` if this individual is anti-linked with individual `i`;
+ * `false` otherwise
+ * @since UNRELEASED
+ */
+ public bool has_anti_link_with_individual (Individual i)
+ {
+ foreach (var p in i.personas)
+ {
+ if (this.has_anti_link_with_persona (p) == true)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/folks/persona-store.vala b/folks/persona-store.vala
index 7019cde..6cea21e 100644
--- a/folks/persona-store.vala
+++ b/folks/persona-store.vala
@@ -294,7 +294,14 @@ public enum Folks.PersonaDetail
*
* @since 0.7.1
*/
- LAST_CALL_INTERACTION_DATETIME
+ LAST_CALL_INTERACTION_DATETIME,
+
+ /**
+ * Field for { link AntiLinkable.anti_links}.
+ *
+ * @since UNRELEASED
+ */
+ ANTI_LINKS,
}
/**
@@ -353,7 +360,8 @@ public abstract class Folks.PersonaStore : Object
"im-interaction-count",
"last-im-interaction-datetime",
"call-interaction-count",
- "last-call-interaction-datetime"
+ "last-call-interaction-datetime",
+ "anti-links"
};
/**
diff --git a/folks/potential-match.vala b/folks/potential-match.vala
index 07a2504..ca998cd 100644
--- a/folks/potential-match.vala
+++ b/folks/potential-match.vala
@@ -34,7 +34,8 @@ public enum Folks.MatchResult
*
* This is used in situations where two individuals should never be linked,
* such as when one of them has a { link Individual.trust_level} of
- * { link TrustLevel.NONE}.
+ * { link TrustLevel.NONE}, or when the individuals are explicitly
+ * anti-linked.
*
* @since 0.6.8
*/
@@ -128,6 +129,13 @@ public class Folks.PotentialMatch : Object
return result;
}
+ /* Similarly, immediately discount a match if the individuals have been
+ * anti-linked by the user. */
+ if (a.has_anti_link_with_individual (b))
+ {
+ return result;
+ }
+
result = MatchResult.VERY_LOW;
/* If individuals share gender. */
diff --git a/tools/inspect/utils.vala b/tools/inspect/utils.vala
index cf5f98c..3774e63 100644
--- a/tools/inspect/utils.vala
+++ b/tools/inspect/utils.vala
@@ -252,7 +252,8 @@ private class Folks.Inspect.Utils
}
else if (prop_name == "groups" ||
prop_name == "local-ids" ||
- prop_name == "supported-fields")
+ prop_name == "supported-fields" ||
+ prop_name == "anti-links")
{
Set<string> groups = (Set<string>) prop_value.get_object ();
output_string = "{ ";
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]