diff --git a/CHANGELOG.md b/CHANGELOG.md
index 882e8be..4b2a0d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
+* Downlaoding and loading images now is done asynchronously and is not blocking programs execution.
+[#7](https://gitlab.gnome.org/alatiera/Hammond/issues/7)
## [0.3.1] - 2018-03-28
diff --git a/hammond-data/src/models/new_podcast.rs b/hammond-data/src/models/new_podcast.rs
index 4eb3ac6..97aaaa0 100644
--- a/hammond-data/src/models/new_podcast.rs
+++ b/hammond-data/src/models/new_podcast.rs
@@ -231,9 +231,9 @@ mod tests {
love, a crashed UFO, an alien body, and an impossible heist unlike any \
ever attempted - scripted by Mac Rogers, the award-winning playwright \
and writer of the multi-million download The Message and LifeAfter.
";
- let img = "https://dfkfj8j276wwv.cloudfront.net/images/2c/5f/a0/1a/2c5fa01a-ae78-4a8c-\
- b183-7311d2e436c3/b3a4aa57a576bb662191f2a6bc2a436c8c4ae256ecffaff5c4c54fd42e\
- 923914941c264d01efb1833234b52c9530e67d28a8cebbe3d11a4bc0fbbdf13ecdf1c3.jpeg";
+ let img = "https://dfkfj8j276wwv.cloudfront.net/images/2c/5f/a0/1a/2c5fa01a-ae78-4a8c-\
+ b183-7311d2e436c3/b3a4aa57a576bb662191f2a6bc2a436c8c4ae256ecffaff5c4c54fd42e\
+ 923914941c264d01efb1833234b52c9530e67d28a8cebbe3d11a4bc0fbbdf13ecdf1c3.jpeg";
NewPodcastBuilder::default()
.title("Steal the Stars")
diff --git a/hammond-gtk/src/main.rs b/hammond-gtk/src/main.rs
index d865cfb..a648f9b 100644
--- a/hammond-gtk/src/main.rs
+++ b/hammond-gtk/src/main.rs
@@ -26,15 +26,14 @@ extern crate hammond_downloader;
extern crate humansize;
extern crate loggerv;
extern crate open;
+extern crate rayon;
extern crate regex;
extern crate reqwest;
extern crate send_cell;
extern crate serde_json;
extern crate take_mut;
extern crate url;
-// extern crate rayon;
-// use rayon::prelude::*;
use log::Level;
use gtk::prelude::*;
diff --git a/hammond-gtk/src/utils.rs b/hammond-gtk/src/utils.rs
index f8a6bcb..247dd26 100644
--- a/hammond-gtk/src/utils.rs
+++ b/hammond-gtk/src/utils.rs
@@ -1,8 +1,13 @@
#![cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
-use failure::Error;
use gdk_pixbuf::Pixbuf;
use gio::{Settings, SettingsExt};
+use glib;
+use gtk;
+use gtk::prelude::*;
+
+use failure::Error;
+use rayon;
use regex::Regex;
use reqwest;
use send_cell::SendCell;
@@ -18,7 +23,7 @@ use hammond_downloader::downloader;
use std::collections::{HashMap, HashSet};
use std::sync::{Mutex, RwLock};
use std::sync::Arc;
-use std::sync::mpsc::Sender;
+use std::sync::mpsc::*;
use std::thread;
use app::Action;
@@ -128,6 +133,11 @@ fn refresh_feed(source: Option>, sender: Sender) -> Result<(
lazy_static! {
static ref CACHED_PIXBUFS: RwLock>>> =
{ RwLock::new(HashMap::new()) };
+ static ref COVER_DL_REGISTRY: RwLock> = RwLock::new(HashSet::new());
+ static ref THREADPOOL: rayon::ThreadPool = rayon::ThreadPoolBuilder::new()
+ .breadth_first()
+ .build()
+ .unwrap();
}
// Since gdk_pixbuf::Pixbuf is refference counted and every episode,
@@ -137,25 +147,78 @@ lazy_static! {
// GObjects do not implement Send trait, so SendCell is a way around that.
// Also lazy_static requires Sync trait, so that's what the mutexes are.
// TODO: maybe use something that would just scale to requested size?
-pub fn get_pixbuf_from_path(pd: &PodcastCoverQuery, size: u32) -> Result {
+pub fn set_image_from_path(
+ image: >k::Image,
+ pd: Arc,
+ size: u32,
+) -> Result<(), Error> {
{
- let hashmap = CACHED_PIXBUFS
+ // Check if there's an active download about this show cover.
+ // If there is, a callback will be set so this function will be called again.
+ // If the download succedes, there should be a quick return from the pixbuf cache_image
+ // If it fails another download will be scheduled.
+ let reg_guard = COVER_DL_REGISTRY
.read()
- .map_err(|_| format_err!("Failed to get a lock on the pixbuf cache mutex."))?;
- if let Some(px) = hashmap.get(&(pd.id(), size)) {
- let m = px.lock()
- .map_err(|_| format_err!("Failed to lock pixbuf mutex."))?;
- return Ok(m.clone().into_inner());
+ .map_err(|err| format_err!("Cover Registry: {}.", err))?;
+ if reg_guard.contains(&pd.id()) {
+ let callback = clone!(image, pd => move || {
+ let _ = set_image_from_path(&image, pd.clone(), size);
+ glib::Continue(false)
+ });
+ gtk::timeout_add(250, callback);
+ return Ok(());
}
}
- let img_path = downloader::cache_image(pd)?;
- let px = Pixbuf::new_from_file_at_scale(&img_path, size as i32, size as i32, true)?;
- let mut hashmap = CACHED_PIXBUFS
- .write()
- .map_err(|_| format_err!("Failed to lock pixbuf mutex."))?;
- hashmap.insert((pd.id(), size), Mutex::new(SendCell::new(px.clone())));
- Ok(px)
+ {
+ let hashmap = CACHED_PIXBUFS
+ .read()
+ .map_err(|err| format_err!("Pixbuf HashMap: {}", err))?;
+ if let Some(px) = hashmap.get(&(pd.id(), size)) {
+ let m = px.lock()
+ .map_err(|err| format_err!("SendCell Mutex: {}", err))?;
+ let px = m.try_get().ok_or_else(|| {
+ format_err!("Pixbuf was accessed from a different thread than created")
+ })?;
+ image.set_from_pixbuf(px);
+ return Ok(());
+ }
+ }
+
+ let (sender, receiver) = channel();
+ let pd_ = pd.clone();
+ THREADPOOL.spawn(move || {
+ {
+ if let Ok(mut guard) = COVER_DL_REGISTRY.write() {
+ guard.insert(pd_.id());
+ }
+ }
+ let _ = sender.send(downloader::cache_image(&pd_));
+ {
+ if let Ok(mut guard) = COVER_DL_REGISTRY.write() {
+ guard.remove(&pd_.id());
+ }
+ }
+ });
+
+ let image = image.clone();
+ let s = size as i32;
+ gtk::timeout_add(25, move || {
+ if let Ok(path) = receiver.try_recv() {
+ if let Ok(path) = path {
+ if let Ok(px) = Pixbuf::new_from_file_at_scale(&path, s, s, true) {
+ if let Ok(mut hashmap) = CACHED_PIXBUFS.write() {
+ hashmap.insert((pd.id(), size), Mutex::new(SendCell::new(px.clone())));
+ image.set_from_pixbuf(&px);
+ }
+ }
+ }
+ glib::Continue(false)
+ } else {
+ glib::Continue(true)
+ }
+ });
+ Ok(())
}
#[inline]
@@ -200,8 +263,8 @@ pub fn time_period_to_duration(time: i64, period: &str) -> Duration {
#[cfg(test)]
mod tests {
use super::*;
- use hammond_data::Source;
- use hammond_data::dbqueries;
+ // use hammond_data::Source;
+ // use hammond_data::dbqueries;
#[test]
fn test_time_period_to_duration() {
@@ -221,23 +284,25 @@ mod tests {
assert_eq!(time, time_period_to_duration(time, "seconds").num_seconds());
}
- #[test]
+ // #[test]
// This test inserts an rss feed to your `XDG_DATA/hammond/hammond.db` so we make it explicit
// to run it.
- #[ignore]
- fn test_get_pixbuf_from_path() {
- let url = "https://web.archive.org/web/20180120110727if_/https://rss.acast.com/thetipoff";
- // Create and index a source
- let source = Source::from_url(url).unwrap();
- // Copy it's id
- let sid = source.id();
- pipeline::run(vec![source], true).unwrap();
+ // #[ignore]
+ // Disabled till https://gitlab.gnome.org/alatiera/Hammond/issues/56
+ // fn test_set_image_from_path() {
+ // let url = "https://web.archive.org/web/20180120110727if_/https://rss.acast.com/thetipoff";
+ // Create and index a source
+ // let source = Source::from_url(url).unwrap();
+ // Copy it's id
+ // let sid = source.id();
+ // pipeline::run(vec![source], true).unwrap();
- // Get the Podcast
- let pd = dbqueries::get_podcast_from_source_id(sid).unwrap();
- let pxbuf = get_pixbuf_from_path(&pd.into(), 256);
- assert!(pxbuf.is_ok());
- }
+ // Get the Podcast
+ // let img = gtk::Image::new();
+ // let pd = dbqueries::get_podcast_from_source_id(sid).unwrap().into();
+ // let pxbuf = set_image_from_path(&img, Arc::new(pd), 256);
+ // assert!(pxbuf.is_ok());
+ // }
#[test]
fn test_itunes_to_rss() {
diff --git a/hammond-gtk/src/views/episodes.rs b/hammond-gtk/src/views/episodes.rs
index 43b9bf7..e8f51c5 100644
--- a/hammond-gtk/src/views/episodes.rs
+++ b/hammond-gtk/src/views/episodes.rs
@@ -7,9 +7,10 @@ use hammond_data::EpisodeWidgetQuery;
use hammond_data::dbqueries;
use app::Action;
-use utils::{get_ignored_shows, get_pixbuf_from_path};
+use utils::{get_ignored_shows, set_image_from_path};
use widgets::EpisodeWidget;
+use std::sync::Arc;
use std::sync::mpsc::Sender;
#[derive(Debug, Clone)]
@@ -227,9 +228,7 @@ impl EpisodesViewWidget {
}
fn set_cover(&self, podcast_id: i32) -> Result<(), Error> {
- let pd = dbqueries::get_podcast_cover_from_id(podcast_id)?;
- let img = get_pixbuf_from_path(&pd, 64)?;
- self.image.set_from_pixbuf(&img);
- Ok(())
+ let pd = Arc::new(dbqueries::get_podcast_cover_from_id(podcast_id)?);
+ set_image_from_path(&self.image, pd, 64)
}
}
diff --git a/hammond-gtk/src/views/shows.rs b/hammond-gtk/src/views/shows.rs
index 2a59984..67ba529 100644
--- a/hammond-gtk/src/views/shows.rs
+++ b/hammond-gtk/src/views/shows.rs
@@ -2,11 +2,11 @@ use failure::Error;
use gtk;
use gtk::prelude::*;
-use hammond_data::Podcast;
+use hammond_data::{Podcast, PodcastCoverQuery};
use hammond_data::dbqueries;
use app::Action;
-use utils::{get_ignored_shows, get_pixbuf_from_path};
+use utils::{get_ignored_shows, set_image_from_path};
use std::sync::Arc;
use std::sync::mpsc::Sender;
@@ -57,7 +57,7 @@ impl ShowsPopulated {
let ignore = get_ignored_shows()?;
let podcasts = dbqueries::get_podcasts_filter(&ignore)?;
- podcasts.into_iter().map(Arc::new).for_each(|parent| {
+ podcasts.into_iter().for_each(|parent| {
let flowbox_child = ShowsChild::new(parent);
self.flowbox.add(&flowbox_child.child);
});
@@ -116,25 +116,23 @@ impl Default for ShowsChild {
}
impl ShowsChild {
- pub fn new(pd: Arc) -> ShowsChild {
+ pub fn new(pd: Podcast) -> ShowsChild {
let child = ShowsChild::default();
child.init(pd);
child
}
- fn init(&self, pd: Arc) {
+ fn init(&self, pd: Podcast) {
self.container.set_tooltip_text(pd.title());
+ WidgetExt::set_name(&self.child, &pd.id().to_string());
- if let Err(err) = self.set_cover(pd.clone()) {
+ let pd = Arc::new(pd.into());
+ if let Err(err) = self.set_cover(pd) {
error!("Failed to set a cover: {}", err)
}
-
- WidgetExt::set_name(&self.child, &pd.id().to_string());
}
- fn set_cover(&self, pd: Arc) -> Result<(), Error> {
- let image = get_pixbuf_from_path(&pd.clone().into(), 256)?;
- self.cover.set_from_pixbuf(&image);
- Ok(())
+ fn set_cover(&self, pd: Arc) -> Result<(), Error> {
+ set_image_from_path(&self.cover, pd, 256)
}
}
diff --git a/hammond-gtk/src/widgets/show.rs b/hammond-gtk/src/widgets/show.rs
index 5a304fe..ca5ca04 100644
--- a/hammond-gtk/src/widgets/show.rs
+++ b/hammond-gtk/src/widgets/show.rs
@@ -10,7 +10,7 @@ use hammond_data::dbqueries;
use hammond_data::utils::replace_extra_spaces;
use app::Action;
-use utils::get_pixbuf_from_path;
+use utils::set_image_from_path;
use widgets::episode::episodes_listbox;
use std::sync::Arc;
@@ -113,9 +113,7 @@ impl ShowWidget {
/// Set the show cover.
fn set_cover(&self, pd: Arc) -> Result<(), Error> {
- let image = get_pixbuf_from_path(&pd.into(), 128)?;
- self.cover.set_from_pixbuf(&image);
- Ok(())
+ set_image_from_path(&self.cover, Arc::new(pd.into()), 128)
}
/// Set the descripton text.