diff options
author | Brendan Long <self@brendanlong.com> | 2017-10-20 02:24:36 +0300 |
---|---|---|
committer | Brendan Long <self@brendanlong.com> | 2017-10-20 02:37:21 +0300 |
commit | 1da28f5d2d38fc83b9d95476367a67a8a37ddfa3 (patch) | |
tree | cc43917f577f11a3781afdda4d33179cab3c682d | |
parent | 610a467cc27d844fca9b0d0f3d77aa9477c2488c (diff) |
Use a map of promises for loading favicons
This makes it so:
- We only try to download each icon once
- Once an icon is loaded, we can return it immediately from the map
- Favicon loading is fully async
- Removed ReloadFavicon signal since we currently don't ever reload them
I merged some of the code so we only have getIcon() and downloadFavicon()
-rw-r--r-- | src/Backend/Backend.vala | 1 | ||||
-rw-r--r-- | src/FavIconManager.vala | 198 | ||||
-rw-r--r-- | src/FeedReader.vala | 8 | ||||
-rw-r--r-- | src/Widgets/ArticleList/ArticleList.vala | 5 | ||||
-rw-r--r-- | src/Widgets/ArticleList/ArticleListBox.vala | 11 | ||||
-rw-r--r-- | src/Widgets/ArticleRow.vala | 30 | ||||
-rw-r--r-- | src/Widgets/ColumnView.vala | 6 | ||||
-rw-r--r-- | src/Widgets/FeedList.vala | 13 | ||||
-rw-r--r-- | src/Widgets/FeedRow.vala | 32 |
9 files changed, 103 insertions, 201 deletions
diff --git a/src/Backend/Backend.vala b/src/Backend/Backend.vala index ef681a32..3bdbb6bb 100644 --- a/src/Backend/Backend.vala +++ b/src/Backend/Backend.vala @@ -34,7 +34,6 @@ namespace FeedReader { public signal void newFeedList(); public signal void refreshFeedListCounter(); public signal void updateArticleList(); - public signal void reloadFavIcons(); public signal void showArticleListOverlay(); public signal void setOffline(); public signal void setOnline(); diff --git a/src/FavIconManager.vala b/src/FavIconManager.vala index 7693b553..0fa4a4dc 100644 --- a/src/FavIconManager.vala +++ b/src/FavIconManager.vala @@ -15,11 +15,9 @@ public class FeedReader.FavIconManager : GLib.Object { - private Gee.HashMap<string, Gdk.Pixbuf> m_map; + private Gee.Map<string, Gee.Future<Gdk.Pixbuf?>> m_map = new Gee.HashMap<string, Gee.Future<Gdk.Pixbuf?>>(); private static FavIconManager? m_cache = null; - public signal void ReloadFavIcon(Feed feed); - public static FavIconManager get_default() { if(m_cache == null) @@ -30,76 +28,54 @@ public class FeedReader.FavIconManager : GLib.Object { private FavIconManager() { - m_map = new Gee.HashMap<string, Gdk.Pixbuf>(); } - private async void load(Feed feed, bool firstTry = true) + public async Gdk.Pixbuf? getIcon(Feed feed) { - var fileName = GLib.Base64.encode(feed.getFeedID().data) + ".ico"; try { - var file = File.new_for_path(GLib.Environment.get_user_data_dir() + "/feedreader/data/feed_icons/" + fileName); - var stream = yield file.read_async(); - var pixbuf = yield new Gdk.Pixbuf.from_stream_async(stream); - stream.close(); - if(pixbuf.get_height() <= 1 && pixbuf.get_width() <= 1) + var feed_id = feed.getFeedID(); + var future = m_map.get(feed_id); + if(future == null) { - Logger.warning(@"FavIconManager: $fileName is too small"); - return; - } - - pixbuf = pixbuf.scale_simple(24, 24, Gdk.InterpType.BILINEAR); - m_map.set(feed.getFeedID(), pixbuf); - ReloadFavIcon(feed); - } - catch (IOError.NOT_FOUND e) - { - //Logger.debug(@"FavIconManager: Icon $fileName does not exist"); - - if(!firstTry) - return; + var promise = new Gee.Promise<Gdk.Pixbuf?>(); + try + { + future = promise.future; + m_map.set(feed_id, future); - bool success = yield downloadFavIcon(feed); - if(success) - yield load(feed, false); - } - catch(Gdk.PixbufError.UNKNOWN_TYPE e) - { - Logger.warning(@"FavIconManager.load: Icon $fileName is an unknown type"); - } - catch(Error e) - { - Logger.error(@"FavIconManager.load: $fileName: %s".printf(e.message)); - } - } + var stream = yield downloadFavIcon(feed); + if(stream == null) + return null; - private bool hasIcon(Feed feed) - { - if(m_map == null) - { - m_map = new Gee.HashMap<string, Gdk.Pixbuf>(); - return false; - } + var pixbuf = yield new Gdk.Pixbuf.from_stream_async(stream); + stream.close(); - return m_map.has_key(feed.getFeedID()); - } + if(pixbuf.get_height() <= 1 && pixbuf.get_width() <= 1) + { + Logger.warning(@"FavIconManager: Icon for feed %s is too small".printf(feed.getTitle())); + return null; + } + pixbuf = pixbuf.scale_simple(24, 24, Gdk.InterpType.BILINEAR); - public async Gdk.Pixbuf? getIcon(Feed feed, bool firstTry = true) - { - if(hasIcon(feed)) - { - return m_map.get(feed.getFeedID()).copy(); + promise.set_value(pixbuf); + } + finally + { + if(!future.ready) + promise.set_value(null); + } + } + return yield future.wait_async(); } - else if(firstTry) + catch(Error e) { - yield load(feed); - return yield getIcon(feed, false); + Logger.error("FavIconManager.getIcon: %s".printf(e.message)); + return null; } - - return null; } - private async bool downloadFavIcon(Feed feed, string? hint_url = null, GLib.Cancellable? cancellable = null, string icon_path = GLib.Environment.get_user_data_dir() + "/feedreader/data/feed_icons/") + private async InputStream? downloadFavIcon(Feed feed, GLib.Cancellable? cancellable = null, string icon_path = GLib.Environment.get_user_data_dir() + "/feedreader/data/feed_icons/") throws GLib.Error { string filename_prefix = icon_path + feed.getFeedFileName(); string local_filename = @"$filename_prefix.ico"; @@ -109,7 +85,7 @@ public class FeedReader.FavIconManager : GLib.Object { DateTime? expires = metadata.expires; if(cancellable != null && cancellable.is_cancelled()) - return false; + return null; var now = new DateTime.now_utc(); if(expires != null) @@ -117,7 +93,15 @@ public class FeedReader.FavIconManager : GLib.Object { if(expires.to_unix() > now.to_unix()) { Logger.debug("Favicon for %s is valid until %s, skipping this time".printf(feed.getTitle(), expires.to_string())); - return yield Utils.file_exists(local_filename, FileType.REGULAR); + var file = File.new_for_path(local_filename); + try + { + return yield file.read_async(); + } + catch(FileError.NOENT e) + { + return null; + } } } @@ -126,9 +110,6 @@ public class FeedReader.FavIconManager : GLib.Object { var obvious_icons = new Gee.ArrayList<string>(); - if(hint_url != null) - obvious_icons.add(hint_url); - if(feed.getIconURL() != null) obvious_icons.add(feed.getIconURL()); @@ -147,20 +128,20 @@ public class FeedReader.FavIconManager : GLib.Object { obvious_icons.add(icon_url); } - // Try to find one of those icons foreach(var url in obvious_icons) { - if(yield downloadIcon(feed, url, cancellable, icon_path)) - return true; + var stream = yield downloadIcon(feed, url, cancellable, icon_path); + if(stream != null) + return stream; if(cancellable != null && cancellable.is_cancelled()) - return false; + return null; } // If all else fails, download html and parse to find location of favicon if(siteURL == null) - return false; + return null; var message_html = new Soup.Message("GET", siteURL); if(Settings.tweaks().get_boolean("do-not-track")) @@ -175,7 +156,7 @@ public class FeedReader.FavIconManager : GLib.Object { catch (Error e) { Logger.warning(@"Request for $siteURL failed: " + e.message); - return false; + return null; } if(html != null && message_html.status_code == 200) { @@ -185,43 +166,47 @@ public class FeedReader.FavIconManager : GLib.Object { if(doc == null) { Logger.debug(@"Utils.downloadFavIcon: parsing html on $siteURL failed"); - return false; + return null; } - // check for <link rel="icon"> - var xpath = grabberUtils.getURL(doc, "//link[@rel='icon']"); + try + { + // check for <link rel="icon"> + var xpath = grabberUtils.getURL(doc, "//link[@rel='icon']"); - if(xpath == null) - // check for <link rel="shortcut icon"> - xpath = grabberUtils.getURL(doc, "//link[@rel='shortcut icon']"); + if(xpath == null) + // check for <link rel="shortcut icon"> + xpath = grabberUtils.getURL(doc, "//link[@rel='shortcut icon']"); - if(xpath == null) - // check for <link rel="apple-touch-icon"> - xpath = grabberUtils.getURL(doc, "//link[@rel='apple-touch-icon']"); + if(xpath == null) + // check for <link rel="apple-touch-icon"> + xpath = grabberUtils.getURL(doc, "//link[@rel='apple-touch-icon']"); - if(xpath != null) + if(xpath != null) + { + xpath = grabberUtils.completeURL(xpath, siteURL); + return yield downloadIcon(feed, xpath, cancellable, icon_path); + } + } + finally { - xpath = grabberUtils.completeURL(xpath, siteURL); - if(yield downloadIcon(feed, xpath, cancellable, icon_path)) - return true; + delete doc; } - - delete doc; } - return false; + return null; } - private async bool downloadIcon(Feed feed, string? icon_url, Cancellable? cancellable, string icon_path = GLib.Environment.get_user_data_dir() + "/feedreader/data/feed_icons/") + private async InputStream? downloadIcon(Feed feed, string? icon_url, Cancellable? cancellable, string icon_path = GLib.Environment.get_user_data_dir() + "/feedreader/data/feed_icons/") throws GLib.Error { if(icon_url == "" || icon_url == null || GLib.Uri.parse_scheme(icon_url) == null) { Logger.warning(@"Utils.downloadIcon: icon_url not valid $icon_url"); - return false; + return null; } if(!yield Utils.ensure_path(icon_path)) - return false; + return null; string filename_prefix = icon_path + feed.getFeedFileName(); string local_filename = @"$filename_prefix.ico"; @@ -250,46 +235,29 @@ public class FeedReader.FavIconManager : GLib.Object { catch (Error e) { Logger.error(@"Request for $icon_url failed: " + e.message); - return false; + return null; } var status = message.status_code; if(status == 304) { - return true; + var file = File.new_for_path(local_filename); + return yield file.read_async(); } else if(status == 404 || data == null) { - return false; + return null; } else if(status == 200) { var local_file = File.new_for_path(local_filename); - uint8[]? local_data = null; try { - uint8[] contents; - yield local_file.load_contents_async(null, out contents, null); - local_data = contents; + yield local_file.replace_contents_async(data, null, false, FileCreateFlags.NONE, null, null); } - catch(IOError.NOT_FOUND e){} catch(Error e) { - Logger.error(@"Error reading icon $local_filename: %s".printf(e.message)); - } - - if(local_data == null - ||(local_data != null && data != local_data)) - { - try - { - yield local_file.replace_contents_async(data, null, false, FileCreateFlags.NONE, null, null); - FileUtils.set_data(local_filename, data); - } - catch(Error e) - { - Logger.error("Error writing icon: %s".printf(e.message)); - return false; - } + Logger.error("Error writing icon: %s".printf(e.message)); + return null; } metadata.etag = message.response_headers.get_one("ETag"); @@ -303,7 +271,6 @@ public class FeedReader.FavIconManager : GLib.Object { var parts = header.split("="); if(parts.length < 2 || parts[0] != "max-age") continue; - Logger.debug(parts[1]); var seconds = int64.parse(parts[1]); var expires = new DateTime.now_utc(); expires.add_seconds(seconds); @@ -313,9 +280,10 @@ public class FeedReader.FavIconManager : GLib.Object { } metadata.last_modified = message.response_headers.get_one("Last-Modified"); yield metadata.save_to_file_async(metadata_filename); - return true; + return new MemoryInputStream.from_data(data); } + Logger.warning(@"Could not download icon for feed: %s $icon_url, got response code $status".printf(feed.getFeedID())); - return false; + return null; } } diff --git a/src/FeedReader.vala b/src/FeedReader.vala index 5fdd7af2..29ea63ed 100644 --- a/src/FeedReader.vala +++ b/src/FeedReader.vala @@ -84,14 +84,6 @@ namespace FeedReader { }); }); - FeedReaderBackend.get_default().reloadFavIcons.connect(() => { - GLib.Idle.add(() => { - Logger.debug("FeedReader: reloadFavIcons"); - ColumnView.get_default().reloadFavIcons(); - return GLib.Source.REMOVE; - }); - }); - FeedReaderBackend.get_default().updateArticleList.connect(() => { GLib.Idle.add(() => { Logger.debug("FeedReader: updateArticleList"); diff --git a/src/Widgets/ArticleList/ArticleList.vala b/src/Widgets/ArticleList/ArticleList.vala index 48d14909..8e174ccb 100644 --- a/src/Widgets/ArticleList/ArticleList.vala +++ b/src/Widgets/ArticleList/ArticleList.vala @@ -779,9 +779,4 @@ public class FeedReader.ArticleList : Gtk.Overlay { { m_currentList.emptyList(); } - - public void reloadFavIcons() - { - m_currentList.reloadFavIcons(); - } } diff --git a/src/Widgets/ArticleList/ArticleListBox.vala b/src/Widgets/ArticleList/ArticleListBox.vala index 1920b81a..67444bcf 100644 --- a/src/Widgets/ArticleList/ArticleListBox.vala +++ b/src/Widgets/ArticleList/ArticleListBox.vala @@ -662,15 +662,4 @@ public class FeedReader.ArticleListBox : Gtk.ListBox { addRow(ArticleListBalance.NONE, false, false); return true; } - - public void reloadFavIcons() - { - var children = this.get_children(); - foreach(var row in children) - { - var tmpRow = row as ArticleRow; - if(tmpRow != null) - tmpRow.reloadFavIcon.begin(); - } - } } diff --git a/src/Widgets/ArticleRow.vala b/src/Widgets/ArticleRow.vala index 667ce3b3..bf31b1f2 100644 --- a/src/Widgets/ArticleRow.vala +++ b/src/Widgets/ArticleRow.vala @@ -137,12 +137,7 @@ public class FeedReader.ArticleRow : Gtk.ListBoxRow { m_marked_eventbox.leave_notify_event.connect(markedIconLeave); m_marked_eventbox.button_press_event.connect(markedIconClicked); - m_icon = createFavIcon(); - FavIconManager.get_default().ReloadFavIcon.connect(feed => { - if(m_article.getFeedID() == feed.getFeedID()) - reloadFavIcon.begin(); - }); icon_box.pack_start(m_icon, true, true, 0); icon_box.pack_end(m_unread_eventbox, false, false, 10); @@ -260,23 +255,20 @@ public class FeedReader.ArticleRow : Gtk.ListBoxRow { return false; } - public async void reloadFavIcon(Gtk.Image? inIcon = null) - { - Feed feed = DataBase.readOnly().read_feed(m_article.getFeedID()); - var icon = yield FavIconManager.get_default().getIcon(feed); - if(icon != null) - { - if(inIcon == null) - m_icon.pixbuf = icon; - else - inIcon.pixbuf = icon; - } - } - private Gtk.Image createFavIcon() { var icon = new Gtk.Image.from_icon_name("feed-rss-symbolic", Gtk.IconSize.LARGE_TOOLBAR); - reloadFavIcon.begin(icon); + + var manager = FavIconManager.get_default(); + Feed feed = DataBase.readOnly().read_feed(m_article.getFeedID()); + manager.getIcon.begin(feed, (obj, res) => { + var pixbuf = manager.getIcon.end(res); + if(pixbuf != null) + { + icon.pixbuf = pixbuf; + } + }); + return icon; } diff --git a/src/Widgets/ColumnView.vala b/src/Widgets/ColumnView.vala index 7f7078fb..0ae9aea3 100644 --- a/src/Widgets/ColumnView.vala +++ b/src/Widgets/ColumnView.vala @@ -517,10 +517,4 @@ public class FeedReader.ColumnView : Gtk.Paned { m_article_view.clearContent(); m_feedList.clear(); } - - public void reloadFavIcons() - { - m_articleList.reloadFavIcons(); - m_feedList.reloadFavIcons(); - } } diff --git a/src/Widgets/FeedList.vala b/src/Widgets/FeedList.vala index fa6ce6e7..ddaadc8c 100644 --- a/src/Widgets/FeedList.vala +++ b/src/Widgets/FeedList.vala @@ -1186,17 +1186,4 @@ public class FeedReader.feedList : Gtk.ScrolledWindow { return false; } - - public void reloadFavIcons() - { - var FeedChildList = m_list.get_children(); - - foreach(Gtk.Widget row in FeedChildList) - { - var tmpRow = row as FeedRow; - if(tmpRow != null) - tmpRow.reloadFavIcon.begin(); - } - } - } diff --git a/src/Widgets/FeedRow.vala b/src/Widgets/FeedRow.vala index f8dc4890..540d77fd 100644 --- a/src/Widgets/FeedRow.vala +++ b/src/Widgets/FeedRow.vala @@ -44,10 +44,6 @@ public class FeedReader.FeedRow : Gtk.ListBoxRow { var rowhight = 30; m_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0); m_icon = createFavIcon(); - FavIconManager.get_default().ReloadFavIcon.connect(feed => { - if(m_feed.getFeedID() == feed.getFeedID()) - reloadFavIcon.begin(); - }); m_icon.margin_start = level * 24; @@ -151,30 +147,20 @@ public class FeedReader.FeedRow : Gtk.ListBoxRow { } } - public async void reloadFavIcon(Gtk.Image? inIcon = null) - { - var icon = yield FavIconManager.get_default().getIcon(m_feed); - if(icon == null) - return; - - if(inIcon == null) - { - m_icon.pixbuf = icon; - m_icon.get_style_context().remove_class("fr-sidebar-symbolic"); - } - else - { - inIcon.pixbuf = icon; - inIcon.get_style_context().remove_class("fr-sidebar-symbolic"); - } - } - public Gtk.Image createFavIcon() { var icon = new Gtk.Image.from_icon_name("feed-rss-symbolic", Gtk.IconSize.LARGE_TOOLBAR); icon.get_style_context().add_class("fr-sidebar-symbolic"); - reloadFavIcon.begin(icon); + var manager = FavIconManager.get_default(); + manager.getIcon.begin(m_feed, (obj, res) => { + var pixbuf = manager.getIcon.end(res); + if(pixbuf != null) + { + icon.pixbuf = pixbuf; + icon.get_style_context().remove_class("fr-sidebar-symbolic"); + } + }); return icon; } |