/**
* A physical person, aggregated from the various {@link Persona}s the person
- * might have, such as their different IM addresses or vCard entries.
+ * might have, such as their different IM addresses or vCard entries. An
+ * individual must always contain at least one {@link Persona}.
*
* When choosing the values of single-valued properties (such as
* {@link Individual.alias} and {@link Individual.avatar}; but not multi-valued
{
/* Stores the Personas contained in this Individual. */
private HashSet<Persona> _persona_set =
- new HashSet<Persona> (direct_hash, direct_equal);
+ new HashSet<Persona> ();
/* Read-only view of the above set */
private Set<Persona> _persona_set_ro;
/* Mapping from PersonaStore -> number of Personas from that store contained
*/
public async void change_avatar (LoadableIcon? avatar) throws PropertyError
{
+ /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
+ * this should be rewritten to use async delegates passed to a generic
+ * _change_single_valued_property() method. */
if ((this._avatar != null && ((!) this._avatar).equal (avatar)) ||
(this._avatar == null && avatar == null))
{
debug ("Setting avatar of individual '%s' to '%p'…", this.id, avatar);
PropertyError? persona_error = null;
- var avatar_changed = false;
+ var prop_changed = false;
/* Try to write it to only the writeable Personas which have the
* "avatar" property as writeable. */
{
yield a.change_avatar (avatar);
debug (" written to writeable persona '%s'", p.uid);
- avatar_changed = true;
+ prop_changed = true;
}
catch (PropertyError e)
{
}
}
- /* Failure? */
- if (avatar_changed == false)
+ /* Failure? Changing the property failed on every suitable persona found
+ * (and potentially zero suitable personas were found). */
+ if (prop_changed == false)
{
- assert (persona_error != null);
+ if (persona_error == null)
+ {
+ persona_error = new PropertyError.NOT_WRITEABLE (
+ _("Failed to change property ‘%s’: No suitable personas were found."),
+ "avatar");
+ }
+
throw persona_error;
}
}
*/
public async void change_alias (string alias) throws PropertyError
{
+ /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
+ * this should be rewritten to use async delegates passed to a generic
+ * _change_single_valued_property() method. */
if (this._alias == alias)
{
return;
debug ("Setting alias of individual '%s' to '%s'…", this.id, alias);
PropertyError? persona_error = null;
- var alias_changed = false;
+ var prop_changed = false;
/* Try to write it to only the writeable Personas which have "alias"
* as a writeable property. */
{
yield a.change_alias (alias);
debug (" written to writeable persona '%s'", p.uid);
- alias_changed = true;
+ prop_changed = true;
}
catch (PropertyError e)
{
}
}
- /* Failure? */
- if (alias_changed == false)
+ /* Failure? Changing the property failed on every suitable persona found
+ * (and potentially zero suitable personas were found). */
+ if (prop_changed == false)
{
- assert (persona_error != null);
+ if (persona_error == null)
+ {
+ persona_error = new PropertyError.NOT_WRITEABLE (
+ _("Failed to change property ‘%s’: No suitable personas were found."),
+ "alias");
+ }
+
throw persona_error;
}
-
- /* Update our copy of the alias. */
- this._alias = alias;
- this.notify_property ("alias");
}
private StructuredName? _structured_name = null;
*/
public async void change_nickname (string nickname) throws PropertyError
{
+ /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
+ * this should be rewritten to use async delegates passed to a generic
+ * _change_single_valued_property() method. */
+
// Normalise null values to the empty string
if (nickname == null)
{
debug ("Setting nickname of individual '%s' to '%s'…", this.id, nickname);
PropertyError? persona_error = null;
- var nickname_changed = false;
+ var prop_changed = false;
/* Try to write it to only the writeable Personas which have "nickname"
* as a writeable property. */
{
yield n.change_nickname (nickname);
debug (" written to writeable persona '%s'", p.uid);
- nickname_changed = true;
+ prop_changed = true;
}
catch (PropertyError e)
{
}
}
- /* Failure? */
- if (nickname_changed == false)
+ /* Failure? Changing the property failed on every suitable persona found
+ * (and potentially zero suitable personas were found). */
+ if (prop_changed == false)
{
- assert (persona_error != null);
+ if (persona_error == null)
+ {
+ persona_error = new PropertyError.NOT_WRITEABLE (
+ _("Failed to change property ‘%s’: No suitable personas were found."),
+ "nickname");
+ }
+
throw persona_error;
}
-
- /* Update our copy of the nickname. */
- this._nickname = nickname;
- this.notify_property ("nickname");
}
private Gender _gender = Gender.UNSPECIFIED;
{
get
{
- this._update_urls (true);
+ this._update_urls (true, false, false);
return this._urls_ro;
}
set { this.change_urls.begin (value); } /* not writeable */
{
get
{
- this._update_phone_numbers (true);
+ this._update_phone_numbers (true, false, false);
return this._phone_numbers_ro;
}
set { this.change_phone_numbers.begin (value); } /* not writeable */
{
get
{
- this._update_email_addresses (true);
+ this._update_email_addresses (true, false, false);
return this._email_addresses_ro;
}
set { this.change_email_addresses.begin (value); } /* not writeable */
{
get
{
- this._update_roles (true);
+ this._update_roles (true, false, false);
return this._roles_ro;
}
set { this.change_roles.begin (value); } /* not writeable */
{
get
{
- this._update_local_ids (true);
+ this._update_local_ids (true, false, false);
return this._local_ids_ro;
}
set { this.change_local_ids.begin (value); } /* not writeable */
{
get
{
- this._update_notes (true);
+ this._update_notes (true, false, false);
return this._notes_ro;
}
set { this.change_notes.begin (value); } /* not writeable */
{
get
{
- this._update_postal_addresses (true);
+ this._update_postal_addresses (true, false, false);
return this._postal_addresses_ro;
}
set { this.change_postal_addresses.begin (value); } /* not writeable */
*/
public async void change_is_favourite (bool is_favourite) throws PropertyError
{
+ /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
+ * this should be rewritten to use async delegates passed to a generic
+ * _change_single_valued_property() method. */
if (this._is_favourite == is_favourite)
{
return;
is_favourite ? "TRUE" : "FALSE");
PropertyError? persona_error = null;
- var is_favourite_changed = false;
+ var prop_changed = false;
/* Try to write it to only the Personas which have "is-favourite" as a
* writeable property.
{
yield a.change_is_favourite (is_favourite);
debug (" written to persona '%s'", p.uid);
- is_favourite_changed = true;
+ prop_changed = true;
}
catch (PropertyError e)
{
}
}
- /* Failure? */
- if (is_favourite_changed == false)
+ /* Failure? Changing the property failed on every suitable persona found
+ * (and potentially zero suitable personas were found). */
+ if (prop_changed == false)
{
- assert (persona_error != null);
+ if (persona_error == null)
+ {
+ persona_error = new PropertyError.NOT_WRITEABLE (
+ _("Failed to change property ‘%s’: No suitable personas were found."),
+ "is-favourite");
+ }
+
throw persona_error;
}
-
- /* Update our copy of the property. */
- this._is_favourite = is_favourite;
- this.notify_property ("is-favourite");
}
private HashSet<string>? _groups = null;
{
get
{
- this._update_groups (true);
+ this._update_groups (true, false, false);
return this._groups_ro;
}
set { this.change_groups.begin (value); }
*/
public async void change_groups (Set<string> groups) throws PropertyError
{
+ /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
+ * this should be rewritten to use async delegates passed to a generic
+ * _change_single_valued_property() method. */
debug ("Setting '%s' groups…", this.id);
PropertyError? persona_error = null;
- var groups_changed = false;
+ var prop_changed = false;
/* Try to write it to only the Personas which have "groups" as a
* writeable property. */
{
yield g.change_groups (groups);
debug (" written to persona '%s'", p.uid);
- groups_changed = true;
+ prop_changed = true;
}
catch (PropertyError e)
{
}
}
- /* Failure? */
- if (groups_changed == false)
+ /* Failure? Changing the property failed on every suitable persona found
+ * (and potentially zero suitable personas were found). */
+ if (prop_changed == false)
{
- assert (persona_error != null);
+ if (persona_error == null)
+ {
+ persona_error = new PropertyError.NOT_WRITEABLE (
+ _("Failed to change property ‘%s’: No suitable personas were found."),
+ "groups");
+ }
+
throw persona_error;
}
}
{
get
{
- this._update_im_addresses (true);
+ this._update_im_addresses (true, false, false);
return this._im_addresses;
}
set { this.change_im_addresses.begin (value); } /* not writeable */
{
get
{
- this._update_web_service_addresses (true);
+ this._update_web_service_addresses (true, false, false);
return this._web_service_addresses;
}
/* Not writeable: */
public DateTime? last_im_interaction_datetime
{
- get
+ get
{
if (this._last_im_interaction_datetime == null)
{
*/
public uint call_interaction_count
{
- get
+ get
{
uint counter = 0;
/* Iterate over all personas and sum up their call interaction counts*/
/**
* The set of {@link Persona}s encapsulated by this Individual.
*
+ * There must always be at least one Persona in this set.
+ *
* No order is specified over the set of personas, as such an order may be
* different across each of the properties implemented by the personas (e.g.
* should they be ordered by presence, name, star sign, etc.?).
public Individual (Set<Persona>? personas)
{
Object (personas: personas);
- }
- construct
- {
debug ("Creating new Individual with %u Personas: %p",
this._persona_set.size, this);
+ }
+ construct
+ {
this._persona_set_ro = this._persona_set.read_only_view;
}
setter (candidate_p);
}
- private void _update_groups (bool create_if_not_exist)
+ /* Delegate to add the values of a property from all personas to the
+ * collection of values for that property in this individual.
+ *
+ * Used in _update_multi_valued_property(), below. */
+ private delegate bool MultiValuedPropertySetter ();
+
+ /* Delegate to get whether a multi-valued property in this Individual has not
+ * been initialised yet (and is thus still null).
+ *
+ * Used in _update_multi_valued_property(), below. */
+ private delegate bool PropertyIsNull ();
+
+ /* Delegate to create a new empty collection for a multi-valued property in
+ * this Individual and assign it to the property.
+ *
+ * Used in _update_multi_valued_property(), below. */
+ private delegate void CollectionCreator ();
+
+ /*
+ * Update a multi-valued property from the values in the personas.
+ *
+ * Multi-valued properties are ones such as {@link Individual.notes} or
+ * {@link Individual.email_addresses} which have multiple values taken as the
+ * union of the values listed by the personas for those properties.
+ *
+ * This function handles lazy instantiation of the multi-valued property. If
+ * ``create_if_not_exist`` is ``true``, the property is guaranteed to be
+ * created (by ``create_collection``) and set to a non-``null`` value before
+ * this function returns.
+ *
+ * If ``create_if_not_exist`` is ``false``, however, the property may not be
+ * instantiated if it hasn't already been accessed through its property
+ * getter. In this case, a change notification will be emitted for the
+ * property and this function will return immediately.
+ *
+ * If ``force_update`` is ``true``, then existing values get updated (if
+ * the current value is different) or created (according to the
+ * ``create_if_not_exist`` value). Otherwise the function only ensures
+ * that there is a value (if ``create_if_not_exist`` is set) and leaves
+ * existing values unchanged.
+ *
+ * If the property value is to be instantiated, or already has been
+ * instantiated, its value is updated by ``setter`` from the values of the
+ * property in the individual's personas.
+ *
+ * @param prop_name name of the property being set, as used in
+ * {@link Persona.writeable_properties}
+ * @param create_if_not_exist ``true`` to ensure the property is non-null;
+ * ``false`` otherwise
+ * @param prop_is_null function returning ``true`` iff the property is
+ * currently ``null``
+ * @param create_collection function creating a new collection/container for
+ * the property values and assigning it to the property (and updating the
+ * property's read-only view as necessary)
+ * @param setter function which adds the values from the individual's
+ * personas' values for the property to the individual's value for the
+ * property; it returns ``true`` if the property value has changed
+ * @since 0.7.4
+ */
+ private void _update_multi_valued_property (string prop_name,
+ bool create_if_not_exist, PropertyIsNull prop_is_null,
+ CollectionCreator create_collection, MultiValuedPropertySetter setter,
+ bool emit_notification = true,
+ bool force_update = true)
+ {
+ /* If the set of values doesn't exist, and we're not meant to lazily
+ * create it, then simply emit a notification (since the set might've
+ * changed — we can't be sure, but emitting is a safe over-estimate) and
+ * return. */
+ bool created = false;
+ if (prop_is_null ())
+ {
+ /* Notify and return. */
+ if (create_if_not_exist == false)
+ {
+ if (emit_notification)
+ {
+ this.notify_property (prop_name);
+ }
+ return;
+ }
+
+ /* Lazily instantiate the set of IM addresses. */
+ create_collection ();
+ created = true;
+ }
+
+ /* Re-populate the collection as the union of the values in the
+ * individual's personas. Do this when an empty property was just
+ * created or we were asked to explicitly (usually because the caller
+ * knows that the current value is out-dated).
+ */
+ if ((created || force_update) && setter () == true && emit_notification)
+ {
+ this.notify_property (prop_name);
+ }
+ }
+
+ private void _update_groups (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
{
/* If the set of groups doesn't exist, and we're not meant to lazily
* create it, then simply emit a notification (since the set might've
* changed — we can't be sure, but emitting is a safe over-estimate) and
* return. */
+ bool created = false;
if (this._groups == null && create_if_not_exist == false)
{
- this.notify_property ("groups");
+ if (emit_notification)
+ {
+ this.notify_property ("groups");
+ }
return;
}
{
this._groups = new HashSet<string> ();
this._groups_ro = this._groups.read_only_view;
+ created = true;
}
+ /* Don't touch existing content in get(). */
+ if (!created && !force_update)
+ return;
+
var new_groups = new HashSet<string> ();
/* FIXME: this should partition the personas by store (maybe we should
foreach (var group in new_groups)
{
- if (this._groups.add (group))
+ if (this._groups.add (group) && emit_notification)
{
this.group_changed (group, true);
}
{
unowned string group = (string) l;
this._groups.remove (group);
- this.group_changed (group, false);
+ if (emit_notification)
+ {
+ this.group_changed (group, false);
+ }
});
}
this.trust_level = trust_level;
}
- private void _update_im_addresses (bool create_if_not_exist)
+ private void _update_im_addresses (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
{
- /* If the set of IM addresses doesn't exist, and we're not meant to lazily
- * create it, then simply emit a notification (since the set might've
- * changed — we can't be sure, but emitting is a safe over-estimate) and
- * return. */
- if (this._im_addresses == null && create_if_not_exist == false)
- {
- this.notify_property ("im-addresses");
- return;
- }
-
- /* Lazily instantiate the set of IM addresses. */
- else if (this._im_addresses == null)
- {
- this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
- null, null, ImFieldDetails.hash,
- (EqualFunc) ImFieldDetails.equal);
- }
-
- /* populate the IM addresses as the union of our Personas' addresses */
- this._im_addresses.clear ();
-
- foreach (var persona in this._persona_set)
- {
- if (persona is ImDetails)
+ this._update_multi_valued_property ("im-addresses",
+ create_if_not_exist, () => { return this._im_addresses == null; },
+ () =>
{
- var im_details = (ImDetails) persona;
- foreach (var cur_protocol in im_details.im_addresses.get_keys ())
- {
- var cur_addresses =
- im_details.im_addresses.get (cur_protocol);
+ this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
+ null, null,
+ (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
+ (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
+ },
+ () =>
+ {
+ var new_im_addresses = new HashMultiMap<string, ImFieldDetails> (
+ null, null,
+ (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
+ (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
- foreach (var address in cur_addresses)
+ foreach (var persona in this._persona_set)
+ {
+ /* We only care about personas implementing the given interface. */
+ var im_details = persona as ImDetails;
+ if (im_details != null)
{
- this._im_addresses.set (cur_protocol, address);
+ foreach (var cur_protocol in
+ im_details.im_addresses.get_keys ())
+ {
+ var cur_addresses =
+ im_details.im_addresses.get (cur_protocol);
+
+ foreach (var address in cur_addresses)
+ {
+ new_im_addresses.set (cur_protocol, address);
+ }
+ }
}
}
- }
- }
- this.notify_property ("im-addresses");
- }
- private void _update_web_service_addresses (bool create_if_not_exist)
- {
- /* If the set of web service addresses doesn't exist, and we're not meant
- * to lazily create it, then simply emit a notification (since the set
- * might've changed — we can't be sure, but emitting is a safe
- * over-estimate) and return. */
- if (this._web_service_addresses == null && create_if_not_exist == false)
- {
- this.notify_property ("web-service-addresses");
- return;
- }
-
- /* Lazily instantiate the set of web service addresses. */
- else if (this._web_service_addresses == null)
- {
- this._web_service_addresses =
- new HashMultiMap<string, WebServiceFieldDetails> (null, null,
- (GLib.HashFunc) WebServiceFieldDetails.hash,
- (GLib.EqualFunc) WebServiceFieldDetails.equal);;
- }
+ if (!Utils.multi_map_str_afd_equal (new_im_addresses,
+ this._im_addresses))
+ {
+ this._im_addresses = new_im_addresses;
+ return true;
+ }
- /* populate the web service addresses as the union of our Personas' addresses */
- this._web_service_addresses.clear ();
+ return false;
+ }, emit_notification, force_update);
+ }
- foreach (var persona in this.personas)
- {
- if (persona is WebServiceDetails)
+ private void _update_web_service_addresses (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
+ {
+ this._update_multi_valued_property ("web-service-addresses",
+ create_if_not_exist,
+ () => { return this._web_service_addresses == null; },
+ () =>
{
- var web_service_details = (WebServiceDetails) persona;
- foreach (var cur_web_service in
- web_service_details.web_service_addresses.get_keys ())
+ this._web_service_addresses =
+ new HashMultiMap<string, WebServiceFieldDetails> (null, null,
+ (Gee.HashDataFunc)
+ AbstractFieldDetails<string>.hash_static,
+ (Gee.EqualDataFunc)
+ AbstractFieldDetails<string>.equal_static);
+ },
+ () =>
+ {
+ var new_web_service_addresses =
+ new HashMultiMap<string, WebServiceFieldDetails> (null, null,
+ (Gee.HashDataFunc)
+ AbstractFieldDetails<string>.hash_static,
+ (Gee.EqualDataFunc)
+ AbstractFieldDetails<string>.equal_static);
+
+ foreach (var persona in this._persona_set)
{
- var cur_addresses =
- web_service_details.web_service_addresses.get (
- cur_web_service);
+ /* We only care about personas implementing the given interface. */
+ var web_service_details = persona as WebServiceDetails;
+ if (web_service_details != null)
+ {
+ foreach (var cur_web_service in
+ web_service_details.web_service_addresses.get_keys ())
+ {
+ var cur_addresses =
+ web_service_details.web_service_addresses.get (
+ cur_web_service);
+
+ foreach (var ws_fd in cur_addresses)
+ {
+ new_web_service_addresses.set (cur_web_service,
+ ws_fd);
+ }
+ }
+ }
+ }
- foreach (var ws_fd in cur_addresses)
- this._web_service_addresses.set (cur_web_service, ws_fd);
+ if (!Utils.multi_map_str_afd_equal (new_web_service_addresses,
+ this._web_service_addresses))
+ {
+ this._web_service_addresses = new_web_service_addresses;
+ return true;
}
- }
- }
- this.notify_property ("web-service-addresses");
+
+ return false;
+ }, emit_notification, force_update);
}
private void _connect_to_persona (Persona persona)
}
/* Subscribe to the interactions signal for the persona */
var p_interaction_details = persona as InteractionDetails;
- if (p_interaction_details != null)
+ if (p_interaction_details != null)
{
persona.notify["im-interaction-count"].connect (this._notify_im_interaction_count_cb);
persona.notify["call-interaction-count"].connect (this._notify_call_interaction_count_cb);
/* Unsubscribe from the interactions signal for the persona */
var p_interaction_details = persona as InteractionDetails;
- if (p_interaction_details != null)
+ if (p_interaction_details != null)
{
persona.notify["im-interaction-count"].disconnect (this._notify_im_interaction_count_cb);
persona.notify["call-interaction-count"].disconnect (this._notify_call_interaction_count_cb);
});
}
- private void _update_urls (bool create_if_not_exist)
+ private void _update_urls (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
{
- /* If the set of URIs doesn't exist, and we're not meant to lazily create
- * it, then simply emit a notification (since the set might've changed —
- * we can't be sure, but emitting is a safe over-estimate) and return. */
- if (this._urls == null && create_if_not_exist == false)
- {
- this.notify_property ("urls");
- return;
- }
-
- /* Lazily instantiate the set of URIs. */
- else if (this._urls == null)
- {
- this._urls = new HashSet<UrlFieldDetails> (
- (GLib.HashFunc) UrlFieldDetails.hash,
- (GLib.EqualFunc) UrlFieldDetails.equal);
- this._urls_ro = this._urls.read_only_view;
- }
-
- /* Populate the URLs as the union of our Personas' URLs.
- * If the same URL exists multiple times we merge the parameters. */
- var urls_set = new HashMap<string, UrlFieldDetails> (
- null, null, (GLib.EqualFunc) UrlFieldDetails.equal);
-
- this._urls.clear ();
-
- foreach (var persona in this._persona_set)
- {
- var url_details = persona as UrlDetails;
- if (url_details != null)
+ this._update_multi_valued_property ("urls", create_if_not_exist,
+ () => { return this._urls == null; },
+ () =>
{
- foreach (var url_fd in ((!) url_details).urls)
+ this._urls = new HashSet<UrlFieldDetails> (
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+ this._urls_ro = this._urls.read_only_view;
+ },
+ () =>
+ {
+ var new_urls = new HashSet<UrlFieldDetails> (
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+ var urls_set = new HashMap<unowned string,
+ unowned UrlFieldDetails> (
+ null, null, AbstractFieldDetails<string>.equal_static);
+
+ foreach (var persona in this._persona_set)
{
- var existing = urls_set.get (url_fd.value);
- if (existing != null)
- existing.extend_parameters (url_fd.parameters);
- else
+ /* We only care about personas implementing the given
+ * interface. If the same URL exists multiple times we merge
+ * the parameters. */
+ var url_details = persona as UrlDetails;
+ if (url_details != null)
{
- var new_url_fd = new UrlFieldDetails (url_fd.value);
- new_url_fd.extend_parameters (url_fd.parameters);
- urls_set.set (new_url_fd.value, new_url_fd);
- this._urls.add (new_url_fd);
+ foreach (var url_fd in ((!) url_details).urls)
+ {
+ var existing = urls_set.get (url_fd.value);
+ if (existing != null)
+ {
+ existing.extend_parameters (url_fd.parameters);
+ }
+ else
+ {
+ var new_url_fd =
+ new UrlFieldDetails (url_fd.value);
+ new_url_fd.extend_parameters (url_fd.parameters);
+ urls_set.set (new_url_fd.value, new_url_fd);
+ new_urls.add (new_url_fd);
+ }
+ }
}
}
- }
- }
- this.notify_property ("urls");
+ if (!Utils.set_afd_equal (new_urls, this._urls))
+ {
+ this._urls = new_urls;
+ this._urls_ro = new_urls.read_only_view;
+ return true;
+ }
+
+ return false;
+ }, emit_notification, force_update);
}
- private void _update_phone_numbers (bool create_if_not_exist)
+ private void _update_phone_numbers (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
{
- /* If the set of phone numbers doesn't exist, and we're not meant to
- * lazily create it, then simply emit a notification (since the set
- * might've changed — we can't be sure, but emitting is a safe
- * over-estimate) and return. */
- if (this._phone_numbers == null && create_if_not_exist == false)
- {
- this.notify_property ("phone-numbers");
- return;
- }
-
- /* Lazily instantiate the set of phone numbers. */
- else if (this._phone_numbers == null)
- {
- this._phone_numbers = new HashSet<PhoneFieldDetails> (
- (GLib.HashFunc) PhoneFieldDetails.hash,
- (GLib.EqualFunc) PhoneFieldDetails.equal);
- this._phone_numbers_ro = this._phone_numbers.read_only_view;
- }
-
- /* Populate the phone numbers as the union of our Personas' numbers
- * If the same number exists multiple times we merge the parameters. */
- var phone_numbers_set = new HashMap<string, PhoneFieldDetails> (
- null, null, (GLib.EqualFunc) PhoneFieldDetails.equal);
-
- this._phone_numbers.clear ();
-
- foreach (var persona in this._persona_set)
- {
- var phone_details = persona as PhoneDetails;
- if (phone_details != null)
+ this._update_multi_valued_property ("phone-numbers", create_if_not_exist,
+ () => { return this._phone_numbers == null; },
+ () =>
+ {
+ this._phone_numbers = new HashSet<PhoneFieldDetails> (
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+ this._phone_numbers_ro = this._phone_numbers.read_only_view;
+ },
+ () =>
{
- foreach (var phone_fd in ((!) phone_details).phone_numbers)
+ var new_phone_numbers = new HashSet<PhoneFieldDetails> (
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+ var phone_numbers_set = new HashMap<string, PhoneFieldDetails> (
+ null, null, AbstractFieldDetails<string>.equal_static);
+
+ foreach (var persona in this._persona_set)
{
- var existing = phone_numbers_set.get (phone_fd.value);
- if (existing != null)
- existing.extend_parameters (phone_fd.parameters);
- else
+ /* We only care about personas implementing the given
+ * interface. If the same phone number exists multiple times
+ * we merge the parameters. */
+ var phone_details = persona as PhoneDetails;
+ if (phone_details != null)
{
- var new_fd = new PhoneFieldDetails (phone_fd.value);
- new_fd.extend_parameters (phone_fd.parameters);
- phone_numbers_set.set (new_fd.value, new_fd);
- this._phone_numbers.add (new_fd);
+ foreach (var phone_fd in ((!) phone_details).phone_numbers)
+ {
+ var existing = phone_numbers_set.get (phone_fd.value);
+ if (existing != null)
+ {
+ existing.extend_parameters (phone_fd.parameters);
+ }
+ else
+ {
+ var new_fd =
+ new PhoneFieldDetails (phone_fd.value);
+ new_fd.extend_parameters (phone_fd.parameters);
+ phone_numbers_set.set (new_fd.value, new_fd);
+ new_phone_numbers.add (new_fd);
+ }
+ }
}
}
- }
- }
- this.notify_property ("phone-numbers");
+ if (!Utils.set_afd_equal (new_phone_numbers, this._phone_numbers))
+ {
+ this._phone_numbers = new_phone_numbers;
+ this._phone_numbers_ro = new_phone_numbers.read_only_view;
+ return true;
+ }
+
+ return false;
+ }, emit_notification, force_update);
}
- private void _update_email_addresses (bool create_if_not_exist)
+ private void _update_email_addresses (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
{
- /* If the set of e-mail addresses doesn't exist, and we're not meant to
- * lazily create it, then simply emit a notification (since the set
- * might've changed — we can't be sure, but emitting is a safe
- * over-estimate) and return. */
- if (this._email_addresses == null && create_if_not_exist == false)
- {
- this.notify_property ("email-addresses");
- return;
- }
-
- /* Lazily instantiate the set of e-mail addresses. */
- else if (this._email_addresses == null)
- {
- this._email_addresses = new HashSet<EmailFieldDetails> (
- (GLib.HashFunc) EmailFieldDetails.hash,
- (GLib.EqualFunc) EmailFieldDetails.equal);
- this._email_addresses_ro = this._email_addresses.read_only_view;
- }
-
- /* Populate the email addresses as the union of our Personas' addresses.
- * If the same address exists multiple times we merge the parameters. */
- var emails_set = new HashMap<string, EmailFieldDetails> (
- null, null, (GLib.EqualFunc) EmailFieldDetails.equal);
-
- this._email_addresses.clear ();
-
- foreach (var persona in this._persona_set)
- {
- var email_details = persona as EmailDetails;
- if (email_details != null)
+ this._update_multi_valued_property ("email-addresses",
+ create_if_not_exist, () => { return this._email_addresses == null; },
+ () =>
{
- foreach (var email_fd in ((!) email_details).email_addresses)
+ this._email_addresses = new HashSet<EmailFieldDetails> (
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+ this._email_addresses_ro = this._email_addresses.read_only_view;
+ },
+ () =>
+ {
+ var new_email_addresses = new HashSet<EmailFieldDetails> (
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+ var emails_set = new HashMap<string, EmailFieldDetails> (
+ null, null, AbstractFieldDetails<string>.equal_static);
+
+ foreach (var persona in this._persona_set)
{
- var existing = emails_set.get (email_fd.value);
- if (existing != null)
- existing.extend_parameters (email_fd.parameters);
- else
+ /* We only care about personas implementing the given
+ * interface. If the same e-mail address exists multiple times
+ * we merge the parameters. */
+ var email_details = persona as EmailDetails;
+ if (email_details != null)
{
- var new_email_fd = new EmailFieldDetails (email_fd.value,
- email_fd.parameters);
- emails_set.set (new_email_fd.value, new_email_fd);
- this._email_addresses.add (new_email_fd);
+ foreach (var email_fd in ((!) email_details).email_addresses)
+ {
+ var existing = emails_set.get (email_fd.value);
+ if (existing != null)
+ {
+ existing.extend_parameters (email_fd.parameters);
+ }
+ else
+ {
+ var new_email_fd =
+ new EmailFieldDetails (email_fd.value,
+ email_fd.parameters);
+ emails_set.set (new_email_fd.value, new_email_fd);
+ new_email_addresses.add (new_email_fd);
+ }
+ }
}
}
- }
- }
- this.notify_property ("email-addresses");
+ if (!Utils.set_afd_equal (new_email_addresses,
+ this._email_addresses))
+ {
+ this._email_addresses = new_email_addresses;
+ this._email_addresses_ro = new_email_addresses.read_only_view;
+ return true;
+ }
+
+ return false;
+ }, emit_notification, force_update);
}
- private void _update_roles (bool create_if_not_exist)
+ private void _update_roles (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
{
- /* If the set of roles doesn't exist, and we're not meant to
- * lazily create it, then simply emit a notification (since the set
- * might've changed — we can't be sure, but emitting is a safe
- * over-estimate) and return. */
- if (this._roles == null && create_if_not_exist == false)
- {
- this.notify_property ("roles");
- return;
- }
-
- /* Lazily instantiate the set of roles. */
- else if (this._roles == null)
- {
- this._roles = new HashSet<RoleFieldDetails> (
- (GLib.HashFunc) RoleFieldDetails.hash,
- (GLib.EqualFunc) RoleFieldDetails.equal);
- this._roles_ro = this._roles.read_only_view;
- }
+ this._update_multi_valued_property ("roles", create_if_not_exist,
+ () => { return this._roles == null; },
+ () =>
+ {
+ this._roles = new HashSet<RoleFieldDetails> (
+ AbstractFieldDetails<Role>.hash_static,
+ AbstractFieldDetails<Role>.equal_static);
+ this._roles_ro = this._roles.read_only_view;
+ },
+ () =>
+ {
+ var new_roles = new HashSet<RoleFieldDetails> (
+ AbstractFieldDetails<Role>.hash_static,
+ AbstractFieldDetails<Role>.equal_static);
- this._roles.clear ();
+ foreach (var persona in this._persona_set)
+ {
+ var role_details = persona as RoleDetails;
+ if (role_details != null)
+ {
+ foreach (var role_fd in ((!) role_details).roles)
+ {
+ new_roles.add (role_fd);
+ }
+ }
+ }
- foreach (var persona in this._persona_set)
- {
- var role_details = persona as RoleDetails;
- if (role_details != null)
- {
- foreach (var role_fd in ((!) role_details).roles)
+ if (!Utils.set_afd_equal (new_roles, this._roles))
{
- this._roles.add (role_fd);
+ this._roles = new_roles;
+ this._roles_ro = new_roles.read_only_view;
+ return true;
}
- }
- }
- this.notify_property ("roles");
+ return false;
+ }, emit_notification, force_update);
}
- private void _update_local_ids (bool create_if_not_exist)
+ private void _update_local_ids (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
{
- /* If the set of local IDs doesn't exist, and we're not meant to
- * lazily create it, then simply emit a notification (since the set
- * might've changed — we can't be sure, but emitting is a safe
- * over-estimate) and return. */
- if (this._local_ids == null && create_if_not_exist == false)
- {
- this.notify_property ("local-ids");
- return;
- }
-
- /* Lazily instantiate the set of local IDs. */
- else if (this._local_ids == null)
- {
- this._local_ids = new HashSet<string> ();
- this._local_ids_ro = this._local_ids.read_only_view;
- }
+ this._update_multi_valued_property ("local-ids", create_if_not_exist,
+ () => { return this._local_ids == null; },
+ () =>
+ {
+ this._local_ids = new HashSet<string> ();
+ this._local_ids_ro = this._local_ids.read_only_view;
+ },
+ () =>
+ {
+ var new_local_ids = new HashSet<string> ();
- this._local_ids.clear ();
+ foreach (var persona in this._persona_set)
+ {
+ var local_id_details = persona as LocalIdDetails;
+ if (local_id_details != null)
+ {
+ foreach (var id in ((!) local_id_details).local_ids)
+ {
+ new_local_ids.add (id);
+ }
+ }
+ }
- foreach (var persona in this._persona_set)
- {
- var local_ids_details = persona as LocalIdDetails;
- if (local_ids_details != null)
- {
- foreach (var id in ((!) local_ids_details).local_ids)
+ if (new_local_ids.size != this._local_ids.size ||
+ !new_local_ids.contains_all (this._local_ids))
{
- this._local_ids.add (id);
+ this._local_ids = new_local_ids;
+ this._local_ids_ro = new_local_ids.read_only_view;
+ return true;
}
- }
- }
- this.notify_property ("local-ids");
+ return false;
+ }, emit_notification, force_update);
}
- private void _update_postal_addresses (bool create_if_not_exist)
+ private void _update_postal_addresses (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
{
- /* If the set of addresses doesn't exist, and we're not meant to lazily
- * create it, then simply emit a notification (since the set might've
- * changed — we can't be sure, but emitting is a safe over-estimate) and
- * return. */
- if (this._postal_addresses == null && create_if_not_exist == false)
- {
- this.notify_property ("postal-addresses");
- return;
- }
-
- /* Lazily instantiate the set of addresses. */
- else if (this._postal_addresses == null)
- {
- 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._postal_addresses.clear ();
-
/* FIXME: Detect duplicates somehow? */
- foreach (var persona in this._persona_set)
- {
- var address_details = persona as PostalAddressDetails;
- if (address_details != null)
+ this._update_multi_valued_property ("postal-addresses",
+ create_if_not_exist, () => { return this._postal_addresses == null; },
+ () =>
{
- foreach (var pafd in ((!) address_details).postal_addresses)
- this._postal_addresses.add (pafd);
- }
- }
+ this._postal_addresses = new HashSet<PostalAddressFieldDetails> (
+ AbstractFieldDetails<PostalAddress>.hash_static,
+ AbstractFieldDetails<PostalAddress>.equal_static);
+ this._postal_addresses_ro = this._postal_addresses.read_only_view;
+ },
+ () =>
+ {
+ var new_postal_addresses =
+ new HashSet<PostalAddressFieldDetails> (
+ AbstractFieldDetails<PostalAddress>.hash_static,
+ AbstractFieldDetails<PostalAddress>.equal_static);
- this.notify_property ("postal-addresses");
+ foreach (var persona in this._persona_set)
+ {
+ var postal_address_details = persona as PostalAddressDetails;
+ if (postal_address_details != null)
+ {
+ foreach (var pafd in
+ ((!) postal_address_details).postal_addresses)
+ {
+ new_postal_addresses.add (pafd);
+ }
+ }
+ }
+
+ if (!Utils.set_afd_equal (new_postal_addresses,
+ this._postal_addresses))
+ {
+ this._postal_addresses = new_postal_addresses;
+ this._postal_addresses_ro =
+ new_postal_addresses.read_only_view;
+ return true;
+ }
+
+ return false;
+ }, emit_notification, force_update);
}
private void _update_birthday ()
});
}
- private void _update_notes (bool create_if_not_exist)
+ private void _update_notes (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
{
- /* If the set of notes doesn't exist, and we're not meant to lazily
- * create it, then simply emit a notification (since the set might've
- * changed — we can't be sure, but emitting is a safe over-estimate) and
- * return. */
- if (this._notes == null && create_if_not_exist == false)
- {
- this.notify_property ("notes");
- return;
- }
-
- /* Lazily instantiate the set of notes. */
- else if (this._notes == null)
- {
- this._notes = new HashSet<NoteFieldDetails> (
- (GLib.HashFunc) NoteFieldDetails.hash,
- (GLib.EqualFunc) NoteFieldDetails.equal);
- this._notes_ro = this._notes.read_only_view;
- }
+ this._update_multi_valued_property ("notes", create_if_not_exist,
+ () => { return this._notes == null; },
+ () =>
+ {
+ this._notes = new HashSet<NoteFieldDetails> (
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+ this._notes_ro = this._notes.read_only_view;
+ },
+ () =>
+ {
+ var new_notes = new HashSet<NoteFieldDetails> (
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
- this._notes.clear ();
+ foreach (var persona in this._persona_set)
+ {
+ var note_details = persona as NoteDetails;
+ if (note_details != null)
+ {
+ foreach (var n in ((!) note_details).notes)
+ {
+ new_notes.add (n);
+ }
+ }
+ }
- foreach (var persona in this._persona_set)
- {
- var note_details = persona as NoteDetails;
- if (note_details != null)
- {
- foreach (var n in ((!) note_details).notes)
+ if (!Utils.set_afd_equal (new_notes, this._notes))
{
- this._notes.add (n);
+ this._notes = new_notes;
+ this._notes_ro = new_notes.read_only_view;
+ return true;
}
- }
- }
- this.notify_property ("notes");
+ return false;
+ }, emit_notification, force_update);
}
private void _set_personas (Set<Persona>? personas,