2018-05-24 21:52:59 +02:00
|
|
|
using Soup;
|
|
|
|
using GLib;
|
|
|
|
using Gdk;
|
|
|
|
using Json;
|
|
|
|
|
|
|
|
private struct CachedImage {
|
2018-05-29 19:04:04 +02:00
|
|
|
|
2018-05-24 21:52:59 +02:00
|
|
|
public string uri;
|
|
|
|
public int size;
|
|
|
|
|
|
|
|
public CachedImage(string uri, int size) { this.uri=uri; this.size=size; }
|
|
|
|
|
|
|
|
public static uint hash(CachedImage? c) {
|
|
|
|
assert(c != null);
|
|
|
|
assert(c.uri != null);
|
|
|
|
return GLib.int64_hash(c.size) ^ c.uri.hash();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static bool equal(CachedImage? a, CachedImage? b) {
|
2018-05-29 19:04:04 +02:00
|
|
|
if (a == null || b == null)
|
|
|
|
return false;
|
2018-05-24 21:52:59 +02:00
|
|
|
return a.size == b.size && a.uri == b.uri;
|
|
|
|
}
|
2018-05-29 19:04:04 +02:00
|
|
|
|
2018-05-24 21:52:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public delegate void PixbufCallback (Gdk.Pixbuf pb);
|
|
|
|
|
|
|
|
public class Tootle.ImageCache : GLib.Object {
|
2018-05-29 19:04:04 +02:00
|
|
|
|
2018-05-24 21:52:59 +02:00
|
|
|
private GLib.HashTable<CachedImage?, Soup.Message> in_progress;
|
|
|
|
private GLib.HashTable<CachedImage?, Gdk.Pixbuf> pixbufs;
|
|
|
|
private uint total_size_est;
|
|
|
|
private uint size_limit;
|
2018-05-29 19:04:04 +02:00
|
|
|
private string cache_path;
|
2018-05-24 21:52:59 +02:00
|
|
|
|
|
|
|
construct {
|
|
|
|
pixbufs = new GLib.HashTable<CachedImage?, Gdk.Pixbuf>(CachedImage.hash, CachedImage.equal);
|
|
|
|
in_progress = new GLib.HashTable<CachedImage?, Soup.Message>(CachedImage.hash, CachedImage.equal);
|
|
|
|
total_size_est = 0;
|
2018-05-29 19:04:04 +02:00
|
|
|
cache_path = "%s/%s".printf (GLib.Environment.get_user_cache_dir (), app.application_id);
|
|
|
|
|
|
|
|
settings.changed.connect (on_settings_changed);
|
2018-05-24 21:52:59 +02:00
|
|
|
on_settings_changed ();
|
|
|
|
}
|
|
|
|
|
|
|
|
public ImageCache() {
|
|
|
|
GLib.Object();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void on_settings_changed () {
|
|
|
|
// assume 32BPP (divide bytes by 4 to get # pixels) and raw, overhead-free storage
|
|
|
|
// cache_size setting is number of megabytes
|
2018-05-29 19:04:04 +02:00
|
|
|
size_limit = (1024 * 1024 * settings.cache_size) / 4;
|
|
|
|
if (settings.cache)
|
|
|
|
enforce_size_limit ();
|
|
|
|
else
|
|
|
|
remove_all ();
|
2018-05-24 21:52:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void remove_all () {
|
2018-05-29 19:04:04 +02:00
|
|
|
debug("Image cache cleared");
|
2018-05-24 21:52:59 +02:00
|
|
|
pixbufs.remove_all ();
|
|
|
|
total_size_est = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void remove_one (string uri, int size) {
|
|
|
|
CachedImage ci = CachedImage (uri, size);
|
|
|
|
bool removed = pixbufs.remove(ci);
|
|
|
|
if (removed) {
|
|
|
|
assert (total_size_est >= size * size);
|
|
|
|
total_size_est -= size * size;
|
2018-05-29 19:04:04 +02:00
|
|
|
debug("Cache usage: %zd", total_size_est);
|
2018-05-24 21:52:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-29 19:04:04 +02:00
|
|
|
//TODO: fix me
|
|
|
|
// remove least used image
|
2018-05-24 21:52:59 +02:00
|
|
|
private void remove_least_used () {
|
|
|
|
var keys = pixbufs.get_keys();
|
|
|
|
if (keys.first() != null) {
|
|
|
|
var ci = keys.first().data;
|
|
|
|
remove_one(ci.uri, ci.size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void enforce_size_limit () {
|
2018-05-29 19:04:04 +02:00
|
|
|
debug("Updating size limit (%zd/%zd)", total_size_est, size_limit);
|
|
|
|
while (total_size_est > size_limit && pixbufs.size() > 0)
|
2018-05-24 21:52:59 +02:00
|
|
|
remove_least_used ();
|
2018-05-29 19:04:04 +02:00
|
|
|
|
2018-05-24 21:52:59 +02:00
|
|
|
assert (total_size_est <= size_limit);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void store_pixbuf (CachedImage ci, Gdk.Pixbuf pixbuf) {
|
|
|
|
assert (!pixbufs.contains (ci));
|
|
|
|
pixbufs.insert (ci, pixbuf);
|
|
|
|
in_progress.remove (ci);
|
|
|
|
total_size_est += ci.size * ci.size;
|
|
|
|
enforce_size_limit ();
|
|
|
|
}
|
|
|
|
|
|
|
|
public async void get_image (string uri, int size, owned PixbufCallback? cb = null) {
|
|
|
|
CachedImage ci = CachedImage (uri, size);
|
|
|
|
Gdk.Pixbuf? pb = pixbufs.get(ci);
|
|
|
|
if (pb != null) {
|
|
|
|
cb (pb);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Soup.Message? msg = in_progress.get(ci);
|
|
|
|
if (msg == null) {
|
|
|
|
msg = new Soup.Message("GET", uri);
|
2018-06-22 19:44:37 +02:00
|
|
|
ulong id = 0;
|
|
|
|
id = msg.finished.connect(() => {
|
2018-05-29 19:04:04 +02:00
|
|
|
debug("Caching %s@%d", uri, size);
|
2018-05-24 21:52:59 +02:00
|
|
|
var data = msg.response_body.data;
|
|
|
|
var stream = new MemoryInputStream.from_data (data);
|
|
|
|
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
|
|
|
store_pixbuf(ci, pixbuf);
|
|
|
|
cb(pixbuf);
|
2018-06-22 19:44:37 +02:00
|
|
|
msg.disconnect(id);
|
2018-05-24 21:52:59 +02:00
|
|
|
});
|
|
|
|
in_progress[ci] = msg;
|
2018-05-29 19:04:04 +02:00
|
|
|
network.queue_custom (msg);
|
2018-05-24 21:52:59 +02:00
|
|
|
} else {
|
2018-06-22 19:44:37 +02:00
|
|
|
ulong id = 0;
|
|
|
|
id = msg.finished.connect(() => {
|
2018-05-24 21:52:59 +02:00
|
|
|
cb(pixbufs[ci]);
|
2018-06-22 19:44:37 +02:00
|
|
|
msg.disconnect(id);
|
2018-05-24 21:52:59 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-02 11:27:19 +02:00
|
|
|
public void load_avatar (string uri, Granite.Widgets.Avatar avatar, int size) {
|
|
|
|
get_image.begin(uri, size, (pixbuf) => avatar.pixbuf = pixbuf.scale_simple (size, size, Gdk.InterpType.BILINEAR));
|
2018-05-24 21:52:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void load_image (string uri, Gtk.Image image) {
|
|
|
|
load_scaled_image(uri, image, -1);
|
|
|
|
}
|
|
|
|
|
2018-06-02 11:27:19 +02:00
|
|
|
public void load_scaled_image (string uri, Gtk.Image image, int size) {
|
2018-05-24 21:52:59 +02:00
|
|
|
get_image.begin(uri, size, image.set_from_pixbuf);
|
|
|
|
}
|
2018-05-29 19:04:04 +02:00
|
|
|
|
2018-05-24 21:52:59 +02:00
|
|
|
}
|