[folks] Bug 650414 — Need better APIs to handle image data
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [folks] Bug 650414 — Need better APIs to handle image data
- Date: Mon, 25 Jul 2011 19:39:16 +0000 (UTC)
commit 3862f876a9bef82f4fcf838a00c81ef5c57ae353
Author: Philip Withnall <philip tecnocode co uk>
Date: Mon Jun 13 10:54:23 2011 +0100
Bug 650414 â Need better APIs to handle image data
Change AvatarDetails.avatar to have type LoadableIcon. By introducing
a libfolks-wide avatar cache, propagate this change to all the backends.
This breaks the API of AvatarDetails.
Closes: bgo#650414
NEWS | 3 +
backends/eds/lib/Makefile.am | 1 +
backends/eds/lib/edsf-persona-store.vala | 89 +++------
backends/eds/lib/edsf-persona.vala | 155 +++++++--------
backends/eds/lib/memory-icon.vala | 133 ++++++++++++
backends/libsocialweb/lib/swf-persona.vala | 14 +-
backends/telepathy/lib/tpf-persona.vala | 13 +-
backends/tracker/lib/trf-persona-store.vala | 56 ++++-
backends/tracker/lib/trf-persona.vala | 24 ++-
folks/Makefile.am | 1 +
folks/avatar-cache.vala | 222 ++++++++++++++++++++
folks/avatar-details.vala | 7 +-
folks/individual.vala | 8 +-
tests/eds/add-persona.vala | 23 +--
tests/eds/avatar-details.vala | 20 +--
tests/eds/set-avatar.vala | 23 +--
tests/folks/Makefile.am | 8 +
tests/folks/avatar-cache.vala | 301 +++++++++++++++++++++++++++
tests/tracker/add-persona.vala | 9 +-
tests/tracker/avatar-details-interface.vala | 34 +---
tests/tracker/avatar-updates.vala | 27 ++-
tests/tracker/set-avatar.vala | 6 +-
tools/inspect/utils.vala | 16 ++-
23 files changed, 915 insertions(+), 278 deletions(-)
---
diff --git a/NEWS b/NEWS
index 3084178..07c095b 100644
--- a/NEWS
+++ b/NEWS
@@ -21,6 +21,7 @@ Bugs fixed:
* Bug 645549 â Add a way to get the individual from a persona
* Bug 650422 â Add API for easily checking whether details are writeable
* Bug 655019 â Don't notify twice for nickname changes
+* Bug 650414 â Need better APIs to handle image data
API changes:
* Swf.Persona retains and exposes its libsocialweb Contact
@@ -35,6 +36,8 @@ API changes:
Persona subclasses
* Make BirthdayDetails.calendar_event_id nullable
* Make Folks.Utils public and add Gee structure equality functions
+* AvatarDetails.avatar is now of type LoadableIcon?
+* Add AvatarCache class
Overview of changes from libfolks 0.5.1 to libfolks 0.5.2
=========================================================
diff --git a/backends/eds/lib/Makefile.am b/backends/eds/lib/Makefile.am
index 53905b3..4dc22bf 100644
--- a/backends/eds/lib/Makefile.am
+++ b/backends/eds/lib/Makefile.am
@@ -29,6 +29,7 @@ libfolks_eds_la_vala.stamp:
folks_eds_valasources = \
edsf-persona.vala \
edsf-persona-store.vala \
+ memory-icon.vala \
$(NULL)
libfolks_eds_la_SOURCES = \
diff --git a/backends/eds/lib/edsf-persona-store.vala b/backends/eds/lib/edsf-persona-store.vala
index b3ba9ff..ae539fa 100644
--- a/backends/eds/lib/edsf-persona-store.vala
+++ b/backends/eds/lib/edsf-persona-store.vala
@@ -242,7 +242,7 @@ public class Edsf.PersonaStore : Folks.PersonaStore
}
else if (k == Folks.PersonaStore.detail_key (PersonaDetail.AVATAR))
{
- var avatar = (File) v.get_object ();
+ var avatar = (LoadableIcon?) v.get_object ();
yield this._set_contact_avatar (contact, avatar);
}
else if (k == Folks.PersonaStore.detail_key (
@@ -471,7 +471,7 @@ public class Edsf.PersonaStore : Folks.PersonaStore
}
}
- internal async void _set_avatar (Edsf.Persona persona, File? avatar)
+ internal async void _set_avatar (Edsf.Persona persona, LoadableIcon? avatar)
{
/* Return early if there will be no change */
if ((persona.avatar == null && avatar == null) ||
@@ -479,50 +479,6 @@ public class Edsf.PersonaStore : Folks.PersonaStore
{
return;
}
- else
- {
- if (persona.avatar != null && avatar != null)
- {
- try
- {
- var persona_avatar_input = yield persona.avatar.read_async ();
- var persona_avatar_info =
- yield persona_avatar_input.query_info_async (
- FILE_ATTRIBUTE_STANDARD_SIZE, FileQueryInfoFlags.NONE);
- var persona_avatar_size =
- persona_avatar_info.get_attribute_uint32 (
- FILE_ATTRIBUTE_STANDARD_SIZE);
-
- var avatar_input = yield avatar.read_async ();
- var avatar_info = yield avatar_input.query_info_async (
- FILE_ATTRIBUTE_STANDARD_SIZE, FileQueryInfoFlags.NONE);
- var avatar_size = avatar_info.get_attribute_uint32 (
- FILE_ATTRIBUTE_STANDARD_SIZE);
-
- if (persona_avatar_size == avatar_size)
- {
- var persona_avatar_data = new uint8[persona_avatar_size];
- var avatar_data = new uint8[avatar_size];
- yield persona_avatar_input.read_async (
- persona_avatar_data);
- yield avatar_input.read_async (avatar_data);
-
- var persona_avatar_sum = Checksum.compute_for_data (
- ChecksumType.MD5, persona_avatar_data);
- var avatar_sum = Checksum.compute_for_data (
- ChecksumType.MD5, avatar_data);
-
- if (persona_avatar_sum == avatar_sum)
- return;
- }
- }
- catch (GLib.Error e1)
- {
- warning ("Failed to read an avatar file for comparison: %s",
- e1.message);
- }
- }
- }
try
{
@@ -612,27 +568,40 @@ public class Edsf.PersonaStore : Folks.PersonaStore
}
private async void _set_contact_avatar (E.Contact contact,
- File? avatar)
+ LoadableIcon? avatar)
{
- try
- {
- uint8[] photo_content;
- var cp = new ContactPhoto ();
+ var uid = Folks.Persona.build_uid (BACKEND_NAME, this.id,
+ (string) Edsf.Persona._get_property_from_contact (contact, "id"));
- if (avatar != null)
+ var cache = AvatarCache.dup ();
+ if (avatar != null)
+ {
+ try
{
- yield avatar.load_contents_async (null, out photo_content);
+ // Cache the avatar so that it has a URI
+ var uri = yield cache.store_avatar (uid, avatar);
- cp.type = ContactPhotoType.INLINED;
- cp.set_inlined (photo_content);
- }
+ // Set the avatar on the contact
+ var cp = new ContactPhoto ();
+ cp.type = ContactPhotoType.URI;
+ cp.set_uri (uri);
- contact.set (E.Contact.field_id ("photo"), cp);
+ contact.set (ContactField.PHOTO, cp);
+ }
+ catch (GLib.Error e1)
+ {
+ warning ("Couldn't cache avatar for Edsf.Persona '%s': %s",
+ uid, e1.message);
+ }
}
- catch (GLib.Error e_avatar)
+ else
{
- GLib.warning ("Can't load avatar %s: %s\n\n", avatar.get_path (),
- e_avatar.message);
+ // Delete any old avatar from the cache, ignoring errors
+ try
+ {
+ yield cache.remove_avatar (uid);
+ }
+ catch (GLib.Error e2) {}
}
}
diff --git a/backends/eds/lib/edsf-persona.vala b/backends/eds/lib/edsf-persona.vala
index d020811..291ab0d 100644
--- a/backends/eds/lib/edsf-persona.vala
+++ b/backends/eds/lib/edsf-persona.vala
@@ -211,7 +211,7 @@ public class Edsf.Persona : Folks.Persona,
get { return this._writeable_properties; }
}
- private File _avatar;
+ private LoadableIcon? _avatar = null;
/**
* An avatar for the Persona.
*
@@ -219,7 +219,7 @@ public class Edsf.Persona : Folks.Persona,
*
* @since 0.5.UNRELEASED
*/
- public File avatar
+ public LoadableIcon? avatar
{
get { return this._avatar; }
set
@@ -623,49 +623,90 @@ public class Edsf.Persona : Folks.Persona,
}
}
+ private LoadableIcon? _contact_photo_to_loadable_icon (ContactPhoto? p)
+ {
+ if (p == null)
+ {
+ return null;
+ }
+
+ switch (p.type)
+ {
+ case ContactPhotoType.URI:
+ if (p.get_uri () == null)
+ {
+ return null;
+ }
+
+ return new FileIcon (File.new_for_uri (p.get_uri ()));
+ case ContactPhotoType.INLINED:
+ if (p.get_mime_type () == null || p.get_inlined () == null)
+ {
+ return null;
+ }
+
+ return new Edsf.MemoryIcon (p.get_mime_type (), p.get_inlined ());
+ default:
+ return null;
+ }
+ }
+
private void _update_avatar ()
{
- string filename = this.uid.delimit (Path.DIR_SEPARATOR.to_string (), '-');
- string cached_avatar_path = GLib.Path.build_filename (
- GLib.Environment.get_user_cache_dir (), "folks",
- "avatars", filename);
E.ContactPhoto? p = (E.ContactPhoto) this._get_property ("photo");
- this._avatar = File.new_for_path (cached_avatar_path);
+ var cache = AvatarCache.dup ();
+ var cache_uri = cache.build_uri_for_avatar (this.uid);
- if (p != null)
+ /* Check the avatar isn't being set by our PersonaStore; if it is, just
+ * notify the property and bail. This avoids circular updates to the
+ * cache. */
+ if (p != null &&
+ p.type == ContactPhotoType.URI && p.get_uri () == cache_uri)
{
- var content_old = this.get_avatar_content ();
- var content_new = this._get_avatar_content_from_contact (p);
+ this.notify_property ("avatar");
+ return;
+ }
- if (content_old != content_new)
+ // Convert the ContactPhoto to a LoadableIcon and store or update it.
+ var new_avatar = this._contact_photo_to_loadable_icon (p);
+
+ if (this._avatar != null && new_avatar == null)
+ {
+ // Remove the old cached avatar, ignoring errors.
+ cache.remove_avatar.begin (this.uid, (obj, res) =>
{
try
{
- this._avatar.replace_contents (content_new,
- content_new.length,
- null, false, FileCreateFlags.REPLACE_DESTINATION,
- null);
- this.notify_property ("avatar");
+ cache.remove_avatar.end (res);
}
- catch (GLib.Error e)
- {
- GLib.warning ("Can't write avatar: %s\n", e.message);
- }
- }
+ catch (GLib.Error e1) {}
+
+ this._avatar = new_avatar;
+ this.notify_property ("avatar");
+ });
}
- else
+ else if ((this.avatar == null && new_avatar != null) ||
+ (this.avatar != null && new_avatar != null &&
+ this._avatar.equal (new_avatar) == false))
{
- try
- {
- this._avatar.delete ();
- }
- catch (GLib.Error e) {}
- finally
+ // Store the new avatar in the cache.
+ cache.store_avatar.begin (this.uid, new_avatar, (obj, res) =>
{
- this._avatar = null;
+ try
+ {
+ cache.store_avatar.end (res);
+ }
+ catch (GLib.Error e2)
+ {
+ warning ("Couldn't cache avatar for Edsf.Persona '%s': %s",
+ this.uid, e2.message);
+ new_avatar = null; /* failure */
+ }
+
+ this._avatar = new_avatar;
this.notify_property ("avatar");
- }
+ });
}
}
@@ -765,60 +806,6 @@ public class Edsf.Persona : Folks.Persona,
}
/**
- * Get the avatars content
- *
- * @since 0.5.UNRELEASED
- */
- public string get_avatar_content ()
- {
- string content = "";
-
- if (this._avatar != null &&
- this._avatar.query_exists ())
- {
- try
- {
- uint8[] content_temp;
- this._avatar.load_contents (null, out content_temp);
- content = (string) content_temp;
- }
- catch (GLib.Error e)
- {
- GLib.warning ("Can't compare avatars: %s\n", e.message);
- }
- }
-
- return content;
- }
-
- private string _get_avatar_content_from_contact (E.ContactPhoto p)
- {
- string content = "";
-
- if (p.type == ContactPhotoType.INLINED)
- {
- content = (string) p.get_inlined ();
- }
- else if (p.type == ContactPhotoType.URI)
- {
- try
- {
- uint8[] temp_content;
- var file = File.new_for_uri (p.get_uri ());
- file.load_contents (null, out temp_content);
- content = (string) temp_content;
- }
- catch (GLib.Error e)
- {
- GLib.warning ("Couldn't load content for avatar: %s\n",
- p.get_uri ());
- }
- }
-
- return content;
- }
-
- /**
* build a table of im protocols / im protocol aliases
*/
internal static HashTable<string, E.ContactField> _get_im_eds_map ()
diff --git a/backends/eds/lib/memory-icon.vala b/backends/eds/lib/memory-icon.vala
new file mode 100644
index 0000000..16dab82
--- /dev/null
+++ b/backends/eds/lib/memory-icon.vala
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2011 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 GLib;
+
+/**
+ * A wrapper around a blob of image data (with an associated content type) which
+ * presents it as a { link LoadableIcon}. This allows inlined avatars to be
+ * returned as { link LoadableIcon}s.
+ *
+ * @since UNRELEASED
+ */
+internal class Edsf.MemoryIcon : Object, Icon, LoadableIcon
+{
+ private uint8[] _image_data;
+ private string _image_type;
+
+ /**
+ * Construct a new in-memory icon.
+ *
+ * @param image_type the content type of the image
+ * @param image_data the binary data of the image
+ * @since UNRELEASED
+ */
+ public MemoryIcon (string image_type, uint8[] image_data)
+ {
+ this._image_data = image_data;
+ this._image_type = image_type;
+ }
+
+ /**
+ * Decide whether two { link MemoryIcon} instances are equal. This compares
+ * their image types and image data, and only returns `true` if both are
+ * identical.
+ *
+ * @param icon2 the { link MemoryIcon} instance to compare against
+ * @return `true` if the instances are equal, `false` otherwise
+ * @since UNRELEASED
+ */
+ public bool equal (Icon icon2)
+ {
+ // This type check be taken care of by the interface wrapper.
+ var icon = icon2 as MemoryIcon;
+ assert (icon != null);
+
+ return (this._image_data.length == icon._image_data.length &&
+ this._image_type == icon._image_type &&
+ Memory.cmp (this._image_data, icon._image_data,
+ this._image_data.length) == 0);
+ }
+
+ /**
+ * Calculate a hash value of the image type and data, suitable for use as a
+ * hash table key. This is not a cryptographic hash.
+ *
+ * @return hash value over the image type and data
+ * @since UNRELEASED
+ */
+ public uint hash ()
+ {
+ /* Implementation based on g_str_hash() from GLib. We initialise the hash
+ * with the g_str_hash() hash of the image type (which itself is
+ * initialised with the magic number in GLib thought up by cleverer people
+ * than myself), then add each byte in the image data to the hash value
+ * by multiplying the hash value by 33 and adding the image data, as is
+ * done on all bytes in g_str_hash(). I leave the rationale for this
+ * calculation to the author of g_str_hash().
+ *
+ * Basically, this is just a nul-safe version of g_str_hash(). Which is
+ * calculated over both the image type and image data. */
+ uint hash = this._image_type.hash ();
+
+ for (uint i = 0; i < this._image_data.length; i++)
+ {
+ hash = (hash << 5) + hash + this._image_data[i];
+ }
+
+ return hash;
+ }
+
+ /**
+ * Build an input stream for loading the image data. This will return
+ * without blocking on I/O.
+ *
+ * @param size the square dimensions to output the image at (unused), or -1
+ * @param type return location for the content type of the image, or `null`
+ * @param cancellable optional { link GLib.Cancellable}, or `null`
+ * @return an input stream providing access to the image data
+ * @since UNRELEASED
+ */
+ public InputStream load (int size, out string? type,
+ Cancellable? cancellable = null)
+ {
+ type = this._image_type;
+ return new MemoryInputStream.from_data (this._image_data, free);
+ }
+
+ /**
+ * Asynchronously build an input stream for loading the image data. This
+ * will complete without blocking on I/O.
+ *
+ * @param size the square dimensions to output the image at (unused), or -1
+ * @param cancellable optional { link GLib.Cancellable}, or `null`
+ * @param type return location for the content type of the image, or `null`
+ * @return an input stream providing access to the image data
+ * @since UNRELEASED
+ */
+ public async InputStream load_async (int size,
+ GLib.Cancellable? cancellable, out string? type)
+ {
+ type = this._image_type;
+ return new MemoryInputStream.from_data (this._image_data, free);
+ }
+}
+
+/* vim: filetype=vala textwidth=80 tabstop=2 expandtab: */
diff --git a/backends/libsocialweb/lib/swf-persona.vala b/backends/libsocialweb/lib/swf-persona.vala
index 9dd8a76..f075cb9 100644
--- a/backends/libsocialweb/lib/swf-persona.vala
+++ b/backends/libsocialweb/lib/swf-persona.vala
@@ -66,8 +66,10 @@ public class Swf.Persona : Folks.Persona,
* An avatar for the Persona.
*
* See { link Folks.AvatarOwner.avatar}.
+ *
+ * @since UNRELEASED
*/
- public File avatar { get; private set; }
+ public LoadableIcon? avatar { get; private set; }
/**
* { inheritDoc}
@@ -273,9 +275,13 @@ public class Swf.Persona : Folks.Persona,
var avatar_path = contact.get_value ("icon");
if (avatar_path != null)
{
- var avatar_file = File.new_for_path (avatar_path);
- if (this.avatar != avatar_file)
- this.avatar = avatar_file;
+ var icon = new FileIcon (File.new_for_path (avatar_path));
+ if (this.avatar == null || !this.avatar.equal (icon))
+ this.avatar = icon;
+ }
+ else
+ {
+ this.avatar = null;
}
var structured_name = new StructuredName.simple (
diff --git a/backends/telepathy/lib/tpf-persona.vala b/backends/telepathy/lib/tpf-persona.vala
index d31a4c3..18a06d9 100644
--- a/backends/telepathy/lib/tpf-persona.vala
+++ b/backends/telepathy/lib/tpf-persona.vala
@@ -70,8 +70,10 @@ public class Tpf.Persona : Folks.Persona,
* An avatar for the Persona.
*
* See { link Folks.AvatarDetails.avatar}.
+ *
+ * @since UNRELEASED
*/
- public File avatar { get; private set; }
+ public LoadableIcon? avatar { get; private set; }
/**
* The Persona's presence type.
@@ -416,7 +418,12 @@ public class Tpf.Persona : Folks.Persona,
private void _contact_notify_avatar ()
{
var file = this.contact.avatar_file;
- if (this.avatar != file)
- this.avatar = file;
+ Icon? icon = null;
+
+ if (file != null)
+ icon = new FileIcon (file);
+
+ if (this.avatar == null || icon == null || !this.avatar.equal (icon))
+ this.avatar = (LoadableIcon) icon;
}
}
diff --git a/backends/tracker/lib/trf-persona-store.vala b/backends/tracker/lib/trf-persona-store.vala
index 47b4c29..2ac30cf 100644
--- a/backends/tracker/lib/trf-persona-store.vala
+++ b/backends/tracker/lib/trf-persona-store.vala
@@ -401,6 +401,10 @@ public class Trf.PersonaStore : Folks.PersonaStore
public override async Folks.Persona? add_persona_from_details (
HashTable<string, Value?> details) throws Folks.PersonaStoreError
{
+ /* We have to set the avatar after pushing the new persona to Tracker,
+ * as we need a UID so that we can cache the avatar. */
+ LoadableIcon? avatar = null;
+
var builder = new Tracker.Sparql.Builder.update ();
builder.insert_open (null);
builder.subject ("_:p");
@@ -451,15 +455,13 @@ public class Trf.PersonaStore : Folks.PersonaStore
}
else if (k == Folks.PersonaStore.detail_key (PersonaDetail.AVATAR))
{
- var avatar = (File) v.get_object ();
- builder.subject ("_:photo");
- builder.predicate ("a");
- builder.object ("nfo:Image, nie:DataObject");
- builder.predicate (Trf.OntologyDefs.NIE_URL);
- builder.object_string (avatar.get_uri ());
- builder.subject ("_:p");
- builder.predicate (Trf.OntologyDefs.NCO_PHOTO);
- builder.object ("_:photo");
+ /* Update the avatar which we'll set later (once we have the
+ * persona's UID) */
+ var new_avatar = (LoadableIcon) v.get_object ();
+ if (new_avatar != null)
+ {
+ avatar = new_avatar;
+ }
}
else if (k == Folks.PersonaStore.detail_key (PersonaDetail.BIRTHDAY))
{
@@ -708,6 +710,12 @@ public class Trf.PersonaStore : Folks.PersonaStore
}
}
+ // Set the avatar on the persona now that we know the persona's UID
+ if (ret != null && avatar != null)
+ {
+ yield this._set_avatar (ret, avatar);
+ }
+
return ret;
}
@@ -1532,7 +1540,7 @@ public class Trf.PersonaStore : Folks.PersonaStore
avatar_url = yield this._get_property (e.object_id,
Trf.OntologyDefs.NIE_URL, Trf.OntologyDefs.NFO_IMAGE);
}
- p._set_avatar (avatar_url);
+ p._set_avatar_from_uri (avatar_url);
}
else if (e.pred_id == this._prefix_tracker_id.get
(Trf.OntologyDefs.NAO_PROPERTY))
@@ -2166,7 +2174,7 @@ public class Trf.PersonaStore : Folks.PersonaStore
}
internal async void _set_avatar (Folks.Persona persona,
- File? avatar)
+ LoadableIcon? avatar)
{
const string query_d = "DELETE {" +
" ?c " + Trf.OntologyDefs.NCO_PHOTO + " ?p " +
@@ -2196,10 +2204,34 @@ public class Trf.PersonaStore : Folks.PersonaStore
this._delete_resource ("<%s>".printf (image_urn));
string query = query_d.printf (p_id);
+
+ var cache = AvatarCache.dup ();
if (avatar != null)
{
- query += query_i.printf (avatar.get_uri (), p_id);
+ try
+ {
+ // Cache the avatar so that it has a URI
+ var uri = yield cache.store_avatar (persona.uid, avatar);
+
+ // Add the avatar to the query
+ query += query_i.printf (uri , p_id);
+ }
+ catch (GLib.Error e1)
+ {
+ warning ("Couldn't cache avatar for Trf.Persona '%s': %s",
+ persona.uid, e1.message);
+ }
+ }
+ else
+ {
+ // Delete any old avatar from the cache, ignoring errors
+ try
+ {
+ yield cache.remove_avatar (persona.uid);
+ }
+ catch (GLib.Error e2) {}
}
+
yield this._tracker_update (query, "_set_avatar");
}
diff --git a/backends/tracker/lib/trf-persona.vala b/backends/tracker/lib/trf-persona.vala
index de06745..1e8777c 100644
--- a/backends/tracker/lib/trf-persona.vala
+++ b/backends/tracker/lib/trf-persona.vala
@@ -135,13 +135,15 @@ public class Trf.Persona : Folks.Persona,
get { return this._writeable_properties; }
}
- private File _avatar;
+ private LoadableIcon? _avatar = null;
/**
* An avatar for the Persona.
*
* See { link Folks.Avatar.avatar}.
+ *
+ * @since UNRELEASED
*/
- public File avatar
+ public LoadableIcon? avatar
{
get { return this._avatar; }
public set
@@ -817,17 +819,25 @@ public class Trf.Persona : Folks.Persona,
{
string avatar_url = this._cursor.get_string (
Trf.Fields.AVATAR_URL).dup ();
- this._set_avatar (avatar_url);
+ this._set_avatar_from_uri (avatar_url);
}
- internal bool _set_avatar (string? avatar_url)
+ internal bool _set_avatar_from_uri (string? avatar_url)
{
- File _avatar = null;
+ LoadableIcon _avatar = null;
if (avatar_url != null && avatar_url != "")
{
- _avatar = File.new_for_uri (avatar_url);
+ _avatar = new FileIcon (File.new_for_uri (avatar_url));
}
- this._avatar = _avatar;
+
+ this._set_avatar (_avatar);
+
+ return true;
+ }
+
+ internal bool _set_avatar (LoadableIcon? avatar)
+ {
+ this._avatar = avatar;
this.notify_property ("avatar");
return true;
}
diff --git a/folks/Makefile.am b/folks/Makefile.am
index 6c66c18..c145087 100644
--- a/folks/Makefile.am
+++ b/folks/Makefile.am
@@ -40,6 +40,7 @@ libfolks_la_SOURCES = \
debug.vala \
utils.vala \
potential-match.vala \
+ avatar-cache.vala \
$(NULL)
libfolks_la_VALAFLAGS = \
diff --git a/folks/avatar-cache.vala b/folks/avatar-cache.vala
new file mode 100644
index 0000000..8c0f4de
--- /dev/null
+++ b/folks/avatar-cache.vala
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * 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 withnall collabora co uk>
+ */
+
+using GLib;
+
+/**
+ * A singleton persistent cache object for avatars used across backends in
+ * folks. Avatars may be added to the cache, and referred to by a persistent
+ * URI from that point onwards.
+ *
+ * @since UNRELEASED
+ */
+public class Folks.AvatarCache : Object
+{
+ private static weak AvatarCache _instance = null; /* needs to be locked */
+ private File _cache_directory;
+
+ /**
+ * Private constructor for an instance of the avatar cache. The singleton
+ * instance should be retrieved by calling { link AvatarCache.dup()} instead.
+ *
+ * @since UNRELEASED
+ */
+ private AvatarCache ()
+ {
+ this._cache_directory =
+ File.new_for_path (Environment.get_user_cache_dir ())
+ .get_child ("folks")
+ .get_child ("avatars");
+ }
+
+ /**
+ * Create or return the singleton { link AvatarCache} class instance.
+ * If the instance doesn't exist already, it will be created.
+ *
+ * This function is thread-safe.
+ *
+ * @return Singleton { link AvatarCache} instance
+ * @since UNRELEASED
+ */
+ public static AvatarCache dup ()
+ {
+ lock (AvatarCache._instance)
+ {
+ var retval = AvatarCache._instance;
+
+ if (retval == null)
+ {
+ /* use an intermediate variable to force a strong reference */
+ retval = new AvatarCache ();
+ AvatarCache._instance = retval;
+ }
+
+ return retval;
+ }
+ }
+
+ ~AvatarCache ()
+ {
+ /* Manually clear the singleton _instance */
+ lock (AvatarCache._instance)
+ {
+ AvatarCache._instance = null;
+ }
+ }
+
+ /**
+ * Fetch an avatar from the cache by its globally unique ID.
+ *
+ * @param id the globally unique ID for the avatar
+ * @return Avatar from the cache, or `null` if it doesn't exist in the cache
+ * @throws GLib.Error if checking for existence of the cache file failed
+ * @since UNRELEASED
+ */
+ public async LoadableIcon? load_avatar (string id) throws GLib.Error
+ {
+ var avatar_file = this._get_avatar_file (id);
+
+ // Return null if the avatar doesn't exist
+ if (avatar_file.query_exists () == false)
+ {
+ return null;
+ }
+
+ return new FileIcon (avatar_file);
+ }
+
+ /**
+ * Store an avatar in the cache, assigning the given globally unique ID to it,
+ * which can later be used to load and remove the avatar from the cache. For
+ * example, this ID could be the UID of a persona. The URI of the cached
+ * avatar file will be returned.
+ *
+ * @param id the globally unique ID for the avatar
+ * @param avatar the avatar data to cache
+ * @return a URI for the file storing the cached avatar
+ * @throws GLib.Error if the avatar data couldn't be loaded, or if creating
+ * the avatar directory or cache file failed
+ * @since UNRELEASED
+ */
+ public async string store_avatar (string id, LoadableIcon avatar)
+ throws GLib.Error
+ {
+ var dest_avatar_file = this._get_avatar_file (id);
+
+ // Copy the icon data into a file
+ while (true)
+ {
+ InputStream src_avatar_stream =
+ yield avatar.load_async (-1, null, null);
+
+ try
+ {
+ OutputStream dest_avatar_stream =
+ yield dest_avatar_file.replace_async (null, false,
+ FileCreateFlags.PRIVATE);
+
+ yield dest_avatar_stream.splice_async (src_avatar_stream,
+ OutputStreamSpliceFlags.CLOSE_SOURCE |
+ OutputStreamSpliceFlags.CLOSE_TARGET);
+
+ break;
+ }
+ catch (GLib.Error e)
+ {
+ /* If the parent directory wasn't found, create it and loop
+ * round to try again. */
+ if (e is IOError.NOT_FOUND)
+ {
+ this._create_cache_directory ();
+ continue;
+ }
+
+ throw e;
+ }
+ }
+
+ return this.build_uri_for_avatar (id);
+ }
+
+ /**
+ * Remove an avatar from the cache, if it exists in the cache. If the avatar
+ * exists in the cache but there is a problem in removing it, an
+ * { link IOError} will be thrown.
+ *
+ * @param id the globally unique ID for the avatar
+ * @throws GLib.Error if deleting the cache file failed
+ * @since UNRELEASED
+ */
+ public async void remove_avatar (string id) throws GLib.Error
+ {
+ var avatar_file = this._get_avatar_file (id);
+ try
+ {
+ avatar_file.delete (null);
+ }
+ catch (GLib.Error e)
+ {
+ // Ignore file not found errors
+ if (!(e is IOError.NOT_FOUND))
+ {
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Build the URI of an avatar file in the cache from a globally unique ID.
+ * This will always succeed, even if the avatar doesn't exist in the cache.
+ *
+ * @param id the globally unique ID for the avatar
+ * @return URI of the avatar file with the given globally unique ID
+ * @since UNRELEASED
+ */
+ public string build_uri_for_avatar (string id)
+ {
+ return this._get_avatar_file (id).get_uri ();
+ }
+
+ private File _get_avatar_file (string id)
+ {
+ var escaped_uri = Uri.escape_string (id, "", false);
+ var file = this._cache_directory.get_child (escaped_uri);
+
+ assert (file.has_parent (this._cache_directory) == true);
+
+ return file;
+ }
+
+ private void _create_cache_directory () throws GLib.Error
+ {
+ try
+ {
+ this._cache_directory.make_directory_with_parents ();
+ }
+ catch (GLib.Error e)
+ {
+ // Ignore errors caused by the directory existing already
+ if (!(e is IOError.EXISTS))
+ {
+ throw e;
+ }
+ }
+ }
+}
diff --git a/folks/avatar-details.vala b/folks/avatar-details.vala
index f9732b9..135cfee 100644
--- a/folks/avatar-details.vala
+++ b/folks/avatar-details.vala
@@ -30,7 +30,10 @@ public interface Folks.AvatarDetails : Object
* An avatar for the contact.
*
* An avatar is a small image file which represents the contact. It may be
- * `null` if unset.
+ * `null` if unset. Otherwise, the image data may be asynchronously loaded
+ * using the methods of the { link LoadableIcon} implementation.
+ *
+ * @since UNRELEASED
*/
- public abstract File avatar { get; set; }
+ public abstract LoadableIcon? avatar { get; set; }
}
diff --git a/folks/individual.vala b/folks/individual.vala
index 435a299..1c964e5 100644
--- a/folks/individual.vala
+++ b/folks/individual.vala
@@ -115,8 +115,10 @@ public class Folks.Individual : Object,
/**
* { inheritDoc}
+ *
+ * @since UNRELEASED
*/
- public File avatar { get; private set; }
+ public LoadableIcon? avatar { get; private set; }
/**
* { inheritDoc}
@@ -1013,7 +1015,7 @@ public class Folks.Individual : Object,
private void _update_avatar ()
{
- File avatar = null;
+ LoadableIcon? avatar = null;
foreach (var p in this._persona_set)
{
@@ -1026,7 +1028,7 @@ public class Folks.Individual : Object,
}
/* only notify if the value has changed */
- if (this.avatar != avatar)
+ if (this.avatar == null || !this.avatar.equal (avatar))
this.avatar = avatar;
}
diff --git a/tests/eds/add-persona.vala b/tests/eds/add-persona.vala
index acc0660..f4fc660 100644
--- a/tests/eds/add-persona.vala
+++ b/tests/eds/add-persona.vala
@@ -164,8 +164,8 @@ public class AddPersonaTests : Folks.TestCase
Folks.PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES),
(owned) v2);
- Value? v3 = Value (typeof (File));
- File avatar = File.new_for_path (this._avatar_path);
+ Value? v3 = Value (typeof (LoadableIcon));
+ var avatar = new FileIcon (File.new_for_path (this._avatar_path));
v3.set_object (avatar);
details.insert (Folks.PersonaStore.detail_key (PersonaDetail.AVATAR),
(owned) v3);
@@ -300,24 +300,11 @@ public class AddPersonaTests : Folks.TestCase
if (i.avatar != null)
{
- uint8[] content_a;
- uint8[] content_b;
- var b = File.new_for_path (this._avatar_path);
+ var b = new FileIcon (File.new_for_path (this._avatar_path));
- try
+ if (b.equal (i.avatar) == true)
{
- i.avatar.load_contents (null, out content_a);
- b.load_contents (null, out content_b);
-
- if (((string) content_a) == ((string) content_b))
- {
- this._properties_found.replace ("avatar", true);
- }
- }
- catch (GLib.Error e)
- {
- GLib.warning ("Couldn't load avatars: %s",
- e.message);
+ this._properties_found.replace ("avatar", true);
}
}
diff --git a/tests/eds/avatar-details.vala b/tests/eds/avatar-details.vala
index 63332ef..ae55c48 100644
--- a/tests/eds/avatar-details.vala
+++ b/tests/eds/avatar-details.vala
@@ -117,24 +117,12 @@ public class AvatarDetailsTests : Folks.TestCase
if (i.full_name == "bernie h. innocenti")
{
- uint8[] content_a;
- uint8[] content_b;
- var b = File.new_for_path (this._avatar_path);
+ var b = new FileIcon (File.new_for_path (this._avatar_path));
- try
+ if (b.equal (i.avatar) == true)
{
- i.avatar.load_contents (null, out content_a);
- b.load_contents (null, out content_b);
-
- if (((string) content_a) == ((string) content_b))
- {
- this._avatars_are_equal = true;
- this._main_loop.quit ();
- }
- }
- catch (GLib.Error e)
- {
- GLib.warning ("couldn't load file a");
+ this._avatars_are_equal = true;
+ this._main_loop.quit ();
}
}
}
diff --git a/tests/eds/set-avatar.vala b/tests/eds/set-avatar.vala
index d12eaa7..5f17f0c 100644
--- a/tests/eds/set-avatar.vala
+++ b/tests/eds/set-avatar.vala
@@ -29,7 +29,7 @@ public class SetAvatarTests : Folks.TestCase
private GLib.MainLoop _main_loop;
private bool _found_before_update;
private bool _found_after_update;
- private File _avatar;
+ private LoadableIcon _avatar;
public SetAvatarTests ()
{
@@ -55,7 +55,7 @@ public class SetAvatarTests : Folks.TestCase
Gee.HashMap<string, Value?> c1 = new Gee.HashMap<string, Value?> ();
this._main_loop = new GLib.MainLoop (null, false);
var avatar_path = Environment.get_variable ("AVATAR_FILE_PATH");
- this._avatar = File.new_for_path (avatar_path);
+ this._avatar = new FileIcon (File.new_for_path (avatar_path));
Value? v;
this._found_before_update = false;
@@ -132,23 +132,10 @@ public class SetAvatarTests : Folks.TestCase
var name = (Folks.NameDetails) i;
if (name.full_name == "bernie h. innocenti")
{
- uint8[] content_a;
- uint8[] content_b;
-
- try
- {
- i.avatar.load_contents (null, out content_a);
- this._avatar.load_contents (null, out content_b);
-
- if (((string) content_a) == ((string) content_b))
- {
- this._found_after_update = true;
- this._main_loop.quit ();
- }
- }
- catch (GLib.Error e)
+ if (this._avatar.equal (i.avatar) == true)
{
- GLib.warning ("Couldn't compare avatars: %s\n", e.message);
+ this._found_after_update = true;
+ this._main_loop.quit ();
}
}
}
diff --git a/tests/folks/Makefile.am b/tests/folks/Makefile.am
index 2bbaca8..8723a28 100644
--- a/tests/folks/Makefile.am
+++ b/tests/folks/Makefile.am
@@ -43,14 +43,17 @@ noinst_PROGRAMS = \
utils \
backend-loading \
aggregation \
+ avatar-cache \
$(NULL)
SESSION_CONF = $(top_builddir)/tests/lib/telepathy/contactlist/session.conf
backend_store_key_file=$(top_srcdir)/tests/data/backend-store-all.ini
+avatar_file= abs_top_srcdir@/tests/data/avatar-01.jpg
TESTS_ENVIRONMENT = \
GCONF_DEFAULT_SOURCE_PATH= abs_top_srcdir@/tests/data/gconf.path \
FOLKS_BACKEND_PATH=$(BACKEND_UNINST_PATH) \
FOLKS_BACKEND_STORE_KEY_FILE_PATH=$(backend_store_key_file) \
+ AVATAR_FILE_PATH=$(avatar_file) \
$(RUN_WITH_PRIVATE_BUS) \
--config-file=$(SESSION_CONF) \
--
@@ -73,6 +76,10 @@ utils_SOURCES = \
utils.vala \
$(NULL)
+avatar_cache_SOURCES = \
+ avatar-cache.vala \
+ $(NULL)
+
CLEANFILES = \
*.pid \
*.address \
@@ -85,6 +92,7 @@ MAINTAINERCLEANFILES = \
aggregation_vala.stamp \
field_details_vala.stamp \
utils_vala.stamp \
+ avatar_cache_vala.stamp \
$(NULL)
EXTRA_DIST = \
diff --git a/tests/folks/avatar-cache.vala b/tests/folks/avatar-cache.vala
new file mode 100644
index 0000000..f9d0062
--- /dev/null
+++ b/tests/folks/avatar-cache.vala
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2011 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 GLib;
+using Gee;
+using Folks;
+
+public class AvatarCacheTests : Folks.TestCase
+{
+ private AvatarCache _cache;
+ private File _cache_dir;
+ private LoadableIcon _avatar;
+ private MainLoop _main_loop;
+
+ public AvatarCacheTests ()
+ {
+ base ("AvatarCache");
+
+ /* Use a temporary cache directory */
+ this._cache_dir =
+ File.new_for_path (Environment.get_tmp_dir ()).
+ get_child ("folks-avatar-cache-tests");
+
+ this.add_test ("store-and-load-avatar", this.test_store_and_load_avatar);
+ this.add_test ("store-avatar-overwrite",
+ this.test_store_avatar_overwrite);
+ this.add_test ("load-avatar-non-existent",
+ this.test_load_avatar_non_existent);
+ this.add_test ("remove-avatar", this.test_remove_avatar);
+ this.add_test ("remove-avatar-non-existent",
+ this.test_remove_avatar_non_existent);
+ this.add_test ("build-uri-for-avatar", this.test_build_uri_for_avatar);
+ }
+
+ public override void set_up ()
+ {
+ this._delete_cache_directory ();
+ Environment.set_variable ("XDG_CACHE_HOME", this._cache_dir.get_path (),
+ true);
+
+ this._cache = AvatarCache.dup ();
+ this._avatar =
+ new FileIcon (File.new_for_path (
+ Environment.get_variable ("AVATAR_FILE_PATH")));
+
+ this._main_loop = new GLib.MainLoop (null, false);
+ }
+
+ public override void tear_down ()
+ {
+ this._main_loop = null;
+ this._avatar = null;
+ this._cache = null;
+ this._delete_cache_directory ();
+ }
+
+ protected void _delete_directory (File dir) throws GLib.Error
+ {
+ // Delete the files in the directory
+ var enumerator =
+ dir.enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME,
+ FileQueryInfoFlags.NONE);
+
+ FileInfo? file_info = enumerator.next_file ();
+ while (file_info != null)
+ {
+ var child_file = dir.get_child (file_info.get_name ());
+
+ if (child_file.query_file_type (FileQueryInfoFlags.NONE) ==
+ FileType.DIRECTORY)
+ {
+ this._delete_directory (child_file);
+ }
+ else
+ {
+ child_file.delete ();
+ }
+
+ file_info = enumerator.next_file ();
+ }
+ enumerator.close ();
+
+ // Delete the directory itself
+ dir.delete ();
+ }
+
+ protected void _delete_cache_directory ()
+ {
+ try
+ {
+ this._delete_directory (this._cache_dir);
+ }
+ catch (Error e)
+ {
+ // Ignore it
+ }
+ }
+
+ protected bool _avatars_equal (LoadableIcon avatar1,
+ LoadableIcon avatar2)
+ {
+ if (avatar1.equal (avatar2) == true)
+ {
+ return true;
+ }
+
+ // Compare content instead.
+ try
+ {
+ var stream1 = avatar1.load (-1, null, null);
+ var stream2 = avatar2.load (-1, null, null);
+
+ var content1 = new uint8[512];
+ var content2 = new uint8[512];
+
+ ssize_t read1 = -1;
+ do
+ {
+ read1 = stream1.read (content1, null);
+ var read2 = stream2.read (content2, null);
+
+ if (read1 != read2 || Memory.cmp (content1, content2, read1) != 0)
+ {
+ return false;
+ }
+ }
+ while (read1 > 0);
+ }
+ catch (GLib.Error e)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected void _assert_store_avatar (string id, LoadableIcon avatar)
+ {
+ this._cache.store_avatar.begin (id, avatar, (obj, res) =>
+ {
+ try
+ {
+ this._cache.store_avatar.end (res);
+ }
+ catch (GLib.Error e)
+ {
+ error ("Error storing avatar: %s", e.message);
+ }
+
+ this._main_loop.quit ();
+ });
+
+ this._main_loop.run ();
+ }
+
+ protected LoadableIcon? _assert_load_avatar (string id)
+ {
+ LoadableIcon? avatar = null;
+
+ this._cache.load_avatar.begin (id, (obj, res) =>
+ {
+ try
+ {
+ avatar = this._cache.load_avatar.end (res);
+ }
+ catch (GLib.Error e)
+ {
+ error ("Error loading avatar: %s", e.message);
+ }
+
+ this._main_loop.quit ();
+ });
+
+ this._main_loop.run ();
+
+ return avatar;
+ }
+
+ protected void _assert_remove_avatar (string id)
+ {
+ this._cache.remove_avatar.begin (id, (obj, res) =>
+ {
+ try
+ {
+ this._cache.remove_avatar.end (res);
+ }
+ catch (GLib.Error e)
+ {
+ error ("Error removing avatar: %s", e.message);
+ }
+
+ this._main_loop.quit ();
+ });
+
+ this._main_loop.run ();
+ }
+
+ public void test_store_and_load_avatar ()
+ {
+ // Store the avatar.
+ this._assert_store_avatar ("test-store-avatar-id", this._avatar);
+
+ // Load it again.
+ var avatar = this._assert_load_avatar ("test-store-avatar-id");
+
+ // Check the avatar's OK
+ assert (avatar != null);
+ assert (avatar is LoadableIcon);
+ assert (this._avatars_equal (this._avatar, avatar) == true);
+ }
+
+ public void test_store_avatar_overwrite ()
+ {
+ // Store the avatar twice.
+ this._assert_store_avatar ("test-store-avatar-ow-id", this._avatar);
+ this._assert_store_avatar ("test-store-avatar-ow-id", this._avatar);
+
+ // Load it again.
+ var avatar = this._assert_load_avatar ("test-store-avatar-ow-id");
+
+ // Check the avatar's OK
+ assert (avatar != null);
+ assert (avatar is LoadableIcon);
+ assert (this._avatars_equal (this._avatar, avatar) == true);
+ }
+
+ public void test_load_avatar_non_existent ()
+ {
+ // Load a non-existent avatar.
+ var avatar = this._assert_load_avatar ("test-load-avatar-non-existent");
+ assert (avatar == null);
+ }
+
+ public void test_remove_avatar ()
+ {
+ LoadableIcon? avatar = null;
+
+ // Store the avatar.
+ this._assert_store_avatar ("test-remove-avatar", this._avatar);
+
+ // Check it's been stored OK.
+ avatar = this._assert_load_avatar ("test-remove-avatar");
+ assert (avatar != null);
+
+ // Remove it.
+ this._assert_remove_avatar ("test-remove-avatar");
+
+ // Check it's been removed OK.
+ avatar = this._assert_load_avatar ("test-remove-avatar");
+ assert (avatar == null);
+ }
+
+ public void test_remove_avatar_non_existent ()
+ {
+ // Check the avatar doesn't exist.
+ var avatar = this._assert_load_avatar ("test-remove-avatar-non-existent");
+ assert (avatar == null);
+
+ // Attempt to remove it.
+ this._assert_remove_avatar ("test-remove-avatar-non-existent");
+ }
+
+ public void test_build_uri_for_avatar ()
+ {
+ // Basic checks on the constructed URI.
+ var uri = this._cache.build_uri_for_avatar ("test-id");
+ assert (uri != null);
+ assert (Uri.parse_scheme (uri) != null); /* basic check for validity */
+ }
+}
+
+public int main (string[] args)
+{
+ Test.init (ref args);
+
+ TestSuite root = TestSuite.get_root ();
+ root.add_suite (new AvatarCacheTests ().get_suite ());
+
+ Test.run ();
+
+ return 0;
+}
+
+/* vim: filetype=vala textwidth=80 tabstop=2 expandtab: */
diff --git a/tests/tracker/add-persona.vala b/tests/tracker/add-persona.vala
index 3c6fb36..328cf0e 100644
--- a/tests/tracker/add-persona.vala
+++ b/tests/tracker/add-persona.vala
@@ -34,6 +34,7 @@ public class AddPersonaTests : Folks.TestCase
private string _given_name;
private HashTable<string, bool> _properties_found;
private string _persona_iid;
+ private LoadableIcon _avatar;
private string _file_uri;
private string _birthday;
private DateTime _bday;
@@ -85,6 +86,7 @@ public class AddPersonaTests : Folks.TestCase
this._given_name = "given";
this._persona_iid = "";
this._file_uri = "file:///tmp/some-avatar.jpg";
+ this._avatar = new FileIcon (File.new_for_uri (this._file_uri));
this._birthday = "2001-10-26T20:32:52Z";
this._email_1 = "someone-1 example org";
this._email_2 = "someone-2 example org";
@@ -202,9 +204,8 @@ public class AddPersonaTests : Folks.TestCase
Folks.PersonaStore.detail_key (PersonaDetail.STRUCTURED_NAME),
(owned) v4);
- Value? v5 = Value (typeof (File));
- File avatar = File.new_for_uri (this._file_uri);
- v5.set_object (avatar);
+ Value? v5 = Value (typeof (LoadableIcon));
+ v5.set_object (this._avatar);
details.insert (Folks.PersonaStore.detail_key (PersonaDetail.AVATAR),
(owned) v5);
@@ -403,7 +404,7 @@ public class AddPersonaTests : Folks.TestCase
}
if (i.avatar != null &&
- i.avatar.get_uri () == this._file_uri)
+ i.avatar.equal (this._avatar))
this._properties_found.replace ("avatar", true);
if (i.birthday != null &&
diff --git a/tests/tracker/avatar-details-interface.vala b/tests/tracker/avatar-details-interface.vala
index 77c92f5..fe6cba8 100644
--- a/tests/tracker/avatar-details-interface.vala
+++ b/tests/tracker/avatar-details-interface.vala
@@ -112,8 +112,8 @@ public class AvatarDetailsInterfaceTests : Folks.TestCase
if (i.avatar != null)
{
var src_avatar = File.new_for_uri (this._avatar_uri);
- this._avatars_are_equal =
- this._compare_files (src_avatar, i.avatar);
+ var src_icon = new FileIcon (src_avatar);
+ this._avatars_are_equal = src_icon.equal (i.avatar);
this._main_loop.quit ();
}
}
@@ -124,36 +124,10 @@ public class AvatarDetailsInterfaceTests : Folks.TestCase
{
Folks.Individual individual = (Folks.Individual) individual_obj;
var src_avatar = File.new_for_uri (this._avatar_uri);
- this._avatars_are_equal = this._compare_files (src_avatar,
- individual.avatar);
+ var src_icon = new FileIcon (src_avatar);
+ this._avatars_are_equal = src_icon.equal (individual.avatar);
this._main_loop.quit ();
}
-
- private bool _compare_files (File a, File b)
- {
- uint8 *content_a;
- uint8 *content_b;
-
- try
- {
- a.load_contents (null, out content_a);
- }
- catch (GLib.Error e)
- {
- GLib.warning ("couldn't load file a");
- }
-
- try
- {
- b.load_contents (null, out content_b);
- }
- catch (GLib.Error e)
- {
- GLib.warning ("couldn't load file b");
- }
-
- return ((string) content_a) == ((string) content_b);
- }
}
public int main (string[] args)
diff --git a/tests/tracker/avatar-updates.vala b/tests/tracker/avatar-updates.vala
index 998a5d7..9dfa881 100644
--- a/tests/tracker/avatar-updates.vala
+++ b/tests/tracker/avatar-updates.vala
@@ -28,12 +28,13 @@ public class AvatarUpdatesTests : Folks.TestCase
private TrackerTest.Backend _tracker_backend;
private IndividualAggregator _aggregator;
private bool _updated_avatar_found;
- private string _updated_avatar;
+ private string _updated_avatar_uri;
+ private LoadableIcon _updated_avatar;
private string _individual_id;
private GLib.MainLoop _main_loop;
private bool _initial_avatar_found;
private string _initial_fullname;
- private string _initial_avatar;
+ private string _initial_avatar_uri;
private string _contact_urn;
private string _photo_urn;
@@ -60,14 +61,16 @@ public class AvatarUpdatesTests : Folks.TestCase
this._main_loop = new GLib.MainLoop (null, false);
Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
this._initial_fullname = "persona #1";
- this._initial_avatar = "file:///tmp/avatar-01";
+ this._initial_avatar_uri = "file:///tmp/avatar-01";
this._contact_urn = "<urn:contact001>";
- this._photo_urn = "<" + this._initial_avatar + ">";
- this._updated_avatar = "file:///tmp/avatar-02";
+ this._photo_urn = "<" + this._initial_avatar_uri + ">";
+ this._updated_avatar_uri = "file:///tmp/avatar-02";
+ this._updated_avatar =
+ new FileIcon (File.new_for_uri (this._updated_avatar_uri));
c1.set (TrackerTest.Backend.URN, this._contact_urn);
c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._initial_fullname);
- c1.set (Trf.OntologyDefs.NCO_PHOTO, this._initial_avatar);
+ c1.set (Trf.OntologyDefs.NCO_PHOTO, this._initial_avatar_uri);
this._tracker_backend.add_contact (c1);
this._tracker_backend.set_up ();
@@ -123,20 +126,22 @@ public class AvatarUpdatesTests : Folks.TestCase
i.notify["avatar"].connect (this._notify_avatar_cb);
this._individual_id = i.id;
- if (i.avatar != null &&
- i.avatar.get_uri () == this._initial_avatar)
+ var initial_avatar =
+ new FileIcon (File.new_for_uri (this._initial_avatar_uri));
+
+ if (i.avatar != null && i.avatar.equal (initial_avatar) == true)
{
this._initial_avatar_found = true;
this._tracker_backend.remove_triplet (this._contact_urn,
Trf.OntologyDefs.NCO_PHOTO, this._photo_urn);
- string photo_urn_2 = "<" + this._updated_avatar;
+ string photo_urn_2 = "<" + this._updated_avatar_uri;
photo_urn_2 += ">";
this._tracker_backend.insert_triplet (photo_urn_2,
"a", "nfo:Image, nie:DataObject",
Trf.OntologyDefs.NIE_URL,
- this._updated_avatar);
+ this._updated_avatar_uri);
this._tracker_backend.insert_triplet
(this._contact_urn,
@@ -156,7 +161,7 @@ public class AvatarUpdatesTests : Folks.TestCase
return;
if (i.avatar != null &&
- i.avatar.get_uri () == this._updated_avatar)
+ i.avatar.equal (this._updated_avatar))
{
this._main_loop.quit ();
this._updated_avatar_found = true;
diff --git a/tests/tracker/set-avatar.vala b/tests/tracker/set-avatar.vala
index 533c2ca..0bd9b4d 100644
--- a/tests/tracker/set-avatar.vala
+++ b/tests/tracker/set-avatar.vala
@@ -30,7 +30,7 @@ public class SetAvatarTests : Folks.TestCase
private IndividualAggregator _aggregator;
private string _persona_fullname;
private string _avatar_uri;
- private File _avatar;
+ private LoadableIcon _avatar;
private bool _avatar_found;
public SetAvatarTests ()
@@ -56,7 +56,7 @@ public class SetAvatarTests : Folks.TestCase
Gee.HashMap<string, string> c1 = new Gee.HashMap<string, string> ();
this._persona_fullname = "persona #1";
this._avatar_uri = "file:///tmp/some-avatar.jpg";
- this._avatar = File.new_for_uri (this._avatar_uri);
+ this._avatar = new FileIcon (File.new_for_uri (this._avatar_uri));
c1.set (Trf.OntologyDefs.NCO_FULLNAME, this._persona_fullname);
this._tracker_backend.add_contact (c1);
@@ -125,7 +125,7 @@ public class SetAvatarTests : Folks.TestCase
Folks.Individual i = (Folks.Individual) individual_obj;
if (i.full_name == this._persona_fullname)
{
- if (i.avatar.get_uri () == this._avatar_uri)
+ if (i.avatar.equal (this._avatar))
{
this._avatar_found = true;
this._main_loop.quit ();
diff --git a/tools/inspect/utils.vala b/tools/inspect/utils.vala
index ef1e5a5..a190fa9 100644
--- a/tools/inspect/utils.vala
+++ b/tools/inspect/utils.vala
@@ -264,9 +264,19 @@ private class Folks.Inspect.Utils
else if (prop_name == "avatar")
{
string ret = null;
- File avatar = (File) prop_value.get_object ();
- if (avatar != null)
- ret = avatar.get_uri ();
+ LoadableIcon? avatar = (LoadableIcon) prop_value.get_object ();
+
+ if (avatar != null &&
+ avatar is FileIcon && ((FileIcon) avatar).get_file () != null)
+ {
+ ret = "%p (file: %s)".printf (avatar,
+ ((FileIcon) avatar).get_file ().get_uri ());
+ }
+ else if (avatar != null)
+ {
+ ret = "%p".printf (avatar);
+ }
+
return ret;
}
else if (prop_name == "im-addresses")
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]