Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/jangernert/FeedReader.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Lukas Gernert <jangernert@gmail.com>2017-10-22 22:29:50 +0300
committerGitHub <noreply@github.com>2017-10-22 22:29:50 +0300
commitf96d7624514c6ce4b8b1c5501ae846412a32aa0d (patch)
treee82ba9f5f1b038716f6192ce1615081cdf206fb9
parentd40594ac81ef0724b79a4f8c075be5f291a4d240 (diff)
parent2eb3813d3fce07786d572611a802b8613dbe53f4 (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.txt2
-rw-r--r--plugins/backend/feedbin/feedbinAPI.vala16
-rw-r--r--plugins/backend/local/SuggestedFeedRow.vala7
-rw-r--r--src/FavIcon.vala324
-rw-r--r--src/FavIconManager.vala295
-rw-r--r--src/Structs.vala13
-rw-r--r--src/Utils.vala4
-rw-r--r--src/Widgets/ArticleRow.vala15
-rw-r--r--src/Widgets/FeedRow.vala17
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;
}