[folks] bluez: Support updating contacts instead of replacing them when re-querying
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [folks] bluez: Support updating contacts instead of replacing them when re-querying
- Date: Sat, 4 Jan 2014 15:29:13 +0000 (UTC)
commit ab765424681093e22b34ef44bda4b27b42177a01
Author: Philip Withnall <philip withnall collabora co uk>
Date: Mon Nov 11 08:19:29 2013 +0000
bluez: Support updating contacts instead of replacing them when re-querying
When downloading the contact list over PBAP for anything other than the
first time, support updating the properties of existing personas, rather
than replacing them all wholesale. This is a step on the way towards
supporting periodic refreshes of the contact list.
https://bugzilla.gnome.org/show_bug.cgi?id=711827
backends/bluez/bluez-persona-store.vala | 79 +++++++++++++++--
backends/bluez/bluez-persona.vala | 151 ++++++++++++++++++++++++-------
2 files changed, 191 insertions(+), 39 deletions(-)
---
diff --git a/backends/bluez/bluez-persona-store.vala b/backends/bluez/bluez-persona-store.vala
index a95b875..b57cb6b 100644
--- a/backends/bluez/bluez-persona-store.vala
+++ b/backends/bluez/bluez-persona-store.vala
@@ -223,8 +223,13 @@ public class Folks.Backends.BlueZ.PersonaStore : Folks.PersonaStore
* the persona store accordingly. Contacts are stored in the file as a
* sequence of vCards, separated by blank lines.
*
+ * If a contact already exists in the store, its properties will be updated
+ * from the vCard; otherwise it will be added as a new contact to the store.
+ * Contacts which are in the store and not in the vCard will be removed from
+ * the store.
+ *
* If this throws an error, it guarantees to leave the store’s internal state
- * unchanged.
+ * unchanged, but may change the state of { link Persona}s in the store.
*
* @param file the file where the contacts are stored
* @throws IOError if there was an error communicating with D-Bus
@@ -235,6 +240,11 @@ public class Folks.Backends.BlueZ.PersonaStore : Folks.PersonaStore
private async void _update_contacts_from_file (File file) throws IOError
{
var added_personas = new HashSet<Persona> ();
+ var removed_personas = new HashSet<Persona> ();
+
+ /* Start with all personas being marked as removed, and then eliminate the
+ * ones which are found in the vCard. */
+ removed_personas.add_all (this._personas.values);
try
{
@@ -243,7 +253,7 @@ public class Folks.Backends.BlueZ.PersonaStore : Folks.PersonaStore
string? line = null;
StringBuilder vcard = new StringBuilder ();
- /* For each vCard in the file create a new Persona */
+ /* For each vCard in the file create or update a Persona. */
while ((line = yield dis.read_line_async ()) != null)
{
/* Ignore blank lines between vCards. */
@@ -254,11 +264,60 @@ public class Folks.Backends.BlueZ.PersonaStore : Folks.PersonaStore
vcard.append_c ('\n');
if (line.strip () == "END:VCARD")
{
+ var card = new E.VCard.from_string (vcard.str);
+
/* The first vCard is always the user themselves. */
var is_user = (i == 0);
- var persona = new Persona (vcard.str, this, is_user);
- added_personas.add (persona);
+ /* Construct the card’s IID. */
+ var iid_is_checksum = false;
+ string iid;
+
+ /* This prefers the ‘UID’ attribute from the vCard, if it’s
+ * available. However, it is not a required attribute, so many
+ * phones do not implement it; in those cases, fall back to a
+ * checksum of the vCard data itself. This means that whenever
+ * a contact’s properties change in the vCard its IID will
+ * change and hence the persona will be removed and re-added,
+ * but without stable UIDs this is unavoidable. */
+ var attribute = card.get_attribute ("UID");
+ if (attribute != null)
+ {
+ /* Try the UID attribute. */
+ iid = attribute.get_value_decoded ().str;
+ }
+ else
+ {
+ /* Fallback. */
+ iid =
+ Checksum.compute_for_string (ChecksumType.SHA1,
+ vcard.str);
+ iid_is_checksum = true;
+ }
+
+ /* Create or update the persona. */
+ var persona = this._personas.get (iid);
+ if (persona == null)
+ {
+ persona =
+ new Persona (vcard.str, card, this, is_user, iid);
+ }
+ else
+ {
+ /* If the IID is a checksum and we found the persona in
+ * the store, that means their properties havent’t
+ * changed, so as an optimisation, don’t bother updating
+ * the Persona from the vCard in that case. */
+ if (iid_is_checksum == false)
+ {
+ /* Note: This updates persona’s state, which could be
+ * left updated if we later throw an error. */
+ persona.update_from_vcard (card);
+ }
+ }
+
+ if (removed_personas.remove (persona) == false)
+ added_personas.add (persona);
i++;
vcard.erase ();
@@ -278,9 +337,14 @@ public class Folks.Backends.BlueZ.PersonaStore : Folks.PersonaStore
* the store’s internal state. */
foreach (var p in added_personas)
this._personas.set (p.iid, p);
+ foreach (var p in removed_personas)
+ this._personas.unset (p.iid);
- if (added_personas.is_empty == false)
- this._emit_personas_changed (added_personas, null);
+ if (added_personas.is_empty == false ||
+ removed_personas.is_empty == false)
+ {
+ this._emit_personas_changed (added_personas, removed_personas);
+ }
}
/**
@@ -655,7 +719,8 @@ public class Folks.Backends.BlueZ.PersonaStore : Folks.PersonaStore
phonebook_filter.insert ("Format", "Vcard30");
phonebook_filter.insert ("Fields",
new Variant.strv ({
- "N", "FN", "NICKNAME", "TEL", "URL", "EMAIL", "PHOTO"
+ "UID", "N", "FN", "NICKNAME", "TEL", "URL", "EMAIL",
+ "PHOTO"
}));
obex_pbap.pull_all ("", phonebook_filter, out path, out props);
diff --git a/backends/bluez/bluez-persona.vala b/backends/bluez/bluez-persona.vala
index 9dca903..43e0590 100644
--- a/backends/bluez/bluez-persona.vala
+++ b/backends/bluez/bluez-persona.vala
@@ -169,15 +169,17 @@ public class Folks.Backends.BlueZ.Persona : Folks.Persona,
* Create a new persona for the { link PersonaStore} ``store``, representing
* the Persona in the given ``vcard``.
*
- * @param vcard the Vcard stored as a string.
+ * @param vcard the vCard stored as a string
+ * @param card a parsed version of the vCard
* @param store the store to which the Persona belongs.
* @param is_user whether the Persona is the user itself or not.
+ * @param iid pre-calculated IID for the persona
*
* @since 0.9.6
*/
- public Persona (string vcard, Folks.PersonaStore store, bool is_user)
+ public Persona (string vcard, E.VCard card, Folks.PersonaStore store,
+ bool is_user, string iid)
{
- var iid = Checksum.compute_for_string (ChecksumType.SHA1, vcard);
var uid = Folks.Persona.build_uid ("bluez", store.id, iid);
/* Have to use the IID as the display ID, since PBAP vCards provide no
@@ -188,7 +190,7 @@ public class Folks.Backends.BlueZ.Persona : Folks.Persona,
store: store,
is_user: is_user);
- this._set_vcard (vcard);
+ this.update_from_vcard (card);
}
construct
@@ -205,77 +207,162 @@ public class Folks.Backends.BlueZ.Persona : Folks.Persona,
this._urls_ro = this._urls.read_only_view;
}
- private void _set_vcard (string vcard)
+ /**
+ * Update the Persona’s properties from a vCard.
+ *
+ * Parse the given ``vcard`` and set the persona’s properties from it. This
+ * emits property change notifications as appropriate.
+ *
+ * @param vcard pre-parsed vCard
+ *
+ * @since UNRELEASED
+ */
+ internal void update_from_vcard (E.VCard card)
{
- E.VCard card = new E.VCard.from_string (vcard);
+ this.freeze_notify ();
+
+ /* Phone numbers. */
+ var attribute = card.get_attribute ("TEL");
+ var new_phone_numbers = new HashSet<PhoneFieldDetails> ();
- E.VCardAttribute? attribute = card.get_attribute ("TEL");
if (attribute != null)
{
- this._phone_numbers.add (
- new PhoneFieldDetails (attribute.get_value_decoded ().str));
+ unowned GLib.List<unowned StringBuilder> vals =
+ attribute.get_values_decoded ();
+ foreach (unowned StringBuilder v in vals)
+ new_phone_numbers.add (new PhoneFieldDetails (v.str));
}
+ if (!Folks.Internal.equal_sets<PhoneFieldDetails> (this._phone_numbers,
+ new_phone_numbers))
+ {
+ this._phone_numbers = new_phone_numbers;
+ this._phone_numbers_ro = new_phone_numbers.read_only_view;
+ this.notify_property ("phone-numbers");
+ }
+
+ /* Full name. */
attribute = card.get_attribute ("FN");
+ var new_full_name = "";
+
if (attribute != null)
+ new_full_name = attribute.get_value_decoded ().str;
+
+ if (this._full_name != new_full_name)
{
- /* Also the display-id. */
- this._full_name = attribute.get_value_decoded ().str;
+ this._full_name = new_full_name;
+ this.notify_property ("full-name");
}
+ /* Nickname. */
attribute = card.get_attribute ("NICKNAME");
+ var new_nickname = "";
+
if (attribute != null)
+ new_nickname = attribute.get_value_decoded ().str;
+
+ if (this._nickname != new_nickname)
{
- this._nickname = attribute.get_value_decoded ().str;
+ this._nickname = new_nickname;
+ this.notify_property ("nickname");
}
+ /* URIs. */
attribute = card.get_attribute ("URL");
+ var new_uris = new HashSet<UrlFieldDetails> ();
+
if (attribute != null)
{
- var url = attribute.get_value_decoded ().str;
- this._urls.add (new UrlFieldDetails (url));
+ unowned GLib.List<unowned StringBuilder> vals =
+ attribute.get_values_decoded ();
+ foreach (unowned StringBuilder v in vals)
+ new_uris.add (new UrlFieldDetails (v.str));
}
- attribute = card.get_attribute ("PHOTO");
- if (attribute != null)
+ if (!Folks.Internal.equal_sets<UrlFieldDetails> (this._urls, new_uris))
{
- var encoded_data = (string) attribute.get_value ().data;
- var bytes = new Bytes (Base64.decode (encoded_data));
- this._avatar = new BytesIcon (bytes);
+ this._urls = new_uris;
+ this._urls_ro = new_uris.read_only_view;
+ this.notify_property ("urls");
}
+ /* Structured name. */
attribute = card.get_attribute ("N");
+ StructuredName? new_structured_name = null;
+
if (attribute != null)
{
- string[] components = {"", "", "", "", ""};
- uint components_size = 5;
- unowned GLib.List<StringBuilder> values =
+ string[] components = { "", "", "", "", "" };
+ unowned GLib.List<unowned StringBuilder> values =
attribute.get_values_decoded ();
- if (values.length () < components_size)
- components_size = values.length ();
-
- for (int i = 0; i < components_size; i++)
+ uint i = 0;
+ foreach (unowned StringBuilder b in values)
{
- components[i] = values.nth_data (i).str;
+ if (i >= components.length)
+ break;
+
+ components[i++] = b.str;
}
this._structured_name = new StructuredName (components[0],
components[1], components[2], components[3], components[4]);
- if (values.length () != 5)
+ if (i != 5)
{
- debug ("Expected 5 components to N value of vcard, got %u",
- values.length ());
+ debug ("Expected 5 components in N value of vCard, but got %u.",
+ i);
}
}
+ if ((new_structured_name == null) != (this._structured_name == null) ||
+ (new_structured_name != null && this._structured_name != null &&
+ !new_structured_name.equal (this._structured_name)))
+ {
+ this._structured_name = new_structured_name;
+ this.notify_property ("structured-name");
+ }
+
+ /* E-mail addresses. */
attribute = card.get_attribute ("EMAIL");
+ var new_email_addresses = new HashSet<EmailFieldDetails> ();
+
if (attribute != null)
{
- this._email_addresses.add (
- new EmailFieldDetails (attribute.get_value_decoded ().str));
+ unowned GLib.List<unowned StringBuilder> vals =
+ attribute.get_values_decoded ();
+ foreach (unowned StringBuilder v in vals)
+ new_email_addresses.add (new EmailFieldDetails (v.str));
}
+
+ if (!Folks.Internal.equal_sets<EmailFieldDetails> (this._email_addresses,
+ new_email_addresses))
+ {
+ this._email_addresses = new_email_addresses;
+ this._email_addresses_ro = new_email_addresses.read_only_view;
+ this.notify_property ("email-addresses");
+ }
+
+ /* Photo. */
+ attribute = card.get_attribute ("PHOTO");
+ BytesIcon? new_avatar = null;
+
+ if (attribute != null)
+ {
+ var encoded_data = (string) attribute.get_value ().data;
+ var bytes = new Bytes (Base64.decode (encoded_data));
+ new_avatar = new BytesIcon (bytes);
+ }
+
+ if ((new_avatar == null) != (this._avatar == null) ||
+ (new_avatar != null && this._avatar != null &&
+ !new_avatar.equal (this._avatar)))
+ {
+ this._avatar = new_avatar;
+ this.notify_property ("avatar");
+ }
+
+ this.thaw_notify ();
}
/**
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]