[folks] Bug 652643 — Add PersonaStore cache
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [folks] Bug 652643 — Add PersonaStore cache
- Date: Wed, 27 Jul 2011 22:53:14 +0000 (UTC)
commit 5b90ebe8fde8229f30f56ea78feff17cea70ed34
Author: Philip Withnall <philip tecnocode co uk>
Date: Thu Jun 16 18:35:52 2011 +0100
Bug 652643 â Add PersonaStore cache
Use ObjectCache in Tpf.PersonaStore.
Closes: bgo#652643
NEWS | 1 +
backends/telepathy/lib/Makefile.am | 1 +
.../telepathy/lib/tpf-persona-store-cache.vala | 154 ++++++++++++++++++++
backends/telepathy/lib/tpf-persona-store.vala | 121 +++++++++++++++-
backends/telepathy/lib/tpf-persona.vala | 66 ++++++++-
5 files changed, 335 insertions(+), 8 deletions(-)
---
diff --git a/NEWS b/NEWS
index 9c05d18..1050242 100644
--- a/NEWS
+++ b/NEWS
@@ -22,6 +22,7 @@ Bugs fixed:
* 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
+* Bug 652643 â Add PersonaStore cache
API changes:
* Swf.Persona retains and exposes its libsocialweb Contact
diff --git a/backends/telepathy/lib/Makefile.am b/backends/telepathy/lib/Makefile.am
index 167fba3..7d3cd01 100644
--- a/backends/telepathy/lib/Makefile.am
+++ b/backends/telepathy/lib/Makefile.am
@@ -96,6 +96,7 @@ folks_telepathy_valasources = \
tpf-persona.vala \
tpf-persona-store.vala \
tpf-logger.vala \
+ tpf-persona-store-cache.vala \
$(NULL)
libfolks_telepathy_la_SOURCES = \
diff --git a/backends/telepathy/lib/tpf-persona-store-cache.vala b/backends/telepathy/lib/tpf-persona-store-cache.vala
new file mode 100644
index 0000000..d2d52e1
--- /dev/null
+++ b/backends/telepathy/lib/tpf-persona-store-cache.vala
@@ -0,0 +1,154 @@
+/*
+ * 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;
+using Gee;
+using Folks;
+
+/**
+ * An object cache class which implements caching of sets of
+ * { link Tpf.Persona}s from a given { link Tpf.PersonaStore}.
+ *
+ * Each { link Tpf.Persona} is stored as a serialised { link Variant} which is
+ * a tuple containing the following fields:
+ * # UID (`s`)
+ * # IID (`s`)
+ * # IM address (`s`)
+ * # Protocol (`s`)
+ * # Set of group names (`as`)
+ * # Favourite? (`b`)
+ * # Alias (`s`)
+ * # In contact list? (`b`)
+ * # Avatar file URI (`s`)
+ *
+ * @since UNRELEASED
+ */
+internal class Tpf.PersonaStoreCache : Folks.ObjectCache<Tpf.Persona>
+{
+ private weak PersonaStore _store;
+
+ /* Version number of the variant type returned by
+ * get_serialised_object_type(). This must be modified whenever that variant
+ * type or its semantics are changed, since that would necessitate a cache
+ * refresh. */
+ private static const uint8 _FILE_FORMAT_VERSION = 1;
+
+ internal PersonaStoreCache (PersonaStore store)
+ {
+ base ("tpf-persona-stores", store.id);
+
+ this._store = store;
+ }
+
+ protected override VariantType get_serialised_object_type ()
+ {
+ return new VariantType.tuple ({
+ VariantType.STRING, // UID
+ VariantType.STRING, // IID
+ VariantType.STRING, // ID
+ VariantType.STRING, // Protocol
+ new VariantType.array (VariantType.STRING), // Groups
+ VariantType.BOOLEAN, // Favourite?
+ VariantType.STRING, // Alias
+ VariantType.BOOLEAN, // In contact list?
+ VariantType.BOOLEAN, // Is user?
+ new VariantType.maybe (VariantType.STRING) // Avatar
+ });
+ }
+
+ protected override uint8 get_serialised_object_version ()
+ {
+ return this._FILE_FORMAT_VERSION;
+ }
+
+ protected override Variant serialise_object (Tpf.Persona persona)
+ {
+ // Sort out the groups
+ Variant[] groups = new Variant[persona.groups.size];
+
+ uint i = 0;
+ foreach (var group in persona.groups)
+ {
+ groups[i++] = new Variant.string (group);
+ }
+
+ // Sort out the IM addresses (there's guaranteed to only be one)
+ string? im_protocol = null;
+
+ foreach (var protocol in persona.im_addresses.get_keys ())
+ {
+ im_protocol = protocol;
+ break;
+ }
+
+ // Avatar
+ var avatar_file = (persona.avatar != null && persona.avatar is FileIcon) ?
+ (persona.avatar as FileIcon).get_file () : null;
+ var avatar_variant = (avatar_file != null) ?
+ new Variant.string (avatar_file.get_uri ()) : null;
+
+ // Serialise the persona
+ return new Variant.tuple ({
+ new Variant.string (persona.uid),
+ new Variant.string (persona.iid),
+ new Variant.string (persona.display_id),
+ new Variant.string (im_protocol),
+ new Variant.array (VariantType.STRING, groups),
+ new Variant.boolean (persona.is_favourite),
+ new Variant.string (persona.alias),
+ new Variant.boolean (persona.is_in_contact_list),
+ new Variant.boolean (persona.is_user),
+ new Variant.maybe (VariantType.STRING, avatar_variant)
+ });
+ }
+
+ protected override Tpf.Persona deserialise_object (Variant variant)
+ {
+ // Deserialise the persona
+ var uid = variant.get_child_value (0).get_string ();
+ var iid = variant.get_child_value (1).get_string ();
+ var display_id = variant.get_child_value (2).get_string ();
+ var im_protocol = variant.get_child_value (3).get_string ();
+ var groups = variant.get_child_value (4);
+ var is_favourite = variant.get_child_value (5).get_boolean ();
+ var alias = variant.get_child_value (6).get_string ();
+ var is_in_contact_list = variant.get_child_value (7).get_boolean ();
+ var is_user = variant.get_child_value (8).get_boolean ();
+ var avatar_variant = variant.get_child_value (9).get_maybe ();
+
+ // Deserialise the groups
+ var group_set = new HashSet<string> ();
+ for (uint i = 0; i < groups.n_children (); i++)
+ {
+ group_set.add (groups.get_child_value (i).get_string ());
+ }
+
+ // Deserialise the avatar
+ var avatar = (avatar_variant != null) ?
+ new FileIcon (File.new_for_uri (avatar_variant.get_string ())) :
+ null;
+
+ return new Tpf.Persona.from_cache (this._store, uid, iid, display_id,
+ im_protocol, group_set, is_favourite, alias, is_in_contact_list,
+ is_user, avatar);
+ }
+}
+
+/* vim: filetype=vala textwidth=80 tabstop=2 expandtab: */
diff --git a/backends/telepathy/lib/tpf-persona-store.vala b/backends/telepathy/lib/tpf-persona-store.vala
index fd5683f..b031c21 100644
--- a/backends/telepathy/lib/tpf-persona-store.vala
+++ b/backends/telepathy/lib/tpf-persona-store.vala
@@ -89,6 +89,9 @@ public class Tpf.PersonaStore : Folks.PersonaStore
private MaybeBool _can_remove_personas = MaybeBool.UNSET;
private bool _is_prepared = false;
private Debug _debug;
+ private PersonaStoreCache _cache;
+ private Cancellable? _load_cache_cancellable = null;
+ private bool _cached = false;
internal signal void group_members_changed (string group,
GLib.List<Persona>? added, GLib.List<Persona>? removed);
@@ -195,6 +198,9 @@ public class Tpf.PersonaStore : Folks.PersonaStore
this._debug = Debug.dup ();
this._debug.print_status.connect (this._debug_print_status);
+ // Set up the cache
+ this._cache = new PersonaStoreCache (this);
+
this._reset ();
}
@@ -498,8 +504,12 @@ public class Tpf.PersonaStore : Folks.PersonaStore
{
if (this.account == a)
{
- this._emit_personas_changed (null, this._persona_set);
- this.removed ();
+ this._store_cache.begin ((o, r) =>
+ {
+ this._store_cache.end (r);
+ this._emit_personas_changed (null, this._persona_set);
+ this.removed ();
+ });
}
});
this._account_manager.account_removed.connect ((a) =>
@@ -507,6 +517,7 @@ public class Tpf.PersonaStore : Folks.PersonaStore
if (this.account == a)
{
this._emit_personas_changed (null, this._persona_set);
+ this._cache.clear_cache ();
this.removed ();
}
});
@@ -516,6 +527,7 @@ public class Tpf.PersonaStore : Folks.PersonaStore
if (!valid && this.account == a)
{
this._emit_personas_changed (null, this._persona_set);
+ this._cache.clear_cache ();
this.removed ();
}
});
@@ -533,6 +545,12 @@ public class Tpf.PersonaStore : Folks.PersonaStore
TelepathyGLib.ConnectionStatus.DISCONNECTED, status,
reason, null, null);
}
+ else
+ {
+ /* If we're disconnected, advertise personas from the cache
+ * instead. */
+ yield this._load_cache ();
+ }
try
{
@@ -713,14 +731,32 @@ public class Tpf.PersonaStore : Folks.PersonaStore
{
/* When disconnecting, we want the PersonaStore to remain alive, but
* all its Personas to be removed. We do *not* want the PersonaStore
- * to be destroyed, as that makes coming back online hard. */
- this._emit_personas_changed (null, this._persona_set);
- this._reset ();
+ * to be destroyed, as that makes coming back online hard.
+ *
+ * We have to start advertising personas from the cache instead.
+ * This will implicitly notify about removal of the existing persona
+ * set and call this._reset().
+ *
+ * Before we do this, we store the current set of personas to the
+ * cache. */
+ this._store_cache.begin ((o, r) =>
+ {
+ this._store_cache.end (r);
+
+ this._load_cache.begin ((o2, r2) =>
+ {
+ this._load_cache.end (r2);
+ });
+ });
+
return;
}
else if (new_status != TelepathyGLib.ConnectionStatus.CONNECTED)
return;
+ // We're connected, so can stop advertising personas from the cache
+ this._unload_cache ();
+
var conn = this.account.connection;
conn.notify["connection-ready"].connect (this._connection_ready_cb);
@@ -836,6 +872,81 @@ public class Tpf.PersonaStore : Folks.PersonaStore
this._initialise_favourite_contacts.begin ();
}
+ /**
+ * If our account is disconnected, we want to continue to export a static
+ * view of personas from the cache.
+ */
+ private async void _load_cache ()
+ {
+ var cancellable = new Cancellable ();
+
+ if (this._load_cache_cancellable != null)
+ {
+ this._load_cache_cancellable.cancel ();
+ }
+
+ this._load_cache_cancellable = cancellable;
+
+ // Load the persona set from the cache and notify of the change
+ var cached_personas = yield this._cache.load_objects (cancellable);
+ var old_personas = this._persona_set;
+
+ /* If the load operation was cancelled, don't change the state
+ * of the persona store at all. */
+ if (cancellable.is_cancelled () == true)
+ {
+ return;
+ }
+
+ this._reset ();
+ this._cached = true;
+
+ this._persona_set = new HashSet<Persona> ();
+ if (cached_personas != null)
+ {
+ foreach (var p in cached_personas)
+ {
+ this._persona_set.add (p);
+ }
+ }
+
+ this._emit_personas_changed (cached_personas, old_personas,
+ null, null, GroupDetails.ChangeReason.NONE);
+
+ this._can_add_personas = MaybeBool.FALSE;
+ this._can_alias_personas = MaybeBool.FALSE;
+ this._can_group_personas = MaybeBool.FALSE;
+ this._can_remove_personas = MaybeBool.FALSE;
+ }
+
+ /**
+ * When we're about to disconnect, store the current set of personas to the
+ * cache file so that we can access them once offline.
+ */
+ private async void _store_cache ()
+ {
+ yield this._cache.store_objects (this._persona_set);
+ }
+
+ /**
+ * When our account is connected again, we can unload the the personas which
+ * we're advertising from the cache.
+ */
+ private void _unload_cache ()
+ {
+ // If we're in the process of loading from the cache, cancel that
+ if (this._load_cache_cancellable != null)
+ {
+ this._load_cache_cancellable.cancel ();
+ }
+
+ this._emit_personas_changed (null, this._persona_set, null, null,
+ GroupDetails.ChangeReason.NONE);
+
+ this._reset ();
+ this._cached = false;
+ }
+
private void _self_handle_changed_cb (Object s, ParamSpec? p)
{
var c = (Connection) s;
diff --git a/backends/telepathy/lib/tpf-persona.vala b/backends/telepathy/lib/tpf-persona.vala
index 18a06d9..5566379 100644
--- a/backends/telepathy/lib/tpf-persona.vala
+++ b/backends/telepathy/lib/tpf-persona.vala
@@ -236,8 +236,14 @@ public class Tpf.Persona : Folks.Persona,
/**
* The Telepathy contact represented by this persona.
+ *
+ * Note that this may be `null` if the { link PersonaStore} providing this
+ * { link Persona} isn't currently available (e.g. due to not being connected
+ * to the network). In this case, most other properties of the { link Persona}
+ * are being retrieved from a cache and may not be current (though there's no
+ * way to tell this).
*/
- public Contact contact { get; construct; }
+ public Contact? contact { get; construct; }
/**
* Create a new persona.
@@ -341,12 +347,66 @@ public class Tpf.Persona : Folks.Persona,
error.code != TelepathyGLib.DBusError.OBJECT_REMOVED))
{
debug ("Group invalidated: %s", error.message);
+ this._change_group (group, false);
}
-
- this._change_group (group, false);
});
}
+ /**
+ * Create a new persona for the { link PersonaStore} `store`, representing
+ * a cached contact for which we currently have no Telepathy contact.
+ *
+ * @param store The persona store to place the persona in.
+ * @param uid The cached UID of the persona.
+ * @param iid The cached IID of the persona.
+ * @param im_address The cached IM address of the persona (excluding
+ * protocol).
+ * @param protocol The cached protocol of the persona.
+ * @param groups The cached set of groups the persona is in.
+ * @param is_favourite Whether the persona is a favourite.
+ * @param alias The cached alias for the persona.
+ * @param is_in_contact_list Whether the persona is in the user's contact
+ * list.
+ * @param is_user Whether the persona is the user.
+ * @param avatar The icon for the persona's cached avatar, or `null` if they
+ * have no avatar.
+ * @return A new { link Tpf.Persona} representing the cached persona.
+ *
+ * @since 0.5.UNRELEASED
+ */
+ internal Persona.from_cache (PersonaStore store, string uid, string iid,
+ string im_address, string protocol, HashSet<string> groups,
+ bool is_favourite, string alias, bool is_in_contact_list, bool is_user,
+ LoadableIcon? avatar)
+ {
+ Object (contact: null,
+ display_id: im_address,
+ iid: iid,
+ uid: uid,
+ store: store,
+ is_user: is_user);
+
+ debug ("Creating new Tpf.Persona '%s' from cache: %p", uid, this);
+
+ // IM addresses
+ this._im_addresses = new HashMultiMap<string, string> ();
+ this._im_addresses.set (protocol, im_address);
+
+ // Groups
+ this._groups = groups;
+ this._groups_ro = this._groups.read_only_view;
+
+ // Other properties
+ this._alias = alias;
+ this._is_favourite = is_favourite;
+ this.is_in_contact_list = is_in_contact_list;
+ this.avatar = avatar;
+
+ // Make the persona appear offline
+ this.presence_type = PresenceType.OFFLINE;
+ this.presence_message = "";
+ }
+
~Persona ()
{
debug ("Destroying Tpf.Persona '%s': %p", this.uid, this);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]