Refactor image cache
This commit is contained in:
parent
6d88f51a90
commit
72396a4e6a
|
@ -4,6 +4,7 @@ using Gdk;
|
||||||
using Json;
|
using Json;
|
||||||
|
|
||||||
private struct CachedImage {
|
private struct CachedImage {
|
||||||
|
|
||||||
public string uri;
|
public string uri;
|
||||||
public int size;
|
public int size;
|
||||||
|
|
||||||
|
@ -16,24 +17,30 @@ private struct CachedImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool equal(CachedImage? a, CachedImage? b) {
|
public static bool equal(CachedImage? a, CachedImage? b) {
|
||||||
if (a == null || b == null) { return false; }
|
if (a == null || b == null)
|
||||||
|
return false;
|
||||||
return a.size == b.size && a.uri == b.uri;
|
return a.size == b.size && a.uri == b.uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate void PixbufCallback (Gdk.Pixbuf pb);
|
public delegate void PixbufCallback (Gdk.Pixbuf pb);
|
||||||
|
|
||||||
public class Tootle.ImageCache : GLib.Object {
|
public class Tootle.ImageCache : GLib.Object {
|
||||||
|
|
||||||
private GLib.HashTable<CachedImage?, Soup.Message> in_progress;
|
private GLib.HashTable<CachedImage?, Soup.Message> in_progress;
|
||||||
private GLib.HashTable<CachedImage?, Gdk.Pixbuf> pixbufs;
|
private GLib.HashTable<CachedImage?, Gdk.Pixbuf> pixbufs;
|
||||||
private uint total_size_est;
|
private uint total_size_est;
|
||||||
private uint size_limit;
|
private uint size_limit;
|
||||||
|
private string cache_path;
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
pixbufs = new GLib.HashTable<CachedImage?, Gdk.Pixbuf>(CachedImage.hash, CachedImage.equal);
|
pixbufs = new GLib.HashTable<CachedImage?, Gdk.Pixbuf>(CachedImage.hash, CachedImage.equal);
|
||||||
in_progress = new GLib.HashTable<CachedImage?, Soup.Message>(CachedImage.hash, CachedImage.equal);
|
in_progress = new GLib.HashTable<CachedImage?, Soup.Message>(CachedImage.hash, CachedImage.equal);
|
||||||
total_size_est = 0;
|
total_size_est = 0;
|
||||||
Tootle.settings.changed.connect (on_settings_changed);
|
cache_path = "%s/%s".printf (GLib.Environment.get_user_cache_dir (), app.application_id);
|
||||||
|
|
||||||
|
settings.changed.connect (on_settings_changed);
|
||||||
on_settings_changed ();
|
on_settings_changed ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,37 +48,35 @@ public class Tootle.ImageCache : GLib.Object {
|
||||||
GLib.Object();
|
GLib.Object();
|
||||||
}
|
}
|
||||||
|
|
||||||
// adopts cache size limit from settings
|
|
||||||
private void on_settings_changed () {
|
private void on_settings_changed () {
|
||||||
// assume 32BPP (divide bytes by 4 to get # pixels) and raw, overhead-free storage
|
// assume 32BPP (divide bytes by 4 to get # pixels) and raw, overhead-free storage
|
||||||
// cache_size setting is number of megabytes
|
// cache_size setting is number of megabytes
|
||||||
size_limit = (1024 * 1024 * Tootle.settings.cache_size / 4);
|
size_limit = (1024 * 1024 * settings.cache_size) / 4;
|
||||||
enforce_size_limit ();
|
if (settings.cache)
|
||||||
|
enforce_size_limit ();
|
||||||
|
else
|
||||||
|
remove_all ();
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove all cached images
|
|
||||||
public void remove_all () {
|
public void remove_all () {
|
||||||
GLib.debug("image cache cleared");
|
debug("Image cache cleared");
|
||||||
pixbufs.remove_all ();
|
pixbufs.remove_all ();
|
||||||
total_size_est = 0;
|
total_size_est = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove any entry for the given uri and size from the cache
|
|
||||||
public void remove_one (string uri, int size) {
|
public void remove_one (string uri, int size) {
|
||||||
GLib.debug("image cache removing %s", uri);
|
|
||||||
CachedImage ci = CachedImage (uri, size);
|
CachedImage ci = CachedImage (uri, size);
|
||||||
bool removed = pixbufs.remove(ci);
|
bool removed = pixbufs.remove(ci);
|
||||||
if (removed) {
|
if (removed) {
|
||||||
assert (total_size_est >= size * size);
|
assert (total_size_est >= size * size);
|
||||||
total_size_est -= size * size;
|
total_size_est -= size * size;
|
||||||
GLib.debug("image cache removed %s; size est. is %zd", uri, total_size_est);
|
debug("Cache usage: %zd", total_size_est);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete the pixbuf with the smallest reference count from the cache
|
//TODO: fix me
|
||||||
|
// remove least used image
|
||||||
private void remove_least_used () {
|
private void remove_least_used () {
|
||||||
GLib.debug("image cache removing least-used");
|
|
||||||
// for now, dummy implementation: just remove the first pixbuf
|
|
||||||
var keys = pixbufs.get_keys();
|
var keys = pixbufs.get_keys();
|
||||||
if (keys.first() != null) {
|
if (keys.first() != null) {
|
||||||
var ci = keys.first().data;
|
var ci = keys.first().data;
|
||||||
|
@ -80,15 +85,14 @@ public class Tootle.ImageCache : GLib.Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enforce_size_limit () {
|
private void enforce_size_limit () {
|
||||||
GLib.debug("image cache enforcing size limit (%zd/%zd)", total_size_est, size_limit);
|
debug("Updating size limit (%zd/%zd)", total_size_est, size_limit);
|
||||||
while (total_size_est > size_limit && pixbufs.size() > 0) {
|
while (total_size_est > size_limit && pixbufs.size() > 0)
|
||||||
remove_least_used ();
|
remove_least_used ();
|
||||||
}
|
|
||||||
assert (total_size_est <= size_limit);
|
assert (total_size_est <= size_limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void store_pixbuf (CachedImage ci, Gdk.Pixbuf pixbuf) {
|
private void store_pixbuf (CachedImage ci, Gdk.Pixbuf pixbuf) {
|
||||||
GLib.debug("image cache inserting %s@%d", ci.uri, ci.size);
|
|
||||||
assert (!pixbufs.contains (ci));
|
assert (!pixbufs.contains (ci));
|
||||||
pixbufs.insert (ci, pixbuf);
|
pixbufs.insert (ci, pixbuf);
|
||||||
in_progress.remove (ci);
|
in_progress.remove (ci);
|
||||||
|
@ -103,13 +107,12 @@ public class Tootle.ImageCache : GLib.Object {
|
||||||
cb (pb);
|
cb (pb);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GLib.debug("image cache miss for %s@%d", uri, size);
|
|
||||||
|
|
||||||
Soup.Message? msg = in_progress.get(ci);
|
Soup.Message? msg = in_progress.get(ci);
|
||||||
if (msg == null) {
|
if (msg == null) {
|
||||||
msg = new Soup.Message("GET", uri);
|
msg = new Soup.Message("GET", uri);
|
||||||
msg.finished.connect(() => {
|
msg.finished.connect(() => {
|
||||||
GLib.debug("image cache about to insert %s@%d", uri, size);
|
debug("Caching %s@%d", uri, size);
|
||||||
var data = msg.response_body.data;
|
var data = msg.response_body.data;
|
||||||
var stream = new MemoryInputStream.from_data (data);
|
var stream = new MemoryInputStream.from_data (data);
|
||||||
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
||||||
|
@ -117,11 +120,9 @@ public class Tootle.ImageCache : GLib.Object {
|
||||||
cb(pixbuf);
|
cb(pixbuf);
|
||||||
});
|
});
|
||||||
in_progress[ci] = msg;
|
in_progress[ci] = msg;
|
||||||
Tootle.network.queue (msg);
|
network.queue_custom (msg);
|
||||||
} else {
|
} else {
|
||||||
GLib.debug("found in-progress request for %s@%d", uri, size);
|
|
||||||
msg.finished.connect_after(() => {
|
msg.finished.connect_after(() => {
|
||||||
GLib.debug("in-progress request finished for %s@%d", uri, size);
|
|
||||||
cb(pixbufs[ci]);
|
cb(pixbufs[ci]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -138,4 +139,5 @@ public class Tootle.ImageCache : GLib.Object {
|
||||||
public void load_scaled_image (string uri, Gtk.Image image, int size = 64) {
|
public void load_scaled_image (string uri, Gtk.Image image, int size = 64) {
|
||||||
get_image.begin(uri, size, image.set_from_pixbuf);
|
get_image.begin(uri, size, image.set_from_pixbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,8 @@ public class Tootle.NetManager : GLib.Object {
|
||||||
|
|
||||||
private int requests_processing = 0;
|
private int requests_processing = 0;
|
||||||
private Soup.Session session;
|
private Soup.Session session;
|
||||||
private Soup.Cache cache;
|
|
||||||
public string cache_path;
|
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
cache_path = "%s/%s".printf (GLib.Environment.get_user_cache_dir (), Tootle.app.application_id);
|
|
||||||
cache = new Soup.Cache (cache_path, Soup.CacheType.SINGLE_USER);
|
|
||||||
session = new Soup.Session ();
|
session = new Soup.Session ();
|
||||||
session.ssl_strict = true;
|
session.ssl_strict = true;
|
||||||
session.ssl_use_system_ca_file = true;
|
session.ssl_use_system_ca_file = true;
|
||||||
|
@ -31,12 +27,6 @@ public class Tootle.NetManager : GLib.Object {
|
||||||
finished ();
|
finished ();
|
||||||
});
|
});
|
||||||
|
|
||||||
Tootle.app.shutdown.connect (() => {
|
|
||||||
cache.dump ();
|
|
||||||
});
|
|
||||||
Tootle.settings.changed.connect (on_settings_changed);
|
|
||||||
on_settings_changed ();
|
|
||||||
|
|
||||||
// Soup.Logger logger = new Soup.Logger (Soup.LoggerLogLevel.BODY, -1);
|
// Soup.Logger logger = new Soup.Logger (Soup.LoggerLogLevel.BODY, -1);
|
||||||
// session.add_feature (logger);
|
// session.add_feature (logger);
|
||||||
}
|
}
|
||||||
|
@ -45,23 +35,6 @@ public class Tootle.NetManager : GLib.Object {
|
||||||
GLib.Object();
|
GLib.Object();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_settings_changed () {
|
|
||||||
// cache.set_max_size (1024 * 1024 * Tootle.settings.cache_size);
|
|
||||||
// var has_cache = session.has_feature (cache.get_type ());
|
|
||||||
// if (Tootle.settings.cache) {
|
|
||||||
// if (!has_cache) {
|
|
||||||
// debug ("Turning on cache");
|
|
||||||
// session.add_feature (cache);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// if (has_cache) {
|
|
||||||
// debug ("Turning off cache");
|
|
||||||
// session.remove_feature (cache);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
public async WebsocketConnection stream (Soup.Message msg) {
|
public async WebsocketConnection stream (Soup.Message msg) {
|
||||||
return yield session.websocket_connect_async (msg, null, null, null);
|
return yield session.websocket_connect_async (msg, null, null, null);
|
||||||
}
|
}
|
||||||
|
@ -108,7 +81,7 @@ public class Tootle.NetManager : GLib.Object {
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void queue_custom (Soup.Message msg, owned Soup.SessionCallback cb) {
|
public void queue_custom (Soup.Message msg, owned Soup.SessionCallback? cb = null) {
|
||||||
session.queue_message (msg, cb);
|
session.queue_message (msg, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,34 +106,49 @@ public class Tootle.NetManager : GLib.Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load_avatar (string url, Granite.Widgets.Avatar avatar, int size = 32){
|
public void load_avatar (string url, Granite.Widgets.Avatar avatar, int size = 32){
|
||||||
|
if (settings.cache) {
|
||||||
|
image_cache.load_avatar (url, avatar, size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var msg = new Soup.Message("GET", url);
|
var msg = new Soup.Message("GET", url);
|
||||||
msg.finished.connect(() => {
|
msg.finished.connect(() => {
|
||||||
var data = msg.response_body.data;
|
var data = msg.response_body.data;
|
||||||
var stream = new MemoryInputStream.from_data (data);
|
var stream = new MemoryInputStream.from_data (data);
|
||||||
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
||||||
avatar.pixbuf = pixbuf;
|
avatar.pixbuf = pixbuf;
|
||||||
});
|
});
|
||||||
Tootle.network.queue (msg);
|
Tootle.network.queue (msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load_image (string url, Gtk.Image image) {
|
public void load_image (string url, Gtk.Image image) {
|
||||||
|
if (settings.cache) {
|
||||||
|
image_cache.load_image (url, image);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var msg = new Soup.Message("GET", url);
|
var msg = new Soup.Message("GET", url);
|
||||||
msg.finished.connect(() => {
|
msg.finished.connect(() => {
|
||||||
var data = msg.response_body.data;
|
var data = msg.response_body.data;
|
||||||
var stream = new MemoryInputStream.from_data (data);
|
var stream = new MemoryInputStream.from_data (data);
|
||||||
var pixbuf = new Gdk.Pixbuf.from_stream (stream);
|
var pixbuf = new Gdk.Pixbuf.from_stream (stream);
|
||||||
image.set_from_pixbuf (pixbuf);
|
image.set_from_pixbuf (pixbuf);
|
||||||
});
|
});
|
||||||
Tootle.network.queue (msg);
|
Tootle.network.queue (msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load_scaled_image (string url, Gtk.Image image, int size = 64) {
|
public void load_scaled_image (string url, Gtk.Image image, int size = 64) {
|
||||||
|
if (settings.cache) {
|
||||||
|
image_cache.load_scaled_image (url, image, size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var msg = new Soup.Message("GET", url);
|
var msg = new Soup.Message("GET", url);
|
||||||
msg.finished.connect(() => {
|
msg.finished.connect(() => {
|
||||||
var data = msg.response_body.data;
|
var data = msg.response_body.data;
|
||||||
var stream = new MemoryInputStream.from_data (data);
|
var stream = new MemoryInputStream.from_data (data);
|
||||||
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
||||||
image.set_from_pixbuf (pixbuf);
|
image.set_from_pixbuf (pixbuf);
|
||||||
});
|
});
|
||||||
Tootle.network.queue (msg);
|
Tootle.network.queue (msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ public class Tootle.AccountView : TimelineView {
|
||||||
username.label = "@" + account.acct;
|
username.label = "@" + account.acct;
|
||||||
note.label = Utils.simplify_html (account.note);
|
note.label = Utils.simplify_html (account.note);
|
||||||
button_follow.visible = !account.is_self ();
|
button_follow.visible = !account.is_self ();
|
||||||
Tootle.image_cache.load_avatar (account.avatar, avatar, 128);
|
Tootle.network.load_avatar (account.avatar, avatar, 128);
|
||||||
|
|
||||||
menu_edit.visible = account.is_self ();
|
menu_edit.visible = account.is_self ();
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ public class Tootle.AccountsButton : Gtk.MenuButton{
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountView (){
|
public AccountView (){
|
||||||
button.clicked.connect (() => Tootle.accounts.remove (id));
|
button.clicked.connect (() => accounts.remove (id));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -62,15 +62,15 @@ public class Tootle.AccountsButton : Gtk.MenuButton{
|
||||||
|
|
||||||
item_refresh = new Gtk.ModelButton ();
|
item_refresh = new Gtk.ModelButton ();
|
||||||
item_refresh.text = _("Refresh");
|
item_refresh.text = _("Refresh");
|
||||||
item_refresh.clicked.connect (() => Tootle.app.refresh ());
|
item_refresh.clicked.connect (() => app.refresh ());
|
||||||
|
|
||||||
item_favs = new Gtk.ModelButton ();
|
item_favs = new Gtk.ModelButton ();
|
||||||
item_favs.text = _("Favorites");
|
item_favs.text = _("Favorites");
|
||||||
item_favs.clicked.connect (() => Tootle.window.open_view (new FavoritesView ()));
|
item_favs.clicked.connect (() => window.open_view (new FavoritesView ()));
|
||||||
|
|
||||||
item_search = new Gtk.ModelButton ();
|
item_search = new Gtk.ModelButton ();
|
||||||
item_search.text = _("Search");
|
item_search.text = _("Search");
|
||||||
item_search.clicked.connect (() => Tootle.window.open_view (new SearchView ()));
|
item_search.clicked.connect (() => window.open_view (new SearchView ()));
|
||||||
|
|
||||||
item_settings = new Gtk.ModelButton ();
|
item_settings = new Gtk.ModelButton ();
|
||||||
item_settings.text = _("Settings");
|
item_settings.text = _("Settings");
|
||||||
|
@ -107,7 +107,7 @@ public class Tootle.AccountsButton : Gtk.MenuButton{
|
||||||
if (widget.id == Tootle.settings.current_account)
|
if (widget.id == Tootle.settings.current_account)
|
||||||
return;
|
return;
|
||||||
else
|
else
|
||||||
Tootle.accounts.switch_account (widget.id);
|
accounts.switch_account (widget.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ public class Tootle.AccountsButton : Gtk.MenuButton{
|
||||||
if (account == null)
|
if (account == null)
|
||||||
avatar.show_default (24);
|
avatar.show_default (24);
|
||||||
else
|
else
|
||||||
image_cache.load_avatar (account.avatar, avatar, 24);
|
network.load_avatar (account.avatar, avatar, 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_selection () {
|
private void update_selection () {
|
||||||
|
|
|
@ -49,9 +49,9 @@ public class Tootle.AttachmentWidget : Gtk.EventBox {
|
||||||
image.valign = Gtk.Align.CENTER;
|
image.valign = Gtk.Align.CENTER;
|
||||||
image.show ();
|
image.show ();
|
||||||
if (editable)
|
if (editable)
|
||||||
Tootle.image_cache.load_scaled_image (attachment.preview_url, image);
|
Tootle.network.load_scaled_image (attachment.preview_url, image);
|
||||||
else
|
else
|
||||||
Tootle.image_cache.load_image (attachment.preview_url, image);
|
Tootle.network.load_image (attachment.preview_url, image);
|
||||||
grid.attach (image, 0, 0);
|
grid.attach (image, 0, 0);
|
||||||
label.hide ();
|
label.hide ();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -222,7 +222,7 @@ public class Tootle.StatusWidget : Gtk.EventBox {
|
||||||
reblog.tooltip_text = _("This post can't be boosted");
|
reblog.tooltip_text = _("This post can't be boosted");
|
||||||
}
|
}
|
||||||
|
|
||||||
Tootle.image_cache.load_avatar (formal.account.avatar, avatar, avatar_size);
|
Tootle.network.load_avatar (formal.account.avatar, avatar, avatar_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool is_spoiler () {
|
public bool is_spoiler () {
|
||||||
|
|
Loading…
Reference in New Issue