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
This commit is contained in:
parent
7d7ed7ce7a
commit
e83656969e
@ -26,6 +26,25 @@ function pathToName(path) {
|
|||||||
return path.substr(1).replace('/', '.', 'g');
|
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
|
// Telepathy D-Bus interface definitions. Note that most of these are
|
||||||
// incomplete, and only cover the methods/properties/signals that
|
// incomplete, and only cover the methods/properties/signals that
|
||||||
// we're currently using.
|
// we're currently using.
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const DBus = imports.dbus;
|
const DBus = imports.dbus;
|
||||||
|
const GLib = imports.gi.GLib;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
@ -181,20 +182,32 @@ function AvatarManager() {
|
|||||||
AvatarManager.prototype = {
|
AvatarManager.prototype = {
|
||||||
_init: function() {
|
_init: function() {
|
||||||
this._connections = {};
|
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) {
|
_addConnection: function(conn) {
|
||||||
if (this._connections[conn.getPath()])
|
|
||||||
return this._connections[conn.getPath()];
|
|
||||||
|
|
||||||
let info = {};
|
let info = {};
|
||||||
|
|
||||||
// avatarData[handle] describes the icon for @handle:
|
// Figure out the cache subdirectory for this connection by
|
||||||
// either the string 'default', meaning to use the default
|
// parsing the connection manager name (eg, 'gabble') and
|
||||||
// avatar, or an array of bytes containing, eg, PNG data.
|
// protocol name (eg, 'jabber') from the Connection's path.
|
||||||
info.avatarData = {};
|
// 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());
|
||||||
|
|
||||||
// icons[handle] is an array of the icon actors currently
|
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 = {};
|
||||||
|
|
||||||
|
// info.icons[handle] is an array of the icon actors currently
|
||||||
// being displayed for @handle. These will be updated
|
// being displayed for @handle. These will be updated
|
||||||
// automatically if @handle's avatar changes.
|
// automatically if @handle's avatar changes.
|
||||||
info.icons = {};
|
info.icons = {};
|
||||||
@ -229,39 +242,57 @@ AvatarManager.prototype = {
|
|||||||
delete this._connections[conn.getPath()];
|
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) {
|
_avatarUpdated: function(conn, handle, token) {
|
||||||
let info = this._connections[conn.getPath()];
|
let info = this._connections[conn.getPath()];
|
||||||
if (!info)
|
if (!info)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!info.avatarData[handle]) {
|
if (info.token[handle] == token)
|
||||||
// This would only happen if either (a) the initial
|
return;
|
||||||
// RequestAvatars() call hasn't returned yet, or (b)
|
|
||||||
// Telepathy is informing us about avatars we didn't ask
|
info.token[handle] = token;
|
||||||
// about. Either way, we don't have to do anything here.
|
if (token != '') {
|
||||||
|
let file = this._getFileForToken(info, token);
|
||||||
|
if (!GLib.file_test(file, GLib.FileTest.EXISTS)) {
|
||||||
|
info.connectionAvatars.RequestAvatarsRemote([handle]);
|
||||||
return;
|
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]);
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
_createIcon: function(iconData, size) {
|
this._updateIcons(info, handle);
|
||||||
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);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_avatarRetrieved: function(conn, handle, token, avatarData, mimeType) {
|
_avatarRetrieved: function(conn, handle, token, avatarData, mimeType) {
|
||||||
@ -269,19 +300,21 @@ AvatarManager.prototype = {
|
|||||||
if (!info)
|
if (!info)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
info.avatarData[handle] = avatarData;
|
let file = this._getFileForToken(info, token);
|
||||||
if (!info.icons[handle])
|
let success = false;
|
||||||
return;
|
try {
|
||||||
|
success = GLib.file_set_contents(file, avatarData, avatarData.length);
|
||||||
for (let i = 0; i < info.icons[handle].length; i++) {
|
} catch (e) {
|
||||||
let iconBox = info.icons[handle][i];
|
logError(e, 'Error caching avatar data');
|
||||||
let size = iconBox.child.height;
|
|
||||||
iconBox.child = this._createIcon(avatarData, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
this._updateIcons(info, handle);
|
||||||
},
|
},
|
||||||
|
|
||||||
createAvatar: function(conn, handle, size) {
|
createAvatar: function(conn, handle, size) {
|
||||||
let iconBox = new St.Bin({ style_class: 'avatar-box' });
|
let iconBox = new St.Bin({ style_class: 'avatar-box' });
|
||||||
|
iconBox._size = size;
|
||||||
|
|
||||||
let info = this._connections[conn.getPath()];
|
let info = this._connections[conn.getPath()];
|
||||||
if (!info)
|
if (!info)
|
||||||
@ -298,22 +331,19 @@ AvatarManager.prototype = {
|
|||||||
info.icons[handle].splice(i, 1);
|
info.icons[handle].splice(i, 1);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let avatarData = info.avatarData[handle];
|
// If we already have the icon cached and know its token, this
|
||||||
if (avatarData) {
|
// will fill it in. Otherwise it will fill in the default
|
||||||
iconBox.child = this._createIcon(avatarData, size);
|
// icon.
|
||||||
return iconBox;
|
this._setIcon(iconBox, info, handle);
|
||||||
}
|
|
||||||
|
|
||||||
// Fill in the default icon and then asynchronously load
|
// Asynchronously load the real avatar if we don't have it yet.
|
||||||
// the real avatar.
|
if (info.token[handle] == null) {
|
||||||
iconBox.child = this._createIcon('default', size);
|
|
||||||
info.connectionAvatars.GetKnownAvatarTokensRemote([handle], Lang.bind(this,
|
info.connectionAvatars.GetKnownAvatarTokensRemote([handle], Lang.bind(this,
|
||||||
function (tokens, err) {
|
function (tokens, err) {
|
||||||
if (tokens && tokens[handle])
|
let token = tokens && tokens[handle] ? tokens[handle] : '';
|
||||||
info.connectionAvatars.RequestAvatarsRemote([handle]);
|
this._avatarUpdated(conn, handle, token);
|
||||||
else
|
|
||||||
info.avatarData[handle] = 'default';
|
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
return iconBox;
|
return iconBox;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user