Fetch avatars only when they are used
This commit is contained in:
parent
daf803e773
commit
50c55c7f55
|
@ -25,7 +25,6 @@ SOURCES
|
||||||
src/plugin/registry.vala
|
src/plugin/registry.vala
|
||||||
|
|
||||||
src/service/avatar_manager.vala
|
src/service/avatar_manager.vala
|
||||||
src/service/avatar_storage.vala
|
|
||||||
src/service/blocking_manager.vala
|
src/service/blocking_manager.vala
|
||||||
src/service/chat_interaction.vala
|
src/service/chat_interaction.vala
|
||||||
src/service/connection_manager.vala
|
src/service/connection_manager.vala
|
||||||
|
|
|
@ -11,7 +11,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
public static ModuleIdentity<AvatarManager> IDENTITY = new ModuleIdentity<AvatarManager>("avatar_manager");
|
public static ModuleIdentity<AvatarManager> IDENTITY = new ModuleIdentity<AvatarManager>("avatar_manager");
|
||||||
public string id { get { return IDENTITY.id; } }
|
public string id { get { return IDENTITY.id; } }
|
||||||
|
|
||||||
public signal void received_avatar(Pixbuf avatar, Jid jid, Account account);
|
public signal void received_avatar(Jid jid, Account account);
|
||||||
|
|
||||||
private enum Source {
|
private enum Source {
|
||||||
USER_AVATARS,
|
USER_AVATARS,
|
||||||
|
@ -20,9 +20,9 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
|
|
||||||
private StreamInteractor stream_interactor;
|
private StreamInteractor stream_interactor;
|
||||||
private Database db;
|
private Database db;
|
||||||
|
private string folder = null;
|
||||||
private HashMap<Jid, string> user_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
|
private HashMap<Jid, string> user_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
|
||||||
private HashMap<Jid, string> vcard_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
|
private HashMap<Jid, string> vcard_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
|
||||||
private AvatarStorage avatar_storage = new AvatarStorage(get_storage_dir());
|
|
||||||
private HashMap<string, Pixbuf> cached_pixbuf = new HashMap<string, Pixbuf>();
|
private HashMap<string, Pixbuf> cached_pixbuf = new HashMap<string, Pixbuf>();
|
||||||
private HashMap<string, Gee.List<SourceFuncWrapper>> pending_pixbuf = new HashMap<string, Gee.List<SourceFuncWrapper>>();
|
private HashMap<string, Gee.List<SourceFuncWrapper>> pending_pixbuf = new HashMap<string, Gee.List<SourceFuncWrapper>>();
|
||||||
private const int MAX_PIXEL = 192;
|
private const int MAX_PIXEL = 192;
|
||||||
|
@ -32,20 +32,17 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
stream_interactor.add_module(m);
|
stream_interactor.add_module(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string get_storage_dir() {
|
|
||||||
return Path.build_filename(Dino.get_storage_dir(), "avatars");
|
|
||||||
}
|
|
||||||
|
|
||||||
private AvatarManager(StreamInteractor stream_interactor, Database db) {
|
private AvatarManager(StreamInteractor stream_interactor, Database db) {
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
stream_interactor.account_added.connect(on_account_added);
|
this.folder = Path.build_filename(Dino.get_storage_dir(), "avatars");
|
||||||
stream_interactor.module_manager.initialize_account_modules.connect(initialize_avatar_modules);
|
DirUtils.create_with_parents(this.folder, 0700);
|
||||||
}
|
|
||||||
|
|
||||||
private void initialize_avatar_modules(Account account, ArrayList<XmppStreamModule> modules) {
|
stream_interactor.account_added.connect(on_account_added);
|
||||||
modules.add(new Xep.UserAvatars.Module(avatar_storage));
|
stream_interactor.module_manager.initialize_account_modules.connect((_, modules) => {
|
||||||
modules.add(new Xep.VCard.Module(avatar_storage));
|
modules.add(new Xep.UserAvatars.Module());
|
||||||
|
modules.add(new Xep.VCard.Module());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Pixbuf? get_avatar_by_hash(string hash) {
|
private async Pixbuf? get_avatar_by_hash(string hash) {
|
||||||
|
@ -58,7 +55,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
return cached_pixbuf[hash];
|
return cached_pixbuf[hash];
|
||||||
}
|
}
|
||||||
pending_pixbuf[hash] = new ArrayList<SourceFuncWrapper>();
|
pending_pixbuf[hash] = new ArrayList<SourceFuncWrapper>();
|
||||||
Pixbuf? image = yield avatar_storage.get_image(hash);
|
Pixbuf? image = yield get_image(hash);
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
cached_pixbuf[hash] = image;
|
cached_pixbuf[hash] = image;
|
||||||
} else {
|
} else {
|
||||||
|
@ -70,40 +67,61 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool has_avatar(Account account, Jid jid) {
|
public async Pixbuf? get_avatar(Account account, Jid jid_) {
|
||||||
string? hash = get_avatar_hash(account, jid);
|
Jid jid = jid_;
|
||||||
if (hash != null) {
|
if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) {
|
||||||
|
jid = jid_.bare_jid;
|
||||||
|
}
|
||||||
|
|
||||||
|
int source = -1;
|
||||||
|
string? hash = null;
|
||||||
|
if (user_avatars.has_key(jid)) {
|
||||||
|
hash = user_avatars[jid];
|
||||||
|
source = 1;
|
||||||
|
} else if (vcard_avatars.has_key(jid)) {
|
||||||
|
hash = vcard_avatars[jid];
|
||||||
|
source = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hash == null) return null;
|
||||||
|
|
||||||
if (cached_pixbuf.has_key(hash)) {
|
if (cached_pixbuf.has_key(hash)) {
|
||||||
return true;
|
return cached_pixbuf[hash];
|
||||||
}
|
|
||||||
return avatar_storage.has_image(hash);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Pixbuf? get_avatar(Account account, Jid jid) {
|
XmppStream? stream = stream_interactor.get_stream(account);
|
||||||
Jid jid_ = jid;
|
if (stream == null || !stream.negotiation_complete) return null;
|
||||||
if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid, account)) {
|
|
||||||
jid_ = jid.bare_jid;
|
if (pending_pixbuf.has_key(hash)) {
|
||||||
|
pending_pixbuf[hash].add(new SourceFuncWrapper(get_avatar.callback));
|
||||||
|
yield;
|
||||||
|
return cached_pixbuf[hash];
|
||||||
}
|
}
|
||||||
|
|
||||||
string? hash = get_avatar_hash(account, jid_);
|
pending_pixbuf[hash] = new ArrayList<SourceFuncWrapper>();
|
||||||
if (hash != null) {
|
Pixbuf? image = yield get_image(hash);
|
||||||
return yield get_avatar_by_hash(hash);
|
if (image != null) {
|
||||||
|
cached_pixbuf[hash] = image;
|
||||||
|
} else {
|
||||||
|
Bytes? bytes = null;
|
||||||
|
if (source == 1) {
|
||||||
|
bytes = yield Xmpp.Xep.UserAvatars.fetch_image(stream, jid, hash);
|
||||||
|
} else if (source == 2) {
|
||||||
|
bytes = yield Xmpp.Xep.VCard.fetch_image(stream, jid, hash);
|
||||||
|
if (bytes == null && jid.is_bare()) {
|
||||||
|
db.avatar.delete().with(db.avatar.jid_id, "=", db.get_jid_id(jid)).perform();
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
if (bytes != null) {
|
||||||
private string? get_avatar_hash(Account account, Jid jid) {
|
store_image(hash, bytes);
|
||||||
string? user_avatars_id = user_avatars[jid];
|
image = yield get_image(hash);
|
||||||
if (user_avatars_id != null) {
|
|
||||||
return user_avatars_id;
|
|
||||||
}
|
}
|
||||||
string? vcard_avatars_id = vcard_avatars[jid];
|
cached_pixbuf[hash] = image;
|
||||||
if (vcard_avatars_id != null) {
|
|
||||||
return vcard_avatars_id;
|
|
||||||
}
|
}
|
||||||
return null;
|
foreach (SourceFuncWrapper sfw in pending_pixbuf[hash]) {
|
||||||
|
sfw.sfun();
|
||||||
|
}
|
||||||
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void publish(Account account, string file) {
|
public void publish(Account account, string file) {
|
||||||
|
@ -120,7 +138,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
pixbuf.save_to_buffer(out buffer, "png");
|
pixbuf.save_to_buffer(out buffer, "png");
|
||||||
XmppStream stream = stream_interactor.get_stream(account);
|
XmppStream stream = stream_interactor.get_stream(account);
|
||||||
if (stream != null) {
|
if (stream != null) {
|
||||||
stream.get_module(Xep.UserAvatars.Module.IDENTITY).publish_png(stream, buffer, pixbuf.width, pixbuf.height);
|
Xmpp.Xep.UserAvatars.publish_png(stream, buffer, pixbuf.width, pixbuf.height);
|
||||||
}
|
}
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
warning(e.message);
|
warning(e.message);
|
||||||
|
@ -128,10 +146,10 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_account_added(Account account) {
|
private void on_account_added(Account account) {
|
||||||
stream_interactor.module_manager.get_module(account, Xep.UserAvatars.Module.IDENTITY).received_avatar.connect((stream, jid, id) =>
|
stream_interactor.module_manager.get_module(account, Xep.UserAvatars.Module.IDENTITY).received_avatar_hash.connect((stream, jid, id) =>
|
||||||
on_user_avatar_received.begin(account, jid, id)
|
on_user_avatar_received.begin(account, jid, id)
|
||||||
);
|
);
|
||||||
stream_interactor.module_manager.get_module(account, Xep.VCard.Module.IDENTITY).received_avatar.connect((stream, jid, id) =>
|
stream_interactor.module_manager.get_module(account, Xep.VCard.Module.IDENTITY).received_avatar_hash.connect((stream, jid, id) =>
|
||||||
on_vcard_avatar_received.begin(account, jid, id)
|
on_vcard_avatar_received.begin(account, jid, id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -139,32 +157,38 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
user_avatars[entry.key] = entry.value;
|
user_avatars[entry.key] = entry.value;
|
||||||
}
|
}
|
||||||
foreach (var entry in get_avatar_hashes(account, Source.VCARD).entries) {
|
foreach (var entry in get_avatar_hashes(account, Source.VCARD).entries) {
|
||||||
|
|
||||||
|
// FIXME: remove. temporary to remove falsely saved avatars.
|
||||||
|
if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(entry.key, account)) {
|
||||||
|
db.avatar.delete().with(db.avatar.jid_id, "=", db.get_jid_id(entry.key)).perform();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
vcard_avatars[entry.key] = entry.value;
|
vcard_avatars[entry.key] = entry.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void on_user_avatar_received(Account account, Jid jid, string id) {
|
private async void on_user_avatar_received(Account account, Jid jid_, string id) {
|
||||||
|
Jid jid = jid_.bare_jid;
|
||||||
|
|
||||||
if (!user_avatars.has_key(jid) || user_avatars[jid] != id) {
|
if (!user_avatars.has_key(jid) || user_avatars[jid] != id) {
|
||||||
user_avatars[jid] = id;
|
user_avatars[jid] = id;
|
||||||
set_avatar_hash(account, jid, id, Source.USER_AVATARS);
|
set_avatar_hash(account, jid, id, Source.USER_AVATARS);
|
||||||
}
|
}
|
||||||
Pixbuf? avatar = yield get_avatar_by_hash(id);
|
received_avatar(jid, account);
|
||||||
if (avatar != null) {
|
|
||||||
received_avatar(avatar, jid, account);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void on_vcard_avatar_received(Account account, Jid jid, string id) {
|
private async void on_vcard_avatar_received(Account account, Jid jid_, string id) {
|
||||||
|
bool is_gc = stream_interactor.get_module(MucManager.IDENTITY).might_be_groupchat(jid_.bare_jid, account);
|
||||||
|
Jid jid = is_gc ? jid_ : jid_.bare_jid;
|
||||||
|
|
||||||
if (!vcard_avatars.has_key(jid) || vcard_avatars[jid] != id) {
|
if (!vcard_avatars.has_key(jid) || vcard_avatars[jid] != id) {
|
||||||
vcard_avatars[jid] = id;
|
vcard_avatars[jid] = id;
|
||||||
if (!jid.is_full()) { // don't save MUC occupant avatars
|
if (jid.is_bare()) { // don't save MUC occupant avatars
|
||||||
set_avatar_hash(account, jid, id, Source.VCARD);
|
set_avatar_hash(account, jid, id, Source.VCARD);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Pixbuf? avatar = yield get_avatar_by_hash(id);
|
received_avatar(jid, account);
|
||||||
if (avatar != null) {
|
|
||||||
received_avatar(avatar, jid, account);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_avatar_hash(Account account, Jid jid, string hash, int type) {
|
public void set_avatar_hash(Account account, Jid jid, string hash, int type) {
|
||||||
|
@ -185,6 +209,45 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void store_image(string id, Bytes data) {
|
||||||
|
File file = File.new_for_path(Path.build_filename(folder, id));
|
||||||
|
try {
|
||||||
|
if (file.query_exists()) file.delete(); //TODO y?
|
||||||
|
DataOutputStream fos = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION));
|
||||||
|
fos.write_bytes_async.begin(data);
|
||||||
|
} catch (Error e) {
|
||||||
|
// Ignore: we failed in storing, so we refuse to display later...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool has_image(string id) {
|
||||||
|
File file = File.new_for_path(Path.build_filename(folder, id));
|
||||||
|
return file.query_exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Pixbuf? get_image(string id) {
|
||||||
|
try {
|
||||||
|
File file = File.new_for_path(Path.build_filename(folder, id));
|
||||||
|
FileInputStream stream = yield file.read_async();
|
||||||
|
|
||||||
|
uint8 fbuf[1024];
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
Checksum checksum = new Checksum (ChecksumType.SHA1);
|
||||||
|
while ((size = yield stream.read_async(fbuf)) > 0) {
|
||||||
|
checksum.update(fbuf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checksum.get_string() != id) {
|
||||||
|
FileUtils.remove(file.get_path());
|
||||||
|
}
|
||||||
|
stream.seek(0, SeekType.SET);
|
||||||
|
return yield new Pixbuf.from_stream_async(stream, null);
|
||||||
|
} catch (Error e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
using Gdk;
|
|
||||||
|
|
||||||
using Xmpp;
|
|
||||||
|
|
||||||
namespace Dino {
|
|
||||||
public class AvatarStorage : Xep.PixbufStorage, Object {
|
|
||||||
|
|
||||||
string folder;
|
|
||||||
|
|
||||||
public AvatarStorage(string folder) {
|
|
||||||
this.folder = folder;
|
|
||||||
DirUtils.create_with_parents(folder, 0700);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void store(string id, Bytes data) {
|
|
||||||
File file = File.new_for_path(Path.build_filename(folder, id));
|
|
||||||
try {
|
|
||||||
if (file.query_exists()) file.delete(); //TODO y?
|
|
||||||
DataOutputStream fos = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION));
|
|
||||||
fos.write_bytes_async.begin(data);
|
|
||||||
} catch (Error e) {
|
|
||||||
// Ignore: we failed in storing, so we refuse to display later...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool has_image(string id) {
|
|
||||||
File file = File.new_for_path(Path.build_filename(folder, id));
|
|
||||||
return file.query_exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Pixbuf? get_image(string id) {
|
|
||||||
try {
|
|
||||||
File file = File.new_for_path(Path.build_filename(folder, id));
|
|
||||||
FileInputStream stream = yield file.read_async();
|
|
||||||
|
|
||||||
uint8 fbuf[1024];
|
|
||||||
size_t size;
|
|
||||||
|
|
||||||
Checksum checksum = new Checksum (ChecksumType.SHA1);
|
|
||||||
while ((size = yield stream.read_async(fbuf)) > 0) {
|
|
||||||
checksum.update(fbuf, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checksum.get_string() != id) {
|
|
||||||
FileUtils.remove(file.get_path());
|
|
||||||
}
|
|
||||||
stream.seek(0, SeekType.SET);
|
|
||||||
return yield new Pixbuf.from_stream_async(stream, null);
|
|
||||||
} catch (Error e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,9 +21,11 @@ public class MucManager : StreamInteractionModule, Object {
|
||||||
public signal void conference_removed(Account account, Jid jid);
|
public signal void conference_removed(Account account, Jid jid);
|
||||||
|
|
||||||
private StreamInteractor stream_interactor;
|
private StreamInteractor stream_interactor;
|
||||||
|
private HashMap<Account, Gee.List<Jid>> mucs_joining = new HashMap<Account, ArrayList<Jid>>(Account.hash_func, Account.equals_func);
|
||||||
private HashMap<Jid, Xep.Muc.MucEnterError> enter_errors = new HashMap<Jid, Xep.Muc.MucEnterError>(Jid.hash_func, Jid.equals_func);
|
private HashMap<Jid, Xep.Muc.MucEnterError> enter_errors = new HashMap<Jid, Xep.Muc.MucEnterError>(Jid.hash_func, Jid.equals_func);
|
||||||
private ReceivedMessageListener received_message_listener;
|
private ReceivedMessageListener received_message_listener;
|
||||||
private HashMap<Account, BookmarksProvider> bookmarks_provider = new HashMap<Account, BookmarksProvider>(Account.hash_func, Account.equals_func);
|
private HashMap<Account, BookmarksProvider> bookmarks_provider = new HashMap<Account, BookmarksProvider>(Account.hash_func, Account.equals_func);
|
||||||
|
|
||||||
public static void start(StreamInteractor stream_interactor) {
|
public static void start(StreamInteractor stream_interactor) {
|
||||||
MucManager m = new MucManager(stream_interactor);
|
MucManager m = new MucManager(stream_interactor);
|
||||||
stream_interactor.add_module(m);
|
stream_interactor.add_module(m);
|
||||||
|
@ -45,6 +47,7 @@ public class MucManager : StreamInteractionModule, Object {
|
||||||
public async Muc.JoinResult? join(Account account, Jid jid, string? nick, string? password) {
|
public async Muc.JoinResult? join(Account account, Jid jid, string? nick, string? password) {
|
||||||
XmppStream? stream = stream_interactor.get_stream(account);
|
XmppStream? stream = stream_interactor.get_stream(account);
|
||||||
if (stream == null) return null;
|
if (stream == null) return null;
|
||||||
|
|
||||||
string nick_ = (nick ?? account.localpart) ?? account.domainpart;
|
string nick_ = (nick ?? account.localpart) ?? account.domainpart;
|
||||||
|
|
||||||
DateTime? history_since = null;
|
DateTime? history_since = null;
|
||||||
|
@ -54,8 +57,15 @@ public class MucManager : StreamInteractionModule, Object {
|
||||||
if (last_message != null) history_since = last_message.time;
|
if (last_message != null) history_since = last_message.time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mucs_joining.has_key(account)) {
|
||||||
|
mucs_joining[account] = new ArrayList<Jid>();
|
||||||
|
}
|
||||||
|
mucs_joining[account].add(jid);
|
||||||
|
|
||||||
Muc.JoinResult? res = yield stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid, nick_, password, history_since);
|
Muc.JoinResult? res = yield stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid, nick_, password, history_since);
|
||||||
|
|
||||||
|
mucs_joining[account].remove(jid);
|
||||||
|
|
||||||
if (res.nick != null) {
|
if (res.nick != null) {
|
||||||
// Join completed
|
// Join completed
|
||||||
enter_errors.unset(jid);
|
enter_errors.unset(jid);
|
||||||
|
@ -213,6 +223,11 @@ public class MucManager : StreamInteractionModule, Object {
|
||||||
return !jid.is_full() && conversation != null;
|
return !jid.is_full() && conversation != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool might_be_groupchat(Jid jid, Account account) {
|
||||||
|
if (mucs_joining.has_key(account) && mucs_joining[account].contains(jid)) return true;
|
||||||
|
return is_groupchat(jid, account);
|
||||||
|
}
|
||||||
|
|
||||||
public bool is_groupchat_occupant(Jid jid, Account account) {
|
public bool is_groupchat_occupant(Jid jid, Account account) {
|
||||||
return is_groupchat(jid.bare_jid, account) && jid.resourcepart != null;
|
return is_groupchat(jid.bare_jid, account) && jid.resourcepart != null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ public class AvatarImage : Misc {
|
||||||
update_avatar_if_jid(jid);
|
update_avatar_if_jid(jid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_received_avatar(Gdk.Pixbuf avatar, Jid jid, Account account) {
|
private void on_received_avatar(Jid jid, Account account) {
|
||||||
if (!account.equals(this.account)) return;
|
if (!account.equals(this.account)) return;
|
||||||
update_avatar_if_jid(jid);
|
update_avatar_if_jid(jid);
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,7 @@ public class Dialog : Gtk.Dialog {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_received_avatar(Pixbuf pixbuf, Jid jid, Account account) {
|
private void on_received_avatar(Jid jid, Account account) {
|
||||||
if (selected_account.equals(account) && jid.equals(account.bare_jid)) {
|
if (selected_account.equals(account) && jid.equals(account.bare_jid)) {
|
||||||
image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT));
|
image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,24 @@ namespace Xmpp.Xep.VCard {
|
||||||
private const string NS_URI = "vcard-temp";
|
private const string NS_URI = "vcard-temp";
|
||||||
private const string NS_URI_UPDATE = NS_URI + ":x:update";
|
private const string NS_URI_UPDATE = NS_URI + ":x:update";
|
||||||
|
|
||||||
|
public async Bytes? fetch_image(XmppStream stream, Jid jid, string hash) {
|
||||||
|
Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("vCard", NS_URI).add_self_xmlns()) { to=jid };
|
||||||
|
Iq.Stanza iq_res = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq);
|
||||||
|
|
||||||
|
if (iq_res.is_error()) return null;
|
||||||
|
string? res = iq_res.stanza.get_deep_string_content(@"$NS_URI:vCard", "PHOTO", "BINVAL");
|
||||||
|
if (res == null) return null;
|
||||||
|
Bytes content = new Bytes.take(Base64.decode(res));
|
||||||
|
string sha1 = Checksum.compute_for_bytes(ChecksumType.SHA1, content);
|
||||||
|
if (sha1 != hash) return null;
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
public class Module : XmppStreamModule {
|
public class Module : XmppStreamModule {
|
||||||
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0153_vcard_based_avatars");
|
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0153_vcard_based_avatars");
|
||||||
|
|
||||||
public signal void received_avatar(XmppStream stream, Jid jid, string id);
|
public signal void received_avatar_hash(XmppStream stream, Jid jid, string hash);
|
||||||
|
|
||||||
private PixbufStorage storage;
|
|
||||||
|
|
||||||
public Module(PixbufStorage storage) {
|
|
||||||
this.storage = storage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void attach(XmppStream stream) {
|
public override void attach(XmppStream stream) {
|
||||||
stream.get_module(Presence.Module.IDENTITY).received_presence.connect(on_received_presence);
|
stream.get_module(Presence.Module.IDENTITY).received_presence.connect(on_received_presence);
|
||||||
|
@ -34,31 +42,7 @@ public class Module : XmppStreamModule {
|
||||||
if (photo_node == null) return;
|
if (photo_node == null) return;
|
||||||
string? sha1 = photo_node.get_string_content();
|
string? sha1 = photo_node.get_string_content();
|
||||||
if (sha1 == null) return;
|
if (sha1 == null) return;
|
||||||
if (storage.has_image(sha1)) {
|
received_avatar_hash(stream, presence.from, sha1);
|
||||||
if (stream.get_flag(Muc.Flag.IDENTITY).is_occupant(presence.from)) {
|
|
||||||
received_avatar(stream, presence.from, sha1);
|
|
||||||
} else {
|
|
||||||
received_avatar(stream, presence.from.bare_jid, sha1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("vCard", NS_URI).add_self_xmlns());
|
|
||||||
if (stream.get_flag(Muc.Flag.IDENTITY).is_occupant(presence.from)) {
|
|
||||||
iq.to = presence.from;
|
|
||||||
} else {
|
|
||||||
iq.to = presence.from.bare_jid;
|
|
||||||
}
|
|
||||||
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, on_received_vcard);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_received_vcard(XmppStream stream, Iq.Stanza iq) {
|
|
||||||
if (iq.is_error()) return;
|
|
||||||
string? res = iq.stanza.get_deep_string_content(@"$NS_URI:vCard", "PHOTO", "BINVAL");
|
|
||||||
if (res == null) return;
|
|
||||||
Bytes content = new Bytes.take(Base64.decode(res));
|
|
||||||
string sha1 = Checksum.compute_for_bytes(ChecksumType.SHA1, content);
|
|
||||||
storage.store(sha1, content);
|
|
||||||
stream.get_module(IDENTITY).received_avatar(stream, iq.from, sha1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using Gee;
|
using Gee;
|
||||||
|
|
||||||
namespace Xmpp.Xep.Pubsub {
|
namespace Xmpp.Xep.Pubsub {
|
||||||
private const string NS_URI = "http://jabber.org/protocol/pubsub";
|
public const string NS_URI = "http://jabber.org/protocol/pubsub";
|
||||||
private const string NS_URI_EVENT = NS_URI + "#event";
|
private const string NS_URI_EVENT = NS_URI + "#event";
|
||||||
private const string NS_URI_OWNER = NS_URI + "#owner";
|
private const string NS_URI_OWNER = NS_URI + "#owner";
|
||||||
|
|
||||||
|
@ -39,18 +39,14 @@ namespace Xmpp.Xep.Pubsub {
|
||||||
Iq.Stanza request_iq = new Iq.Stanza.get(new StanzaNode.build("pubsub", NS_URI).add_self_xmlns().put_node(new StanzaNode.build("items", NS_URI).put_attribute("node", node)));
|
Iq.Stanza request_iq = new Iq.Stanza.get(new StanzaNode.build("pubsub", NS_URI).add_self_xmlns().put_node(new StanzaNode.build("items", NS_URI).put_attribute("node", node)));
|
||||||
request_iq.to = jid;
|
request_iq.to = jid;
|
||||||
|
|
||||||
Gee.List<StanzaNode>? ret = null;
|
Iq.Stanza iq_res = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, request_iq);
|
||||||
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, request_iq, (stream, iq) => {
|
|
||||||
StanzaNode event_node = iq.stanza.get_subnode("pubsub", NS_URI);
|
|
||||||
if (event_node == null) return;
|
|
||||||
StanzaNode items_node = event_node.get_subnode("items", NS_URI);
|
|
||||||
if (items_node == null) return;
|
|
||||||
ret = items_node.get_subnodes("item", NS_URI);
|
|
||||||
Idle.add(request_all.callback);
|
|
||||||
});
|
|
||||||
yield;
|
|
||||||
|
|
||||||
return ret;
|
StanzaNode event_node = iq_res.stanza.get_subnode("pubsub", NS_URI);
|
||||||
|
if (event_node == null) return null;
|
||||||
|
StanzaNode items_node = event_node.get_subnode("items", NS_URI);
|
||||||
|
if (items_node == null) return null;
|
||||||
|
|
||||||
|
return items_node.get_subnodes("item", NS_URI);
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate void OnResult(XmppStream stream, Jid jid, string? id, StanzaNode? node);
|
public delegate void OnResult(XmppStream stream, Jid jid, string? id, StanzaNode? node);
|
||||||
|
|
|
@ -3,17 +3,6 @@ namespace Xmpp.Xep.UserAvatars {
|
||||||
private const string NS_URI_DATA = NS_URI + ":data";
|
private const string NS_URI_DATA = NS_URI + ":data";
|
||||||
private const string NS_URI_METADATA = NS_URI + ":metadata";
|
private const string NS_URI_METADATA = NS_URI + ":metadata";
|
||||||
|
|
||||||
public class Module : XmppStreamModule {
|
|
||||||
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0084_user_avatars");
|
|
||||||
|
|
||||||
public signal void received_avatar(XmppStream stream, Jid jid, string id);
|
|
||||||
|
|
||||||
private PixbufStorage storage;
|
|
||||||
|
|
||||||
public Module(PixbufStorage storage) {
|
|
||||||
this.storage = storage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void publish_png(XmppStream stream, uint8[] image, int width, int height) {
|
public void publish_png(XmppStream stream, uint8[] image, int width, int height) {
|
||||||
string sha1 = Checksum.compute_for_data(ChecksumType.SHA1, image);
|
string sha1 = Checksum.compute_for_data(ChecksumType.SHA1, image);
|
||||||
StanzaNode data_node = new StanzaNode.build("data", NS_URI_DATA).add_self_xmlns()
|
StanzaNode data_node = new StanzaNode.build("data", NS_URI_DATA).add_self_xmlns()
|
||||||
|
@ -31,6 +20,28 @@ namespace Xmpp.Xep.UserAvatars {
|
||||||
stream.get_module(Pubsub.Module.IDENTITY).publish.begin(stream, null, NS_URI_METADATA, sha1, metadata_node);
|
stream.get_module(Pubsub.Module.IDENTITY).publish.begin(stream, null, NS_URI_METADATA, sha1, metadata_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Bytes? fetch_image(XmppStream stream, Jid jid, string hash) {
|
||||||
|
Gee.List<StanzaNode>? items = yield stream.get_module(Pubsub.Module.IDENTITY).request_all(stream, jid, NS_URI_DATA);
|
||||||
|
if (items == null || items.size == 0 || items[0].sub_nodes.size == 0) return null;
|
||||||
|
|
||||||
|
StanzaNode node = items[0].sub_nodes[0];
|
||||||
|
string? id = items[0].get_attribute("id", Pubsub.NS_URI);
|
||||||
|
if (id == null) return null;
|
||||||
|
|
||||||
|
Bytes image = new Bytes.take(Base64.decode(node.get_string_content()));
|
||||||
|
string sha1 = Checksum.compute_for_bytes(ChecksumType.SHA1, image);
|
||||||
|
if (sha1 != id) {
|
||||||
|
warning("sha sum did not match for avatar from %s expected=%s actual=%s", jid.to_string(), id, sha1);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Module : XmppStreamModule {
|
||||||
|
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0084_user_avatars");
|
||||||
|
|
||||||
|
public signal void received_avatar_hash(XmppStream stream, Jid jid, string id);
|
||||||
|
|
||||||
public override void attach(XmppStream stream) {
|
public override void attach(XmppStream stream) {
|
||||||
stream.get_module(Pubsub.Module.IDENTITY).add_filtered_notification(stream, NS_URI_METADATA, on_pupsub_event, null);
|
stream.get_module(Pubsub.Module.IDENTITY).add_filtered_notification(stream, NS_URI_METADATA, on_pupsub_event, null);
|
||||||
}
|
}
|
||||||
|
@ -40,32 +51,14 @@ namespace Xmpp.Xep.UserAvatars {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void on_pupsub_event(XmppStream stream, Jid jid, string id, StanzaNode? node) {
|
public void on_pupsub_event(XmppStream stream, Jid jid, string hash, StanzaNode? node) {
|
||||||
StanzaNode? info_node = node.get_subnode("info", NS_URI_METADATA);
|
StanzaNode? info_node = node.get_subnode("info", NS_URI_METADATA);
|
||||||
string? type = info_node == null ? null : info_node.get_attribute("type");
|
string? type = info_node == null ? null : info_node.get_attribute("type");
|
||||||
if (type != "image/png" && type != "image/jpeg") return;
|
if (type != "image/png" && type != "image/jpeg") return;
|
||||||
if (storage.has_image(id)) {
|
received_avatar_hash(stream, jid, hash);
|
||||||
stream.get_module(Module.IDENTITY).received_avatar(stream, jid, id);
|
|
||||||
} else {
|
|
||||||
stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, NS_URI_DATA, on_pubsub_data_response);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string get_ns() { return NS_URI; }
|
public override string get_ns() { return NS_URI; }
|
||||||
public override string get_id() { return IDENTITY.id; }
|
public override string get_id() { return IDENTITY.id; }
|
||||||
|
|
||||||
private void on_pubsub_data_response(XmppStream stream, Jid jid, string? id, StanzaNode? node) {
|
|
||||||
if (node == null || id == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Bytes image = new Bytes.take(Base64.decode(node.get_string_content()));
|
|
||||||
string sha1 = Checksum.compute_for_bytes(ChecksumType.SHA1, image);
|
|
||||||
if (sha1 != id) {
|
|
||||||
warning("sha sum did not match for avatar from %s expected=%s actual=%s", jid.to_string(), id, sha1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
storage.store(id, image);
|
|
||||||
stream.get_module(Module.IDENTITY).received_avatar(stream, jid, id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue