diff options
author | Jan Lukas Gernert <jangernert@gmail.com> | 2017-10-22 22:29:50 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-22 22:29:50 +0300 |
commit | f96d7624514c6ce4b8b1c5501ae846412a32aa0d (patch) | |
tree | e82ba9f5f1b038716f6192ce1615081cdf206fb9 | |
parent | d40594ac81ef0724b79a4f8c075be5f291a4d240 (diff) | |
parent | 2eb3813d3fce07786d572611a802b8613dbe53f4 (diff) |
Merge pull request #577 from jangernert/hopefully_last_favicon_adjustments
Try to download favicons again after certain amount of time
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | plugins/backend/feedbin/feedbinAPI.vala | 16 | ||||
-rw-r--r-- | plugins/backend/local/SuggestedFeedRow.vala | 7 | ||||
-rw-r--r-- | src/FavIcon.vala | 324 | ||||
-rw-r--r-- | src/FavIconManager.vala | 295 | ||||
-rw-r--r-- | src/Structs.vala | 13 | ||||
-rw-r--r-- | src/Utils.vala | 4 | ||||
-rw-r--r-- | src/Widgets/ArticleRow.vala | 15 | ||||
-rw-r--r-- | src/Widgets/FeedRow.vala | 17 |
9 files changed, 376 insertions, 317 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 02b5f2f3..2fae0670 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,7 +98,7 @@ set(UI_PACKAGES gio-2.0) vala_precompile(FEEDREADER ${FEEDREADER_NAME} - src/FavIconManager.vala + src/FavIcon.vala src/FeedReader.vala src/Widgets/AddPopover.vala src/Widgets/ArticleRow.vala diff --git a/plugins/backend/feedbin/feedbinAPI.vala b/plugins/backend/feedbin/feedbinAPI.vala index 872705e4..d82deed3 100644 --- a/plugins/backend/feedbin/feedbinAPI.vala +++ b/plugins/backend/feedbin/feedbinAPI.vala @@ -39,10 +39,18 @@ public class FeedbinAPI : Object { if(user_agent != null) m_session.user_agent = user_agent; - m_session.authenticate.connect((msg, auth, retrying) => { - if(!retrying) - auth.authenticate(this.username, this.password); - }); + m_session.authenticate.connect(authenticate); + } + + ~FeedbinAPI() + { + m_session.authenticate.disconnect(authenticate); + } + + private void authenticate(Soup.Message msg, Soup.Auth auth, bool retrying) + { + if(!retrying) + auth.authenticate(this.username, this.password); } private Soup.Message request(string method, string path, string? input = null) throws FeedbinError diff --git a/plugins/backend/local/SuggestedFeedRow.vala b/plugins/backend/local/SuggestedFeedRow.vala index f9913106..b4a91cbd 100644 --- a/plugins/backend/local/SuggestedFeedRow.vala +++ b/plugins/backend/local/SuggestedFeedRow.vala @@ -71,14 +71,11 @@ public class FeedReader.SuggestedFeedRow : Gtk.ListBoxRow { private async void load_favicon(Gtk.Stack iconStack, Feed feed, string iconURL) { Gtk.Image? icon = null; - var pixBuf = yield FavIconManager.get_default().getIcon(feed); + var pixBuf = yield FavIcon.for_feed(feed).get_pixbuf(); if(pixBuf != null) icon = new Gtk.Image.from_pixbuf(pixBuf); - - if(icon == null) - { + else icon = new Gtk.Image.from_icon_name("feed-rss-symbolic", Gtk.IconSize.LARGE_TOOLBAR); - } iconStack.add_named(icon, "icon"); show_all(); diff --git a/src/FavIcon.vala b/src/FavIcon.vala new file mode 100644 index 00000000..50506418 --- /dev/null +++ b/src/FavIcon.vala @@ -0,0 +1,324 @@ +// This file is part of FeedReader. +// +// FeedReader is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// FeedReader is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with FeedReader. If not, see <http://www.gnu.org/licenses/>. +public class FeedReader.FavIcon : GLib.Object +{ + private static string m_icon_path = GLib.Environment.get_user_data_dir() + "/feedreader/data/feed_icons/"; + private static Gee.Map<string, FavIcon> m_map = null; + + public static FavIcon for_feed(Feed feed) + { + if(m_map == null) + m_map = new Gee.HashMap<string, FavIcon>(); + + var feed_id = feed.getFeedID(); + var icon = m_map.get(feed_id); + if(icon == null) + { + icon = new FavIcon(feed); + m_map.set(feed_id, icon); + } + + return icon; + } + + private Feed m_feed; + private Gee.Promise<Gdk.Pixbuf?> m_pixbuf = null; + private ResourceMetadata m_metadata; + + public signal void pixbuf_changed(Feed feed, Gdk.Pixbuf pixbuf); + + private FavIcon(Feed feed) + { + m_feed = feed; + } + + public async Gdk.Pixbuf? get_pixbuf() + { + // wait for ready + expired so we don't make a bunch of requests at once + if(m_pixbuf == null || (m_pixbuf.future.ready && m_metadata.is_expired())) + { + m_pixbuf = new Gee.Promise<Gdk.Pixbuf?>(); + load.begin((obj, res) => { + load.end(res); + }); + } + try + { + return yield m_pixbuf.future.wait_async(); + } + catch(Error e) + { + Logger.error("FavIcon.get_pixbuf: " + e.message); + return null; + } + } + + private async void load() + { + try + { + var stream = yield downloadFavIcon(); + if(stream == null) + return; + + var pixbuf = yield new Gdk.Pixbuf.from_stream_async(stream); + stream.close(); + + if(pixbuf.get_height() <= 1 && pixbuf.get_width() <= 1) + { + Logger.warning("FavIcon: Icon for feed %s is too small".printf(m_feed.getTitle())); + return; + } + pixbuf = pixbuf.scale_simple(24, 24, Gdk.InterpType.BILINEAR); + + m_pixbuf.set_value(pixbuf); + if(pixbuf != null) + pixbuf_changed(m_feed, pixbuf); + } + catch(Error e) + { + Logger.error("FavIcon.load: " + e.message); + } + finally + { + if(!m_pixbuf.future.ready) + m_pixbuf.set_value(null); + } + } + + private async InputStream? downloadFavIcon(GLib.Cancellable? cancellable = null) throws GLib.Error + { + string filename_prefix = m_icon_path + m_feed.getFeedFileName(); + string local_filename = @"$filename_prefix.ico"; + string metadata_filename = @"$filename_prefix.txt"; + + if(!yield Utils.ensure_path(m_icon_path)) + return null; + + m_metadata = yield ResourceMetadata.from_file_async(metadata_filename); + DateTime? expires = m_metadata.expires; + + if(cancellable != null && cancellable.is_cancelled()) + return null; + + bool use_cached = false; + if(!m_metadata.is_expired()) + { + Logger.debug("Favicon for %s is valid until %s, skipping this time".printf(m_feed.getTitle(), expires.to_string())); + use_cached = true; + } + else if(!NetworkMonitor.get_default().network_available) + { + Logger.debug("Network not available, skipping favicon download"); + use_cached = true; + } + if(use_cached) + { + var file = File.new_for_path(local_filename); + try + { + return yield file.read_async(); + } + catch(IOError.NOT_FOUND e) + { + return null; + } + } + + try + { + var obvious_icons = new Gee.ArrayList<string>(); + + if(m_feed.getIconURL() != null) + obvious_icons.add(m_feed.getIconURL()); + + // try domainname/favicon.ico + var uri = new Soup.URI(m_feed.getURL()); + string? siteURL = null; + if(uri != null) + { + string hostname = uri.get_host(); + siteURL = uri.get_scheme() + "://" + hostname; + + var icon_url = siteURL; + if(!icon_url.has_suffix("/")) + icon_url += "/"; + icon_url += "favicon.ico"; + obvious_icons.add(icon_url); + } + + // Try to find one of those icons + foreach(var url in obvious_icons) + { + var stream = yield downloadIcon(url, cancellable); + if(stream != null) + return stream; + + if(cancellable != null && cancellable.is_cancelled()) + return null; + } + + // If all else fails, download html and parse to find location of favicon + if(siteURL == null) + return null; + + var message_html = new Soup.Message("GET", siteURL); + if(Settings.tweaks().get_boolean("do-not-track")) + message_html.request_headers.append("DNT", "1"); + + string html; + try + { + var bodyStream = yield Utils.getSession().send_async(message_html); + html = (string)yield Utils.inputStreamToArray(bodyStream, cancellable); + } + catch (Error e) + { + Logger.warning(@"Request for $siteURL failed: " + e.message); + return null; + } + if(html != null && message_html.status_code == 200) + { + var html_cntx = new Html.ParserCtxt(); + html_cntx.use_options(Html.ParserOption.NOERROR + Html.ParserOption.NOWARNING); + Html.Doc* doc = html_cntx.read_doc(html, siteURL, null, Html.ParserOption.NOERROR + Html.ParserOption.NOWARNING); + if(doc == null) + { + Logger.debug(@"Utils.downloadFavIcon: parsing html on $siteURL failed"); + return null; + } + + 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="apple-touch-icon"> + xpath = grabberUtils.getURL(doc, "//link[@rel='apple-touch-icon']"); + + if(xpath != null) + { + xpath = grabberUtils.completeURL(xpath, siteURL); + return yield downloadIcon(xpath, cancellable); + } + } + finally + { + delete doc; + } + } + + return null; + } + finally + { + var default_expires = new DateTime.now_utc().add_days(Constants.REDOWNLOAD_FAVICONS_AFTER_DAYS); + if(m_metadata.expires == null || m_metadata.expires.to_unix() < default_expires.to_unix()) + { + m_metadata.expires = default_expires; + } + yield m_metadata.save_to_file_async(metadata_filename); + } + } + + private async InputStream? downloadIcon(string? icon_url, Cancellable? cancellable) 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 null; + } + + string filename_prefix = m_icon_path + m_feed.getFeedFileName(); + string local_filename = @"$filename_prefix.ico"; + + string etag = m_metadata.etag; + string last_modified = m_metadata.last_modified; + + Logger.debug(@"Utils.downloadIcon: url = $icon_url"); + var message = new Soup.Message("GET", icon_url); + if(Settings.tweaks().get_boolean("do-not-track")) + message.request_headers.append("DNT", "1"); + + if(etag != null) + message.request_headers.append("If-None-Match", etag); + if(last_modified != null) + message.request_headers.append("If-Modified-Since", last_modified); + + uint8[]? data; + try + { + var bodyStream = yield Utils.getSession().send_async(message, cancellable); + data = yield Utils.inputStreamToArray(bodyStream, cancellable); + } + catch (Error e) + { + Logger.error(@"Request for $icon_url failed: " + e.message); + return null; + } + var status = message.status_code; + if(status == 304) + { + var file = File.new_for_path(local_filename); + return yield file.read_async(); + } + else if(status == 404 || data == null) + { + return null; + } + else if(status == 200) + { + var local_file = File.new_for_path(local_filename); + try + { + yield local_file.replace_contents_async(data, null, false, FileCreateFlags.NONE, null, null); + } + catch(Error e) + { + Logger.error("Error writing icon: %s".printf(e.message)); + return null; + } + + m_metadata.etag = message.response_headers.get_one("ETag"); + m_metadata.last_modified = message.response_headers.get_one("Last-Modified"); + + var cache_control = message.response_headers.get_list("Cache-Control"); + if(cache_control != null) + { + foreach(var header in message.response_headers.get_list("Cache-Control").split(",")) + { + var parts = header.split("="); + if(parts.length < 2 || parts[0] != "max-age") + continue; + var seconds = int64.parse(parts[1]); + var expires = new DateTime.now_utc(); + expires.add_seconds(seconds); + m_metadata.expires = expires; + } + } + + m_metadata.last_modified = message.response_headers.get_one("Last-Modified"); + return new MemoryInputStream.from_data(data); + } + + Logger.warning(@"Could not download icon for feed: %s $icon_url, got response code $status".printf(m_feed.getFeedID())); + return null; + } +} diff --git a/src/FavIconManager.vala b/src/FavIconManager.vala deleted file mode 100644 index fcd9ae71..00000000 --- a/src/FavIconManager.vala +++ /dev/null @@ -1,295 +0,0 @@ -// This file is part of FeedReader. -// -// FeedReader is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// FeedReader is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with FeedReader. If not, see <http://www.gnu.org/licenses/>. - -public class FeedReader.FavIconManager : GLib.Object { - - 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 static FavIconManager get_default() - { - if(m_cache == null) - m_cache = new FavIconManager(); - - return m_cache; - } - - private FavIconManager() - { - } - - public async Gdk.Pixbuf? getIcon(Feed feed) - { - try - { - var feed_id = feed.getFeedID(); - var future = m_map.get(feed_id); - if(future == null) - { - var promise = new Gee.Promise<Gdk.Pixbuf?>(); - try - { - future = promise.future; - m_map.set(feed_id, future); - - var stream = yield downloadFavIcon(feed); - if(stream == null) - return null; - - var pixbuf = yield new Gdk.Pixbuf.from_stream_async(stream); - stream.close(); - - 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); - - promise.set_value(pixbuf); - } - finally - { - if(!future.ready) - promise.set_value(null); - } - } - return yield future.wait_async(); - } - catch(Error e) - { - Logger.error("FavIconManager.getIcon: %s".printf(e.message)); - return null; - } - } - - 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"; - string metadata_filename = @"$filename_prefix.txt"; - - var metadata = yield ResourceMetadata.from_file_async(metadata_filename); - DateTime? expires = metadata.expires; - - if(cancellable != null && cancellable.is_cancelled()) - return null; - - var now = new DateTime.now_utc(); - if(expires != null) - { - 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())); - var file = File.new_for_path(local_filename); - try - { - return yield file.read_async(); - } - catch(IOError.NOT_FOUND e) - { - return null; - } - } - } - - var default_expires = now.add_days(Constants.REDOWNLOAD_FAVICONS_AFTER_DAYS); - if(metadata.expires == null || metadata.expires.to_unix() < default_expires.to_unix()) - { - metadata.expires = default_expires; - yield metadata.save_to_file_async(metadata_filename); - } - - var obvious_icons = new Gee.ArrayList<string>(); - - if(feed.getIconURL() != null) - obvious_icons.add(feed.getIconURL()); - - // try domainname/favicon.ico - var uri = new Soup.URI(feed.getURL()); - string? siteURL = null; - if(uri != null) - { - string hostname = uri.get_host(); - siteURL = uri.get_scheme() + "://" + hostname; - - var icon_url = siteURL; - if(!icon_url.has_suffix("/")) - icon_url += "/"; - icon_url += "favicon.ico"; - obvious_icons.add(icon_url); - } - - // Try to find one of those icons - foreach(var url in obvious_icons) - { - var stream = yield downloadIcon(feed, url, cancellable, icon_path); - if(stream != null) - return stream; - - if(cancellable != null && cancellable.is_cancelled()) - return null; - } - - // If all else fails, download html and parse to find location of favicon - if(siteURL == null) - return null; - - var message_html = new Soup.Message("GET", siteURL); - if(Settings.tweaks().get_boolean("do-not-track")) - message_html.request_headers.append("DNT", "1"); - - string html; - try - { - var bodyStream = yield Utils.getSession().send_async(message_html); - html = (string)yield Utils.inputStreamToArray(bodyStream, cancellable); - } - catch (Error e) - { - Logger.warning(@"Request for $siteURL failed: " + e.message); - return null; - } - if(html != null && message_html.status_code == 200) - { - var html_cntx = new Html.ParserCtxt(); - html_cntx.use_options(Html.ParserOption.NOERROR + Html.ParserOption.NOWARNING); - Html.Doc* doc = html_cntx.read_doc(html, siteURL, null, Html.ParserOption.NOERROR + Html.ParserOption.NOWARNING); - if(doc == null) - { - Logger.debug(@"Utils.downloadFavIcon: parsing html on $siteURL failed"); - return null; - } - - 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="apple-touch-icon"> - xpath = grabberUtils.getURL(doc, "//link[@rel='apple-touch-icon']"); - - if(xpath != null) - { - xpath = grabberUtils.completeURL(xpath, siteURL); - return yield downloadIcon(feed, xpath, cancellable, icon_path); - } - } - finally - { - delete doc; - } - } - - return null; - } - - 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 null; - } - - if(!yield Utils.ensure_path(icon_path)) - return null; - - string filename_prefix = icon_path + feed.getFeedFileName(); - string local_filename = @"$filename_prefix.ico"; - string metadata_filename = @"$filename_prefix.txt"; - - var metadata = yield ResourceMetadata.from_file_async(metadata_filename); - string etag = metadata.etag; - string last_modified = metadata.last_modified; - - Logger.debug(@"Utils.downloadIcon: url = $icon_url"); - var message = new Soup.Message("GET", icon_url); - if(Settings.tweaks().get_boolean("do-not-track")) - message.request_headers.append("DNT", "1"); - - if(etag != null) - message.request_headers.append("If-None-Match", etag); - if(last_modified != null) - message.request_headers.append("If-Modified-Since", last_modified); - - uint8[]? data; - try - { - var bodyStream = yield Utils.getSession().send_async(message, cancellable); - data = yield Utils.inputStreamToArray(bodyStream, cancellable); - } - catch (Error e) - { - Logger.error(@"Request for $icon_url failed: " + e.message); - return null; - } - var status = message.status_code; - if(status == 304) - { - var file = File.new_for_path(local_filename); - return yield file.read_async(); - } - else if(status == 404 || data == null) - { - return null; - } - else if(status == 200) - { - var local_file = File.new_for_path(local_filename); - try - { - yield local_file.replace_contents_async(data, null, false, FileCreateFlags.NONE, null, null); - } - catch(Error e) - { - Logger.error("Error writing icon: %s".printf(e.message)); - return null; - } - - metadata.etag = message.response_headers.get_one("ETag"); - metadata.last_modified = message.response_headers.get_one("Last-Modified"); - - var cache_control = message.response_headers.get_list("Cache-Control"); - metadata.expires = new DateTime.now_utc().add_days(Constants.REDOWNLOAD_FAVICONS_AFTER_DAYS);; - if(cache_control != null) - { - foreach(var header in message.response_headers.get_list("Cache-Control").split(",")) - { - var parts = header.split("="); - if(parts.length < 2 || parts[0] != "max-age") - continue; - var seconds = int64.parse(parts[1]); - var expires = new DateTime.now_utc(); - expires.add_seconds(seconds); - if(expires.to_unix() > metadata.expires.to_unix()) - metadata.expires = expires; - } - } - - metadata.last_modified = message.response_headers.get_one("Last-Modified"); - yield metadata.save_to_file_async(metadata_filename); - 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 null; - } -} diff --git a/src/Structs.vala b/src/Structs.vala index b3dec470..8b06abbc 100644 --- a/src/Structs.vala +++ b/src/Structs.vala @@ -34,7 +34,7 @@ namespace FeedReader { } } - private struct ResourceMetadata + public struct ResourceMetadata { private const string CACHE_GROUP = "cache"; private const string ETAG_KEY = "etag"; @@ -131,6 +131,17 @@ namespace FeedReader { } } } + + public bool is_expired() + { + if(expires == null) + return true; + + if(expires.compare(new DateTime.now_utc()) == 1) + return false; + + return true; + } } } diff --git a/src/Utils.vala b/src/Utils.vala index d2385e2f..7660bda3 100644 --- a/src/Utils.vala +++ b/src/Utils.vala @@ -548,6 +548,10 @@ public class FeedReader.Utils : GLib.Object { path.make_directory_with_parents(); return true; } + catch(IOError.EXISTS e) + { + return true; + } catch(Error e) { Logger.error(@"ensure_path: Failed to create folder $path_str: " + e.message); diff --git a/src/Widgets/ArticleRow.vala b/src/Widgets/ArticleRow.vala index dc614e8b..04f67982 100644 --- a/src/Widgets/ArticleRow.vala +++ b/src/Widgets/ArticleRow.vala @@ -268,20 +268,23 @@ public class FeedReader.ArticleRow : Gtk.ListBoxRow { { var icon = new Gtk.Image.from_icon_name("feed-rss-symbolic", Gtk.IconSize.LARGE_TOOLBAR); - 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); + var favicon = FavIcon.for_feed(feed); + favicon.get_pixbuf.begin((obj, res) => { + var pixbuf = favicon.get_pixbuf.end(res); if(pixbuf != null) - { icon.pixbuf = pixbuf; - } + }); + ulong handler_id = favicon.pixbuf_changed.connect((feed, pixbuf) => { + icon.pixbuf = pixbuf; + }); + icon.destroy.connect(() => { + favicon.disconnect(handler_id); }); return icon; } - private Gtk.Window getFeedIconWindow() { var icon = createFavIcon(); diff --git a/src/Widgets/FeedRow.vala b/src/Widgets/FeedRow.vala index d32f7cae..08df0313 100644 --- a/src/Widgets/FeedRow.vala +++ b/src/Widgets/FeedRow.vala @@ -44,7 +44,6 @@ public class FeedReader.FeedRow : Gtk.ListBoxRow { var rowhight = 30; m_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0); m_icon = createFavIcon(); - m_icon.margin_start = level * 24; m_label = new Gtk.Label(m_feed.getTitle()); @@ -153,15 +152,23 @@ public class FeedReader.FeedRow : Gtk.ListBoxRow { var icon = new Gtk.Image.from_icon_name("feed-rss-symbolic", Gtk.IconSize.LARGE_TOOLBAR); icon.get_style_context().add_class("fr-sidebar-symbolic"); - var manager = FavIconManager.get_default(); - manager.getIcon.begin(m_feed, (obj, res) => { - var pixbuf = manager.getIcon.end(res); + var favicon = FavIcon.for_feed(m_feed); + favicon.get_pixbuf.begin((obj, res) => { + var pixbuf = favicon.get_pixbuf.end(res); if(pixbuf != null) { icon.pixbuf = pixbuf; - icon.get_style_context().remove_class("fr-sidebar-symbolic"); + m_icon.get_style_context().remove_class("fr-sidebar-symbolic"); } }); + ulong handler_id = favicon.pixbuf_changed.connect((feed, pixbuf) => { + icon.pixbuf = pixbuf; + icon.get_style_context().remove_class("fr-sidebar-symbolic"); + }); + icon.destroy.connect(() => { + favicon.disconnect(handler_id); + }); + return icon; } |