[gnome-shell] telepathyClient: cache avatar images
- From: Dan Winship <danw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] telepathyClient: cache avatar images
- Date: Thu, 29 Apr 2010 18:04:33 +0000 (UTC)
commit e83656969e6431b1bd6fb1c57742d6623da9505f
Author: Dan Winship <danw gnome org>
Date: Wed Apr 14 10:24:14 2010 -0400
telepathyClient: cache avatar images
Cache avatars to avoid having to re-download them every session.
We use a cache format that is compatible with empathy's, but we don't
actually use empathy's. This could be changed if we wanted.
https://bugzilla.gnome.org/show_bug.cgi?id=614974
js/misc/telepathy.js | 19 ++++++
js/ui/telepathyClient.js | 140 ++++++++++++++++++++++++++++------------------
2 files changed, 104 insertions(+), 55 deletions(-)
---
diff --git a/js/misc/telepathy.js b/js/misc/telepathy.js
index fb809d7..9802c69 100644
--- a/js/misc/telepathy.js
+++ b/js/misc/telepathy.js
@@ -26,6 +26,25 @@ function pathToName(path) {
return path.substr(1).replace('/', '.', 'g');
};
+// This is tp_escape_as_identifier() from telepathy-glib
+function escapeAsIdentifier(name) {
+ if (!name)
+ return '_';
+
+ // first char is replaced with _XX if it's non-alpha,
+ // later chars are replaced with _XX if they're non-alphanumeric
+ if (name.length == 1) {
+ return name.replace(/[^a-zA-Z]/, _hexEscape);
+ } else {
+ return (name[0].replace(/[^a-zA-Z]/, _hexEscape) +
+ name.substring(1).replace(/[^a-zA-Z0-9]/g, _hexEscape));
+ }
+}
+
+function _hexEscape(ch) {
+ return '_' + ch.charCodeAt(0).toString(16);
+}
+
// Telepathy D-Bus interface definitions. Note that most of these are
// incomplete, and only cover the methods/properties/signals that
// we're currently using.
diff --git a/js/ui/telepathyClient.js b/js/ui/telepathyClient.js
index 1c66ff1..1bb6123 100644
--- a/js/ui/telepathyClient.js
+++ b/js/ui/telepathyClient.js
@@ -2,6 +2,7 @@
const Clutter = imports.gi.Clutter;
const DBus = imports.dbus;
+const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
@@ -181,20 +182,32 @@ function AvatarManager() {
AvatarManager.prototype = {
_init: function() {
this._connections = {};
+ // Note that if we changed this to '/telepathy/avatars' then
+ // we would share cache files with empathy. But since this is
+ // not documented/guaranteed, it seems a little sketchy
+ this._cacheDir = GLib.get_user_cache_dir() + '/gnome-shell/avatars';
},
_addConnection: function(conn) {
- if (this._connections[conn.getPath()])
- return this._connections[conn.getPath()];
-
let info = {};
- // avatarData[handle] describes the icon for @handle:
- // either the string 'default', meaning to use the default
- // avatar, or an array of bytes containing, eg, PNG data.
- info.avatarData = {};
+ // Figure out the cache subdirectory for this connection by
+ // parsing the connection manager name (eg, 'gabble') and
+ // protocol name (eg, 'jabber') from the Connection's path.
+ // Telepathy requires the D-Bus path for a connection to have
+ // a specific form, and explicitly says that clients are
+ // allowed to parse it.
+ let match = conn.getPath().match(/\/org\/freedesktop\/Telepathy\/Connection\/([^\/]*\/[^\/]*)\/.*/);
+ if (!match)
+ throw new Error('Could not parse connection path ' + conn.getPath());
+
+ info.cacheDir = this._cacheDir + '/' + match[1];
+ GLib.mkdir_with_parents(info.cacheDir, 0700);
+
+ // info.token[handle] is the token for @handle's avatar
+ info.token = {};
- // icons[handle] is an array of the icon actors currently
+ // info.icons[handle] is an array of the icon actors currently
// being displayed for @handle. These will be updated
// automatically if @handle's avatar changes.
info.icons = {};
@@ -229,39 +242,57 @@ AvatarManager.prototype = {
delete this._connections[conn.getPath()];
},
+ _getFileForToken: function(info, token) {
+ return info.cacheDir + '/' + Telepathy.escapeAsIdentifier(token);
+ },
+
+ _setIcon: function(iconBox, info, handle) {
+ let textureCache = St.TextureCache.get_default();
+ let token = info.token[handle];
+ let file;
+
+ if (token) {
+ file = this._getFileForToken(info, token);
+ if (!GLib.file_test(file, GLib.FileTest.EXISTS))
+ file = null;
+ }
+
+ if (file) {
+ let uri = GLib.filename_to_uri(file, null);
+ iconBox.child = textureCache.load_uri_async(uri, iconBox._size, iconBox._size);
+ } else {
+ iconBox.child = textureCache.load_icon_name('stock_person', iconBox._size);
+ }
+ },
+
+ _updateIcons: function(info, handle) {
+ if (!info.icons[handle])
+ return;
+
+ for (let i = 0; i < info.icons[handle].length; i++) {
+ let iconBox = info.icons[handle][i];
+ this._setIcon(iconBox, info, handle);
+ }
+ },
+
_avatarUpdated: function(conn, handle, token) {
let info = this._connections[conn.getPath()];
if (!info)
return;
- if (!info.avatarData[handle]) {
- // This would only happen if either (a) the initial
- // RequestAvatars() call hasn't returned yet, or (b)
- // Telepathy is informing us about avatars we didn't ask
- // about. Either way, we don't have to do anything here.
+ if (info.token[handle] == token)
return;
- }
- if (token == '') {
- // Invoke the next async callback in the chain, telling
- // it to use the default image.
- this._avatarRetrieved(conn, handle, token, 'default', null);
- } else {
- // In this case, @token is some sort of UUID. Telepathy
- // expects us to cache avatar images to disk and use the
- // tokens to figure out when we already have the right
- // images cached. But we don't do that, we just
- // ignore @token and request the image unconditionally.
- info.connectionAvatars.RequestAvatarsRemote([handle]);
+ info.token[handle] = token;
+ if (token != '') {
+ let file = this._getFileForToken(info, token);
+ if (!GLib.file_test(file, GLib.FileTest.EXISTS)) {
+ info.connectionAvatars.RequestAvatarsRemote([handle]);
+ return;
+ }
}
- },
- _createIcon: function(iconData, size) {
- let textureCache = St.TextureCache.get_default();
- if (iconData == 'default')
- return textureCache.load_icon_name('stock_person', size);
- else
- return textureCache.load_from_data(iconData, iconData.length, size);
+ this._updateIcons(info, handle);
},
_avatarRetrieved: function(conn, handle, token, avatarData, mimeType) {
@@ -269,19 +300,21 @@ AvatarManager.prototype = {
if (!info)
return;
- info.avatarData[handle] = avatarData;
- if (!info.icons[handle])
- return;
-
- for (let i = 0; i < info.icons[handle].length; i++) {
- let iconBox = info.icons[handle][i];
- let size = iconBox.child.height;
- iconBox.child = this._createIcon(avatarData, size);
+ let file = this._getFileForToken(info, token);
+ let success = false;
+ try {
+ success = GLib.file_set_contents(file, avatarData, avatarData.length);
+ } catch (e) {
+ logError(e, 'Error caching avatar data');
}
+
+ if (success)
+ this._updateIcons(info, handle);
},
createAvatar: function(conn, handle, size) {
let iconBox = new St.Bin({ style_class: 'avatar-box' });
+ iconBox._size = size;
let info = this._connections[conn.getPath()];
if (!info)
@@ -298,23 +331,20 @@ AvatarManager.prototype = {
info.icons[handle].splice(i, 1);
}));
- let avatarData = info.avatarData[handle];
- if (avatarData) {
- iconBox.child = this._createIcon(avatarData, size);
- return iconBox;
+ // If we already have the icon cached and know its token, this
+ // will fill it in. Otherwise it will fill in the default
+ // icon.
+ this._setIcon(iconBox, info, handle);
+
+ // Asynchronously load the real avatar if we don't have it yet.
+ if (info.token[handle] == null) {
+ info.connectionAvatars.GetKnownAvatarTokensRemote([handle], Lang.bind(this,
+ function (tokens, err) {
+ let token = tokens && tokens[handle] ? tokens[handle] : '';
+ this._avatarUpdated(conn, handle, token);
+ }));
}
- // Fill in the default icon and then asynchronously load
- // the real avatar.
- iconBox.child = this._createIcon('default', size);
- info.connectionAvatars.GetKnownAvatarTokensRemote([handle], Lang.bind(this,
- function (tokens, err) {
- if (tokens && tokens[handle])
- info.connectionAvatars.RequestAvatarsRemote([handle]);
- else
- info.avatarData[handle] = 'default';
- }));
-
return iconBox;
}
};
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]