Merge branch '40-gtk-unwrap-cleanup' into 'master'

Resolve "Clean up the unwraps in the Gtk Client."

Closes #40

See merge request alatiera/Hammond!17
This commit is contained in:
Jordan Petridis 2018-02-07 02:12:11 +00:00
commit 8cbae4050e
13 changed files with 462 additions and 327 deletions

View File

@ -212,7 +212,7 @@ impl Source {
/// Updates the validator Http Headers. /// Updates the validator Http Headers.
/// ///
/// Consumes `self` and Returns the corresponding `Feed` Object. /// Consumes `self` and Returns the corresponding `Feed` Object.
// TODO: Refactor into TryInto once it lands on stable. // Refactor into TryInto once it lands on stable.
pub fn into_feed( pub fn into_feed(
self, self,
client: &Client<HttpsConnector<HttpConnector>>, client: &Client<HttpsConnector<HttpConnector>>,

View File

@ -172,65 +172,59 @@ pub fn get_episode(
ep.save()?; ep.save()?;
}; };
let res = download_into( let path = download_into(
download_folder, download_folder,
&ep.rowid().to_string(), &ep.rowid().to_string(),
ep.uri().unwrap(), ep.uri().unwrap(),
progress, progress,
); )?;
if let Ok(path) = res { // If download succedes set episode local_uri to dlpath.
// If download succedes set episode local_uri to dlpath. ep.set_local_uri(Some(&path));
ep.set_local_uri(Some(&path));
// Over-write episode lenght // Over-write episode lenght
let size = fs::metadata(path); let size = fs::metadata(path);
if let Ok(s) = size { if let Ok(s) = size {
ep.set_length(Some(s.len() as i32)) ep.set_length(Some(s.len() as i32))
}; };
ep.save()?; ep.save()?;
Ok(()) Ok(())
} else {
error!("Something whent wrong while downloading.");
Err(res.unwrap_err())
}
} }
pub fn cache_image(pd: &PodcastCoverQuery) -> Option<String> { pub fn cache_image(pd: &PodcastCoverQuery) -> Result<String, DownloadError> {
let url = pd.image_uri()?.to_owned(); let url = pd.image_uri()
.ok_or_else(|| DownloadError::NoImageLocation)?
.to_owned();
if url == "" { if url == "" {
return None; return Err(DownloadError::NoImageLocation);
} }
let cache_download_fold = format!("{}{}", HAMMOND_CACHE.to_str()?, pd.title().to_owned()); let cache_path = HAMMOND_CACHE
.to_str()
.ok_or_else(|| DownloadError::InvalidCacheLocation)?;
let cache_download_fold = format!("{}{}", cache_path, pd.title().to_owned());
// Weird glob magic. // Weird glob magic.
if let Ok(mut foo) = glob(&format!("{}/cover.*", cache_download_fold)) { if let Ok(mut foo) = glob(&format!("{}/cover.*", cache_download_fold)) {
// For some reason there is no .first() method so nth(0) is used // For some reason there is no .first() method so nth(0) is used
let path = foo.nth(0).and_then(|x| x.ok()); let path = foo.nth(0).and_then(|x| x.ok());
if let Some(p) = path { if let Some(p) = path {
return Some(p.to_str()?.into()); return Ok(p.to_str()
.ok_or_else(|| DownloadError::InvalidCachedImageLocation)?
.into());
} }
}; };
// Create the folders if they don't exist. // Create the folders if they don't exist.
DirBuilder::new() DirBuilder::new()
.recursive(true) .recursive(true)
.create(&cache_download_fold) .create(&cache_download_fold)?;
.ok()?;
match download_into(&cache_download_fold, "cover", &url, None) { let path = download_into(&cache_download_fold, "cover", &url, None)?;
Ok(path) => { info!("Cached img into: {}", &path);
info!("Cached img into: {}", &path); Ok(path)
Some(path)
}
Err(err) => {
error!("Failed to get feed image.");
error!("Error: {}", err);
None
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -240,6 +234,8 @@ mod tests {
use hammond_data::dbqueries; use hammond_data::dbqueries;
use hammond_data::pipeline; use hammond_data::pipeline;
use std::fs;
#[test] #[test]
// This test inserts an rss feed to your `XDG_DATA/hammond/hammond.db` so we make it explicit // This test inserts an rss feed to your `XDG_DATA/hammond/hammond.db` so we make it explicit
// to run it. // to run it.
@ -262,6 +258,7 @@ mod tests {
HAMMOND_CACHE.to_str().unwrap(), HAMMOND_CACHE.to_str().unwrap(),
pd.title() pd.title()
); );
assert_eq!(img_path, Some(foo_)); assert_eq!(img_path.unwrap(), foo_);
fs::remove_file(foo_).unwrap();
} }
} }

View File

@ -10,10 +10,16 @@ pub enum DownloadError {
DataError(#[cause] DataError), DataError(#[cause] DataError),
#[fail(display = "Io error: {}", _0)] #[fail(display = "Io error: {}", _0)]
IoError(#[cause] io::Error), IoError(#[cause] io::Error),
#[fail(display = "The Download was cancelled")]
DownloadCancelled,
#[fail(display = "Unexpected server response: {}", _0)] #[fail(display = "Unexpected server response: {}", _0)]
UnexpectedResponse(reqwest::StatusCode), UnexpectedResponse(reqwest::StatusCode),
#[fail(display = "The Download was cancelled.")]
DownloadCancelled,
#[fail(display = "Remote Image location not found.")]
NoImageLocation,
#[fail(display = "Failed to parse CacheLocation.")]
InvalidCacheLocation,
#[fail(display = "Failed to parse Cached Image Location.")]
InvalidCachedImageLocation,
} }
impl From<reqwest::Error> for DownloadError { impl From<reqwest::Error> for DownloadError {

View File

@ -46,7 +46,7 @@ pub struct App {
impl App { impl App {
pub fn new() -> App { pub fn new() -> App {
let application = gtk::Application::new("org.gnome.Hammond", ApplicationFlags::empty()) let application = gtk::Application::new("org.gnome.Hammond", ApplicationFlags::empty())
.expect("Initialization failed..."); .expect("Application Initialization failed...");
// Weird magic I copy-pasted that sets the Application Name in the Shell. // Weird magic I copy-pasted that sets the Application Name in the Shell.
glib::set_application_name("Hammond"); glib::set_application_name("Hammond");
@ -65,7 +65,8 @@ impl App {
let (sender, receiver) = channel(); let (sender, receiver) = channel();
// Create a content instance // Create a content instance
let content = Arc::new(Content::new(sender.clone())); let content =
Arc::new(Content::new(sender.clone()).expect("Content Initialization failed."));
// Create the headerbar // Create the headerbar
let header = Arc::new(Header::new(content.clone(), &window, sender.clone())); let header = Arc::new(Header::new(content.clone(), &window, sender.clone()));
@ -87,7 +88,7 @@ impl App {
let sender = self.sender.clone(); let sender = self.sender.clone();
// Update the feeds right after the Application is initialized. // Update the feeds right after the Application is initialized.
gtk::timeout_add_seconds(2, move || { gtk::timeout_add_seconds(2, move || {
utils::refresh_feed(None, sender.clone()); utils::refresh_feed_wrapper(None, sender.clone());
glib::Continue(false) glib::Continue(false)
}); });
@ -95,7 +96,7 @@ impl App {
// Auto-updater, runs every hour. // Auto-updater, runs every hour.
// TODO: expose the interval in which it run to a user setting. // TODO: expose the interval in which it run to a user setting.
gtk::timeout_add_seconds(3600, move || { gtk::timeout_add_seconds(3600, move || {
utils::refresh_feed(None, sender.clone()); utils::refresh_feed_wrapper(None, sender.clone());
glib::Continue(true) glib::Continue(true)
}); });
@ -122,9 +123,9 @@ impl App {
match receiver.recv_timeout(Duration::from_millis(10)) { match receiver.recv_timeout(Duration::from_millis(10)) {
Ok(Action::UpdateSources(source)) => { Ok(Action::UpdateSources(source)) => {
if let Some(s) = source { if let Some(s) = source {
utils::refresh_feed(Some(vec![s]), sender.clone()) utils::refresh_feed_wrapper(Some(vec![s]), sender.clone());
} else { } else {
utils::refresh_feed(None, sender.clone()) utils::refresh_feed_wrapper(None, sender.clone());
} }
} }
Ok(Action::RefreshAllViews) => content.update(), Ok(Action::RefreshAllViews) => content.update(),
@ -134,7 +135,12 @@ impl App {
Ok(Action::RefreshWidgetIfSame(id)) => content.update_widget_if_same(id), Ok(Action::RefreshWidgetIfSame(id)) => content.update_widget_if_same(id),
Ok(Action::RefreshEpisodesView) => content.update_episode_view(), Ok(Action::RefreshEpisodesView) => content.update_episode_view(),
Ok(Action::RefreshEpisodesViewBGR) => content.update_episode_view_if_baground(), Ok(Action::RefreshEpisodesViewBGR) => content.update_episode_view_if_baground(),
Ok(Action::ReplaceWidget(ref pd)) => content.get_shows().replace_widget(pd), Ok(Action::ReplaceWidget(ref pd)) => {
if let Err(err) = content.get_shows().replace_widget(pd) {
error!("Something went wrong while trying to update the ShowWidget.");
error!("Error: {}", err);
}
}
Ok(Action::ShowWidgetAnimated) => content.get_shows().switch_widget_animated(), Ok(Action::ShowWidgetAnimated) => content.get_shows().switch_widget_animated(),
Ok(Action::ShowShowsAnimated) => content.get_shows().switch_podcasts_animated(), Ok(Action::ShowShowsAnimated) => content.get_shows().switch_podcasts_animated(),
Ok(Action::HeaderBarShowTile(title)) => headerbar.switch_to_back(&title), Ok(Action::HeaderBarShowTile(title)) => headerbar.switch_to_back(&title),

View File

@ -2,6 +2,8 @@ use gtk;
use gtk::Cast; use gtk::Cast;
use gtk::prelude::*; use gtk::prelude::*;
use failure::Error;
use hammond_data::Podcast; use hammond_data::Podcast;
use hammond_data::dbqueries; use hammond_data::dbqueries;
@ -24,20 +26,20 @@ pub struct Content {
} }
impl Content { impl Content {
pub fn new(sender: Sender<Action>) -> Content { pub fn new(sender: Sender<Action>) -> Result<Content, Error> {
let stack = gtk::Stack::new(); let stack = gtk::Stack::new();
let episodes = Arc::new(EpisodeStack::new(sender.clone())); let episodes = Arc::new(EpisodeStack::new(sender.clone())?);
let shows = Arc::new(ShowStack::new(sender.clone())); let shows = Arc::new(ShowStack::new(sender.clone())?);
stack.add_titled(&episodes.stack, "episodes", "Episodes"); stack.add_titled(&episodes.stack, "episodes", "Episodes");
stack.add_titled(&shows.stack, "shows", "Shows"); stack.add_titled(&shows.stack, "shows", "Shows");
Content { Ok(Content {
stack, stack,
shows, shows,
episodes, episodes,
sender, sender,
} })
} }
pub fn update(&self) { pub fn update(&self) {
@ -46,33 +48,46 @@ impl Content {
self.update_widget() self.update_widget()
} }
// TODO: Maybe propagate the error?
pub fn update_episode_view(&self) { pub fn update_episode_view(&self) {
self.episodes.update(); if let Err(err) = self.episodes.update() {
error!("Something went wrong while trying to update the episode view.");
error!("Error: {}", err);
}
} }
pub fn update_episode_view_if_baground(&self) { pub fn update_episode_view_if_baground(&self) {
if self.stack.get_visible_child_name() != Some("episodes".into()) { if self.stack.get_visible_child_name() != Some("episodes".into()) {
self.episodes.update(); self.update_episode_view();
} }
} }
pub fn update_shows_view(&self) { pub fn update_shows_view(&self) {
self.shows.update_podcasts(); if let Err(err) = self.shows.update_podcasts() {
error!("Something went wrong while trying to update the ShowsView.");
error!("Error: {}", err);
}
} }
pub fn update_widget(&self) { pub fn update_widget(&self) {
self.shows.update_widget(); if let Err(err) = self.shows.update_widget() {
error!("Something went wrong while trying to update the Show Widget.");
error!("Error: {}", err);
}
} }
pub fn update_widget_if_same(&self, pid: i32) { pub fn update_widget_if_same(&self, pid: i32) {
self.shows.update_widget_if_same(pid); if let Err(err) = self.shows.update_widget_if_same(pid) {
error!("Something went wrong while trying to update the Show Widget.");
error!("Error: {}", err);
}
} }
pub fn update_widget_if_visible(&self) { pub fn update_widget_if_visible(&self) {
if self.stack.get_visible_child_name() == Some("shows".to_string()) if self.stack.get_visible_child_name() == Some("shows".to_string())
&& self.shows.get_stack().get_visible_child_name() == Some("widget".to_string()) && self.shows.get_stack().get_visible_child_name() == Some("widget".to_string())
{ {
self.shows.update_widget(); self.update_widget();
} }
} }
@ -92,7 +107,7 @@ pub struct ShowStack {
} }
impl ShowStack { impl ShowStack {
fn new(sender: Sender<Action>) -> ShowStack { fn new(sender: Sender<Action>) -> Result<ShowStack, Error> {
let stack = gtk::Stack::new(); let stack = gtk::Stack::new();
let show = ShowStack { let show = ShowStack {
@ -100,7 +115,7 @@ impl ShowStack {
sender: sender.clone(), sender: sender.clone(),
}; };
let pop = ShowsPopulated::new(sender.clone()); let pop = ShowsPopulated::new(sender.clone())?;
let widget = ShowWidget::default(); let widget = ShowWidget::default();
let empty = EmptyView::new(); let empty = EmptyView::new();
@ -114,7 +129,7 @@ impl ShowStack {
show.stack.set_visible_child_name("podcasts") show.stack.set_visible_child_name("podcasts")
} }
show Ok(show)
} }
// pub fn update(&self) { // pub fn update(&self) {
@ -122,29 +137,27 @@ impl ShowStack {
// self.update_podcasts(); // self.update_podcasts();
// } // }
pub fn update_podcasts(&self) { pub fn update_podcasts(&self) -> Result<(), Error> {
let vis = self.stack.get_visible_child_name().unwrap(); let vis = self.stack
.get_visible_child_name()
.ok_or_else(|| format_err!("Failed to get visible child name."))?;
let old = self.stack let old = self.stack
.get_child_by_name("podcasts") .get_child_by_name("podcasts")
// This is guaranted to exists, based on `ShowStack::new()`. .ok_or_else(|| format_err!("Faild to get \"podcasts\" child from the stack."))?
.unwrap()
.downcast::<gtk::Box>() .downcast::<gtk::Box>()
// This is guaranted to be a Box based on the `ShowsPopulated` impl. .map_err(|_| format_err!("Failed to downcast stack child to a Box."))?;
.unwrap();
debug!("Name: {:?}", WidgetExt::get_name(&old)); debug!("Name: {:?}", WidgetExt::get_name(&old));
let scrolled_window = old.get_children() let scrolled_window = old.get_children()
.first() .first()
// This is guaranted to exist based on the show_widget.ui file. .ok_or_else(|| format_err!("Box container has no childs."))?
.unwrap()
.clone() .clone()
.downcast::<gtk::ScrolledWindow>() .downcast::<gtk::ScrolledWindow>()
// This is guaranted based on the show_widget.ui file. .map_err(|_| format_err!("Failed to downcast stack child to a ScrolledWindow."))?;
.unwrap();
debug!("Name: {:?}", WidgetExt::get_name(&scrolled_window)); debug!("Name: {:?}", WidgetExt::get_name(&scrolled_window));
let pop = ShowsPopulated::new(self.sender.clone()); let pop = ShowsPopulated::new(self.sender.clone())?;
// Copy the vertical scrollbar adjustment from the old view into the new one. // Copy the vertical scrollbar adjustment from the old view into the new one.
scrolled_window scrolled_window
.get_vadjustment() .get_vadjustment()
@ -162,16 +175,15 @@ impl ShowStack {
} }
old.destroy(); old.destroy();
Ok(())
} }
pub fn replace_widget(&self, pd: &Podcast) { pub fn replace_widget(&self, pd: &Podcast) -> Result<(), Error> {
let old = self.stack let old = self.stack
.get_child_by_name("widget") .get_child_by_name("widget")
// This is guaranted to exists, based on `ShowStack::new()`. .ok_or_else(|| format_err!("Faild to get \"widget\" child from the stack."))?
.unwrap()
.downcast::<gtk::Box>() .downcast::<gtk::Box>()
// This is guaranted to be a Box based on the `ShowWidget` impl. .map_err(|_| format_err!("Failed to downcast stack child to a Box."))?;
.unwrap();
debug!("Name: {:?}", WidgetExt::get_name(&old)); debug!("Name: {:?}", WidgetExt::get_name(&old));
let new = ShowWidget::new(pd, self.sender.clone()); let new = ShowWidget::new(pd, self.sender.clone());
@ -185,12 +197,10 @@ impl ShowStack {
if newid == oldid { if newid == oldid {
let scrolled_window = old.get_children() let scrolled_window = old.get_children()
.first() .first()
// This is guaranted to exist based on the show_widget.ui file. .ok_or_else(|| format_err!("Box container has no childs."))?
.unwrap()
.clone() .clone()
.downcast::<gtk::ScrolledWindow>() .downcast::<gtk::ScrolledWindow>()
// This is guaranted based on the show_widget.ui file. .map_err(|_| format_err!("Failed to downcast stack child to a ScrolledWindow."))?;
.unwrap();
debug!("Name: {:?}", WidgetExt::get_name(&scrolled_window)); debug!("Name: {:?}", WidgetExt::get_name(&scrolled_window));
// Copy the vertical scrollbar adjustment from the old view into the new one. // Copy the vertical scrollbar adjustment from the old view into the new one.
@ -201,34 +211,42 @@ impl ShowStack {
self.stack.remove(&old); self.stack.remove(&old);
self.stack.add_named(&new.container, "widget"); self.stack.add_named(&new.container, "widget");
Ok(())
} }
pub fn update_widget(&self) { pub fn update_widget(&self) -> Result<(), Error> {
let vis = self.stack.get_visible_child_name().unwrap(); let vis = self.stack
let old = self.stack.get_child_by_name("widget").unwrap(); .get_visible_child_name()
.ok_or_else(|| format_err!("Failed to get visible child name."))?;
let old = self.stack
.get_child_by_name("widget")
.ok_or_else(|| format_err!("Faild to get \"widget\" child from the stack."))?;
let id = WidgetExt::get_name(&old); let id = WidgetExt::get_name(&old);
if id == Some("GtkBox".to_string()) || id.is_none() { if id == Some("GtkBox".to_string()) || id.is_none() {
return; return Ok(());
} }
let pd = dbqueries::get_podcast_from_id(id.unwrap().parse::<i32>().unwrap()); let id = id.ok_or_else(|| format_err!("Failed to get widget's name."))?;
if let Ok(pd) = pd { let pd = dbqueries::get_podcast_from_id(id.parse::<i32>()?)?;
self.replace_widget(&pd); self.replace_widget(&pd)?;
self.stack.set_visible_child_name(&vis); self.stack.set_visible_child_name(&vis);
old.destroy(); old.destroy();
} Ok(())
} }
// Only update widget if it's podcast_id is equal to pid. // Only update widget if it's podcast_id is equal to pid.
pub fn update_widget_if_same(&self, pid: i32) { pub fn update_widget_if_same(&self, pid: i32) -> Result<(), Error> {
let old = self.stack.get_child_by_name("widget").unwrap(); let old = self.stack
.get_child_by_name("widget")
.ok_or_else(|| format_err!("Faild to get \"widget\" child from the stack."))?;
let id = WidgetExt::get_name(&old); let id = WidgetExt::get_name(&old);
if id != Some(pid.to_string()) || id.is_none() { if id != Some(pid.to_string()) || id.is_none() {
return; debug!("Different widget. Early return");
return Ok(());
} }
self.update_widget(); self.update_widget()
} }
pub fn switch_podcasts_animated(&self) { pub fn switch_podcasts_animated(&self) {
@ -253,8 +271,8 @@ pub struct EpisodeStack {
} }
impl EpisodeStack { impl EpisodeStack {
fn new(sender: Sender<Action>) -> EpisodeStack { fn new(sender: Sender<Action>) -> Result<EpisodeStack, Error> {
let episodes = EpisodesView::new(sender.clone()); let episodes = EpisodesView::new(sender.clone())?;
let empty = EmptyView::new(); let empty = EmptyView::new();
let stack = gtk::Stack::new(); let stack = gtk::Stack::new();
@ -267,30 +285,27 @@ impl EpisodeStack {
stack.set_visible_child_name("episodes"); stack.set_visible_child_name("episodes");
} }
EpisodeStack { stack, sender } Ok(EpisodeStack { stack, sender })
} }
pub fn update(&self) { // Look into refactoring to a state-machine.
pub fn update(&self) -> Result<(), Error> {
let old = self.stack let old = self.stack
.get_child_by_name("episodes") .get_child_by_name("episodes")
// This is guaranted to exists, based on `EpisodeStack::new()`. .ok_or_else(|| format_err!("Faild to get \"episodes\" child from the stack."))?
.unwrap()
.downcast::<gtk::Box>() .downcast::<gtk::Box>()
// This is guaranted to be a Box based on the `EpisodesView` impl. .map_err(|_| format_err!("Failed to downcast stack child to a Box."))?;
.unwrap();
debug!("Name: {:?}", WidgetExt::get_name(&old)); debug!("Name: {:?}", WidgetExt::get_name(&old));
let scrolled_window = old.get_children() let scrolled_window = old.get_children()
.first() .first()
// This is guaranted to exist based on the episodes_view.ui file. .ok_or_else(|| format_err!("Box container has no childs."))?
.unwrap()
.clone() .clone()
.downcast::<gtk::ScrolledWindow>() .downcast::<gtk::ScrolledWindow>()
// This is guaranted based on the episodes_view.ui file. .map_err(|_| format_err!("Failed to downcast stack child to a ScrolledWindow."))?;
.unwrap();
debug!("Name: {:?}", WidgetExt::get_name(&scrolled_window)); debug!("Name: {:?}", WidgetExt::get_name(&scrolled_window));
let eps = EpisodesView::new(self.sender.clone()); let eps = EpisodesView::new(self.sender.clone())?;
// Copy the vertical scrollbar adjustment from the old view into the new one. // Copy the vertical scrollbar adjustment from the old view into the new one.
scrolled_window scrolled_window
.get_vadjustment() .get_vadjustment()
@ -306,5 +321,7 @@ impl EpisodeStack {
} }
old.destroy(); old.destroy();
Ok(())
} }
} }

View File

@ -1,9 +1,11 @@
use failure::Error;
use failure::ResultExt;
use gtk; use gtk;
use gtk::prelude::*; use gtk::prelude::*;
use url::Url;
use hammond_data::Source; use hammond_data::Source;
use hammond_data::dbqueries; use hammond_data::dbqueries;
use url::Url;
use std::sync::Arc; use std::sync::Arc;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
@ -55,6 +57,7 @@ impl Default for Header {
} }
} }
// TODO: Refactor components into smaller state machines
impl Header { impl Header {
pub fn new(content: Arc<Content>, window: &gtk::Window, sender: Sender<Action>) -> Header { pub fn new(content: Arc<Content>, window: &gtk::Window, sender: Sender<Action>) -> Header {
let h = Header::default(); let h = Header::default();
@ -72,18 +75,24 @@ impl Header {
self.switch.set_stack(&content.get_stack()); self.switch.set_stack(&content.get_stack());
new_url.connect_changed(clone!(add_button => move |url| { new_url.connect_changed(clone!(add_button => move |url| {
on_url_change(url, &result_label, &add_button); if let Err(err) = on_url_change(url, &result_label, &add_button) {
error!("Error: {}", err);
}
})); }));
add_button.connect_clicked(clone!(add_popover, new_url, sender => move |_| { add_button.connect_clicked(clone!(add_popover, new_url, sender => move |_| {
on_add_bttn_clicked(&new_url, sender.clone()); if let Err(err) = on_add_bttn_clicked(&new_url, sender.clone()) {
error!("Error: {}", err);
}
add_popover.hide(); add_popover.hide();
})); }));
self.add_toggle.set_popover(&add_popover); self.add_toggle.set_popover(&add_popover);
self.update_button.connect_clicked(move |_| { self.update_button.connect_clicked(move |_| {
sender.send(Action::UpdateSources(None)).unwrap(); sender
.send(Action::UpdateSources(None))
.expect("Action channel blew up.");
}); });
self.about_button self.about_button
@ -142,28 +151,32 @@ impl Header {
} }
} }
fn on_add_bttn_clicked(entry: &gtk::Entry, sender: Sender<Action>) { fn on_add_bttn_clicked(entry: &gtk::Entry, sender: Sender<Action>) -> Result<(), Error> {
let url = entry.get_text().unwrap_or_default(); let url = entry.get_text().unwrap_or_default();
let source = Source::from_url(&url); let source = Source::from_url(&url).context("Failed to convert url to a Source entry.")?;
entry.set_text("");
if source.is_ok() { sender
entry.set_text(""); .send(Action::UpdateSources(Some(source)))
sender.send(Action::UpdateSources(source.ok())).unwrap(); .context("App channel blew up.")?;
} else { Ok(())
error!("Something went wrong.");
error!("Error: {:?}", source.unwrap_err());
}
} }
fn on_url_change(entry: &gtk::Entry, result: &gtk::Label, add_button: &gtk::Button) { fn on_url_change(
let uri = entry.get_text().unwrap(); entry: &gtk::Entry,
result: &gtk::Label,
add_button: &gtk::Button,
) -> Result<(), Error> {
let uri = entry
.get_text()
.ok_or_else(|| format_err!("GtkEntry blew up somehow."))?;
debug!("Url: {}", uri); debug!("Url: {}", uri);
let url = Url::parse(&uri); let url = Url::parse(&uri);
// TODO: refactor to avoid duplication // TODO: refactor to avoid duplication
match url { match url {
Ok(u) => { Ok(u) => {
if !dbqueries::source_exists(u.as_str()).unwrap() { if !dbqueries::source_exists(u.as_str())? {
add_button.set_sensitive(true); add_button.set_sensitive(true);
result.hide(); result.hide();
result.set_label(""); result.set_label("");
@ -172,6 +185,7 @@ fn on_url_change(entry: &gtk::Entry, result: &gtk::Label, add_button: &gtk::Butt
result.set_label("Show already exists."); result.set_label("Show already exists.");
result.show(); result.show();
} }
Ok(())
} }
Err(err) => { Err(err) => {
add_button.set_sensitive(false); add_button.set_sensitive(false);
@ -182,6 +196,7 @@ fn on_url_change(entry: &gtk::Entry, result: &gtk::Label, add_button: &gtk::Butt
} else { } else {
result.hide(); result.hide();
} }
Ok(())
} }
} }
} }

View File

@ -9,8 +9,8 @@ extern crate gtk;
#[macro_use] #[macro_use]
extern crate failure; extern crate failure;
#[macro_use] // #[macro_use]
extern crate failure_derive; // extern crate failure_derive;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
#[macro_use] #[macro_use]
@ -65,15 +65,15 @@ use app::App;
fn main() { fn main() {
// TODO: make the the logger a cli -vv option // TODO: make the the logger a cli -vv option
loggerv::init_with_level(Level::Info).unwrap(); loggerv::init_with_level(Level::Info).expect("Error initializing loggerv.");
gtk::init().expect("Error initializing gtk"); gtk::init().expect("Error initializing gtk.");
static_resource::init().expect("Something went wrong with the resource file initialization."); static_resource::init().expect("Something went wrong with the resource file initialization.");
// Add custom style // Add custom style
let provider = gtk::CssProvider::new(); let provider = gtk::CssProvider::new();
gtk::CssProvider::load_from_resource(&provider, "/org/gnome/hammond/gtk/style.css"); gtk::CssProvider::load_from_resource(&provider, "/org/gnome/hammond/gtk/style.css");
gtk::StyleContext::add_provider_for_screen( gtk::StyleContext::add_provider_for_screen(
&gdk::Screen::get_default().unwrap(), &gdk::Screen::get_default().expect("Error initializing gtk css provider."),
&provider, &provider,
600, 600,
); );

View File

@ -1,3 +1,5 @@
use failure::Error;
// use hammond_data::Episode; // use hammond_data::Episode;
use hammond_data::dbqueries; use hammond_data::dbqueries;
use hammond_downloader::downloader::{get_episode, DownloadProgress}; use hammond_downloader::downloader::{get_episode, DownloadProgress};
@ -11,6 +13,9 @@ use std::sync::mpsc::Sender;
// use std::path::PathBuf; // use std::path::PathBuf;
use std::thread; use std::thread;
// This is messy, undocumented and hacky af.
// I am terrible at writting downloaders and download managers.
#[derive(Debug)] #[derive(Debug)]
pub struct Progress { pub struct Progress {
total_bytes: u64, total_bytes: u64,
@ -73,14 +78,15 @@ lazy_static! {
}; };
} }
pub fn add(id: i32, directory: &str, sender: Sender<Action>) { pub fn add(id: i32, directory: &str, sender: Sender<Action>) -> Result<(), Error> {
// Create a new `Progress` struct to keep track of dl progress. // Create a new `Progress` struct to keep track of dl progress.
let prog = Arc::new(Mutex::new(Progress::default())); let prog = Arc::new(Mutex::new(Progress::default()));
{ {
if let Ok(mut m) = ACTIVE_DOWNLOADS.write() { let mut m = ACTIVE_DOWNLOADS
m.insert(id, prog.clone()); .write()
} .map_err(|_| format_err!("Failed to get a lock on the mutex."))?;
m.insert(id, prog.clone());
} }
let dir = directory.to_owned(); let dir = directory.to_owned();
@ -88,12 +94,11 @@ pub fn add(id: i32, directory: &str, sender: Sender<Action>) {
if let Ok(episode) = dbqueries::get_episode_from_rowid(id) { if let Ok(episode) = dbqueries::get_episode_from_rowid(id) {
let pid = episode.podcast_id(); let pid = episode.podcast_id();
let id = episode.rowid(); let id = episode.rowid();
get_episode(&mut episode.into(), dir.as_str(), Some(prog))
.err() if let Err(err) = get_episode(&mut episode.into(), dir.as_str(), Some(prog)) {
.map(|err| { error!("Error while trying to download an episode");
error!("Error while trying to download an episode"); error!("Error: {}", err);
error!("Error: {}", err); }
});
{ {
if let Ok(mut m) = ACTIVE_DOWNLOADS.write() { if let Ok(mut m) = ACTIVE_DOWNLOADS.write() {
@ -107,10 +112,16 @@ pub fn add(id: i32, directory: &str, sender: Sender<Action>) {
// } // }
// } // }
sender.send(Action::RefreshEpisodesView).unwrap(); sender
sender.send(Action::RefreshWidgetIfSame(pid)).unwrap(); .send(Action::RefreshEpisodesView)
.expect("Action channel blew up.");
sender
.send(Action::RefreshWidgetIfSame(pid))
.expect("Action channel blew up.");
} }
}); });
Ok(())
} }
#[cfg(test)] #[cfg(test)]
@ -152,7 +163,7 @@ mod tests {
let (sender, _rx) = channel(); let (sender, _rx) = channel();
let download_fold = get_download_folder(&pd.title()).unwrap(); let download_fold = get_download_folder(&pd.title()).unwrap();
add(episode.rowid(), download_fold.as_str(), sender); add(episode.rowid(), download_fold.as_str(), sender).unwrap();
assert_eq!(ACTIVE_DOWNLOADS.read().unwrap().len(), 1); assert_eq!(ACTIVE_DOWNLOADS.read().unwrap().len(), 1);
// Give it soem time to download the file // Give it soem time to download the file

View File

@ -1,5 +1,6 @@
#![cfg_attr(feature = "cargo-clippy", allow(type_complexity))] #![cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
use failure::Error;
use gdk_pixbuf::Pixbuf; use gdk_pixbuf::Pixbuf;
use send_cell::SendCell; use send_cell::SendCell;
@ -16,14 +17,23 @@ use std::thread;
use app::Action; use app::Action;
pub fn refresh_feed_wrapper(source: Option<Vec<Source>>, sender: Sender<Action>) {
if let Err(err) = refresh_feed(source, sender) {
error!("An error occured while trying to update the feeds.");
error!("Error: {}", err);
}
}
/// Update the rss feed(s) originating from `source`. /// Update the rss feed(s) originating from `source`.
/// If `source` is None, Fetches all the `Source` entries in the database and updates them. /// If `source` is None, Fetches all the `Source` entries in the database and updates them.
/// When It's done,it queues up a `RefreshViews` action. /// When It's done,it queues up a `RefreshViews` action.
pub fn refresh_feed(source: Option<Vec<Source>>, sender: Sender<Action>) { fn refresh_feed(source: Option<Vec<Source>>, sender: Sender<Action>) -> Result<(), Error> {
sender.send(Action::HeaderBarShowUpdateIndicator).unwrap(); sender.send(Action::HeaderBarShowUpdateIndicator)?;
thread::spawn(move || { thread::spawn(move || {
let mut sources = source.unwrap_or_else(|| dbqueries::get_sources().unwrap()); let mut sources = source.unwrap_or_else(|| {
dbqueries::get_sources().expect("Failed to retrieve Sources from the database.")
});
// Work around to improve the feed addition experience. // Work around to improve the feed addition experience.
// Many times links to rss feeds are just redirects(usually to an https version). // Many times links to rss feeds are just redirects(usually to an https version).
@ -39,11 +49,11 @@ pub fn refresh_feed(source: Option<Vec<Source>>, sender: Sender<Action>) {
if let Err(err) = pipeline::index_single_source(source, false) { if let Err(err) = pipeline::index_single_source(source, false) {
error!("Error While trying to update the database."); error!("Error While trying to update the database.");
error!("Error msg: {}", err); error!("Error msg: {}", err);
let source = dbqueries::get_source_from_id(id).unwrap(); if let Ok(source) = dbqueries::get_source_from_id(id) {
if let Err(err) = pipeline::index_single_source(source, false) {
if let Err(err) = pipeline::index_single_source(source, false) { error!("Error While trying to update the database.");
error!("Error While trying to update the database."); error!("Error msg: {}", err);
error!("Error msg: {}", err); }
} }
} }
} else { } else {
@ -54,9 +64,14 @@ pub fn refresh_feed(source: Option<Vec<Source>>, sender: Sender<Action>) {
} }
} }
sender.send(Action::HeaderBarHideUpdateIndicator).unwrap(); sender
sender.send(Action::RefreshAllViews).unwrap(); .send(Action::HeaderBarHideUpdateIndicator)
.expect("Action channel blew up.");
sender
.send(Action::RefreshAllViews)
.expect("Action channel blew up.");
}); });
Ok(())
} }
lazy_static! { lazy_static! {
@ -72,24 +87,25 @@ lazy_static! {
// GObjects do not implement Send trait, so SendCell is a way around that. // 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. // Also lazy_static requires Sync trait, so that's what the mutexes are.
// TODO: maybe use something that would just scale to requested size? // TODO: maybe use something that would just scale to requested size?
pub fn get_pixbuf_from_path(pd: &PodcastCoverQuery, size: u32) -> Option<Pixbuf> { pub fn get_pixbuf_from_path(pd: &PodcastCoverQuery, size: u32) -> Result<Pixbuf, Error> {
{ {
let hashmap = CACHED_PIXBUFS.read().unwrap(); let hashmap = CACHED_PIXBUFS
let res = hashmap.get(&(pd.id(), size)); .read()
if let Some(px) = res { .map_err(|_| format_err!("Failed to get a lock on the pixbuf cache mutex."))?;
let m = px.lock().unwrap(); if let Some(px) = hashmap.get(&(pd.id(), size)) {
return Some(m.clone().into_inner()); let m = px.lock()
.map_err(|_| format_err!("Failed to lock pixbuf mutex."))?;
return Ok(m.clone().into_inner());
} }
} }
let img_path = downloader::cache_image(pd)?; let img_path = downloader::cache_image(pd)?;
let px = Pixbuf::new_from_file_at_scale(&img_path, size as i32, size as i32, true).ok(); let px = Pixbuf::new_from_file_at_scale(&img_path, size as i32, size as i32, true)?;
if let Some(px) = px { let mut hashmap = CACHED_PIXBUFS
let mut hashmap = CACHED_PIXBUFS.write().unwrap(); .write()
hashmap.insert((pd.id(), size), Mutex::new(SendCell::new(px.clone()))); .map_err(|_| format_err!("Failed to lock pixbuf mutex."))?;
return Some(px); hashmap.insert((pd.id(), size), Mutex::new(SendCell::new(px.clone())));
} Ok(px)
None
} }
#[cfg(test)] #[cfg(test)]
@ -113,6 +129,6 @@ mod tests {
// Get the Podcast // Get the Podcast
let pd = dbqueries::get_podcast_from_source_id(sid).unwrap(); let pd = dbqueries::get_podcast_from_source_id(sid).unwrap();
let pxbuf = get_pixbuf_from_path(&pd.into(), 256); let pxbuf = get_pixbuf_from_path(&pd.into(), 256);
assert!(pxbuf.is_some()); assert!(pxbuf.is_ok());
} }
} }

View File

@ -1,4 +1,5 @@
use chrono::prelude::*; use chrono::prelude::*;
use failure::Error;
use gtk; use gtk;
use gtk::prelude::*; use gtk::prelude::*;
@ -9,7 +10,6 @@ use app::Action;
use utils::get_pixbuf_from_path; use utils::get_pixbuf_from_path;
use widgets::episode::EpisodeWidget; use widgets::episode::EpisodeWidget;
use std::sync::Arc;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -75,9 +75,9 @@ impl Default for EpisodesView {
// TODO: REFACTOR ME // TODO: REFACTOR ME
impl EpisodesView { impl EpisodesView {
pub fn new(sender: Sender<Action>) -> Arc<EpisodesView> { pub fn new(sender: Sender<Action>) -> Result<EpisodesView, Error> {
let view = EpisodesView::default(); let view = EpisodesView::default();
let episodes = dbqueries::get_episodes_widgets_with_limit(50).unwrap(); let episodes = dbqueries::get_episodes_widgets_with_limit(50)?;
let now_utc = Utc::now(); let now_utc = Utc::now();
episodes.into_iter().for_each(|mut ep| { episodes.into_iter().for_each(|mut ep| {
@ -124,7 +124,7 @@ impl EpisodesView {
} }
view.container.show_all(); view.container.show_all();
Arc::new(view) Ok(view)
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
@ -203,18 +203,30 @@ impl EpisodesViewWidget {
gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/episodes_view_widget.ui"); gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/episodes_view_widget.ui");
let container: gtk::Box = builder.get_object("container").unwrap(); let container: gtk::Box = builder.get_object("container").unwrap();
let image: gtk::Image = builder.get_object("cover").unwrap(); let image: gtk::Image = builder.get_object("cover").unwrap();
if let Ok(pd) = dbqueries::get_podcast_cover_from_id(episode.podcast_id()) {
get_pixbuf_from_path(&pd, 64).map(|img| image.set_from_pixbuf(&img));
}
let ep = EpisodeWidget::new(episode, sender.clone()); let ep = EpisodeWidget::new(episode, sender.clone());
container.pack_start(&ep.container, true, true, 6);
EpisodesViewWidget { let view = EpisodesViewWidget {
container, container,
image, image,
episode: ep.container, episode: ep.container,
};
view.init(episode);
view
}
fn init(&self, episode: &mut EpisodeWidgetQuery) {
if let Err(err) = self.set_cover(episode) {
error!("Failed to set a cover: {}", err)
} }
self.container.pack_start(&self.episode, true, true, 6);
}
fn set_cover(&self, episode: &mut EpisodeWidgetQuery) -> Result<(), Error> {
let pd = dbqueries::get_podcast_cover_from_id(episode.podcast_id())?;
let img = get_pixbuf_from_path(&pd, 64)?;
self.image.set_from_pixbuf(&img);
Ok(())
} }
} }

View File

@ -1,3 +1,4 @@
use failure::Error;
use gtk; use gtk;
use gtk::prelude::*; use gtk::prelude::*;
@ -32,41 +33,34 @@ impl Default for ShowsPopulated {
} }
impl ShowsPopulated { impl ShowsPopulated {
pub fn new(sender: Sender<Action>) -> ShowsPopulated { pub fn new(sender: Sender<Action>) -> Result<ShowsPopulated, Error> {
let pop = ShowsPopulated::default(); let pop = ShowsPopulated::default();
pop.init(sender); pop.init(sender)?;
pop Ok(pop)
} }
pub fn init(&self, sender: Sender<Action>) { pub fn init(&self, sender: Sender<Action>) -> Result<(), Error> {
use gtk::WidgetExt;
// TODO: handle unwraps.
self.flowbox.connect_child_activated(move |_, child| { self.flowbox.connect_child_activated(move |_, child| {
// This is such an ugly hack... if let Err(err) = on_child_activate(child, sender.clone()) {
let id = WidgetExt::get_name(child).unwrap().parse::<i32>().unwrap(); error!(
let pd = dbqueries::get_podcast_from_id(id).unwrap(); "Something went wrong during flowbox child activation: {}.",
err
sender )
.send(Action::HeaderBarShowTile(pd.title().into())) };
.unwrap();
sender.send(Action::ReplaceWidget(pd)).unwrap();
sender.send(Action::ShowWidgetAnimated).unwrap();
}); });
// Populate the flowbox with the Podcasts. // Populate the flowbox with the Podcasts.
self.populate_flowbox(); self.populate_flowbox()
} }
fn populate_flowbox(&self) { fn populate_flowbox(&self) -> Result<(), Error> {
let podcasts = dbqueries::get_podcasts(); let podcasts = dbqueries::get_podcasts()?;
if let Ok(pds) = podcasts { podcasts.iter().for_each(|parent| {
pds.iter().for_each(|parent| { let flowbox_child = ShowsChild::new(parent);
let flowbox_child = ShowsChild::new(parent); self.flowbox.add(&flowbox_child.child);
self.flowbox.add(&flowbox_child.child); });
}); self.flowbox.show_all();
self.flowbox.show_all(); Ok(())
}
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
@ -79,6 +73,21 @@ impl ShowsPopulated {
} }
} }
fn on_child_activate(child: &gtk::FlowBoxChild, sender: Sender<Action>) -> Result<(), Error> {
use gtk::WidgetExt;
// This is such an ugly hack...
let id = WidgetExt::get_name(child)
.ok_or_else(|| format_err!("Faild to get \"episodes\" child from the stack."))?
.parse::<i32>()?;
let pd = dbqueries::get_podcast_from_id(id)?;
sender.send(Action::HeaderBarShowTile(pd.title().into()))?;
sender.send(Action::ReplaceWidget(pd))?;
sender.send(Action::ShowWidgetAnimated)?;
Ok(())
}
#[derive(Debug)] #[derive(Debug)]
struct ShowsChild { struct ShowsChild {
container: gtk::Box, container: gtk::Box,
@ -114,11 +123,16 @@ impl ShowsChild {
fn init(&self, pd: &Podcast) { fn init(&self, pd: &Podcast) {
self.container.set_tooltip_text(pd.title()); self.container.set_tooltip_text(pd.title());
let cover = get_pixbuf_from_path(&pd.clone().into(), 256); if let Err(err) = self.set_cover(pd) {
if let Some(img) = cover { error!("Failed to set a cover: {}", err)
self.cover.set_from_pixbuf(&img); }
};
WidgetExt::set_name(&self.child, &pd.id().to_string()); WidgetExt::set_name(&self.child, &pd.id().to_string());
} }
fn set_cover(&self, pd: &Podcast) -> Result<(), Error> {
let image = get_pixbuf_from_path(&pd.clone().into(), 256)?;
self.cover.set_from_pixbuf(&image);
Ok(())
}
} }

View File

@ -123,13 +123,20 @@ impl EpisodeWidget {
self.show_buttons(episode.local_uri()); self.show_buttons(episode.local_uri());
// Determine what the state of the progress bar should be. // Determine what the state of the progress bar should be.
self.determine_progess_bar(); if let Err(err) = self.determine_progess_bar() {
error!("Something went wrong determining the ProgressBar State.");
error!("Error: {}", err);
}
let title = &self.title; let title = &self.title;
self.play self.play
.connect_clicked(clone!(episode, title, sender => move |_| { .connect_clicked(clone!(episode, title, sender => move |_| {
let mut episode = episode.clone(); let mut episode = episode.clone();
on_play_bttn_clicked(episode.rowid());
if let Err(err) = on_play_bttn_clicked(episode.rowid()) {
error!("Error: {}", err);
};
if episode.set_played_now().is_ok() { if episode.set_played_now().is_ok() {
title title
.get_style_context() .get_style_context()
@ -141,7 +148,12 @@ impl EpisodeWidget {
self.download self.download
.connect_clicked(clone!(episode, sender => move |dl| { .connect_clicked(clone!(episode, sender => move |dl| {
dl.set_sensitive(false); dl.set_sensitive(false);
on_download_clicked(&episode, sender.clone()); if let Err(err) = on_download_clicked(&episode, sender.clone()) {
error!("Download failed to start.");
error!("Error: {}", err);
} else {
info!("Donwload started succesfully.");
}
})); }));
} }
@ -204,26 +216,22 @@ impl EpisodeWidget {
} }
// FIXME: REFACTOR ME // FIXME: REFACTOR ME
fn determine_progess_bar(&self) { // Something Something State-Machine?
fn determine_progess_bar(&self) -> Result<(), Error> {
let id = WidgetExt::get_name(&self.container) let id = WidgetExt::get_name(&self.container)
.unwrap() .ok_or_else(|| format_err!("Failed to get widget Name"))?
.parse::<i32>() .parse::<i32>()?;
.unwrap();
let prog_struct = || -> Option<_> { let active_dl = || -> Result<Option<_>, Error> {
if let Ok(m) = manager::ACTIVE_DOWNLOADS.read() { let m = manager::ACTIVE_DOWNLOADS
if !m.contains_key(&id) { .read()
return None; .map_err(|_| format_err!("Failed to get a lock on the mutex."))?;
};
return m.get(&id).cloned();
}
None
}();
let progress_bar = self.progress.clone(); Ok(m.get(&id).cloned())
let total_size = self.total_size.clone(); }()?;
let local_size = self.local_size.clone();
if let Some(prog) = prog_struct { if let Some(prog) = active_dl {
// FIXME: Document me?
self.download.hide(); self.download.hide();
self.progress.show(); self.progress.show();
self.local_size.show(); self.local_size.show();
@ -232,13 +240,17 @@ impl EpisodeWidget {
self.prog_separator.show(); self.prog_separator.show();
self.cancel.show(); self.cancel.show();
let progress_bar = self.progress.clone();
let total_size = self.total_size.clone();
let local_size = self.local_size.clone();
// Setup a callback that will update the progress bar. // Setup a callback that will update the progress bar.
update_progressbar_callback(prog.clone(), id, progress_bar, local_size); update_progressbar_callback(prog.clone(), id, &progress_bar, &local_size);
// Setup a callback that will update the total_size label // Setup a callback that will update the total_size label
// with the http ContentLength header number rather than // with the http ContentLength header number rather than
// relying to the RSS feed. // relying to the RSS feed.
update_total_size_callback(prog.clone(), total_size); update_total_size_callback(prog.clone(), &total_size);
self.cancel.connect_clicked(clone!(prog => move |cancel| { self.cancel.connect_clicked(clone!(prog => move |cancel| {
if let Ok(mut m) = prog.lock() { if let Ok(mut m) = prog.lock() {
@ -247,44 +259,37 @@ impl EpisodeWidget {
} }
})); }));
} }
Ok(())
} }
} }
fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: Sender<Action>) { fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: Sender<Action>) -> Result<(), Error> {
let download_fold = dbqueries::get_podcast_from_id(ep.podcast_id()) let pd = dbqueries::get_podcast_from_id(ep.podcast_id())?;
.ok() let download_fold = get_download_folder(&pd.title().to_owned())?;
.map(|pd| get_download_folder(&pd.title().to_owned()).ok())
.and_then(|x| x);
// Start a new download. // Start a new download.
if let Some(fold) = download_fold { manager::add(ep.rowid(), &download_fold, sender.clone())?;
manager::add(ep.rowid(), &fold, sender.clone());
}
// Update Views // Update Views
sender.send(Action::RefreshEpisodesView).unwrap(); sender.send(Action::RefreshEpisodesView)?;
sender.send(Action::RefreshWidgetIfVis).unwrap(); sender.send(Action::RefreshWidgetIfVis)?;
Ok(())
} }
fn on_play_bttn_clicked(episode_id: i32) { fn on_play_bttn_clicked(episode_id: i32) -> Result<(), Error> {
let local_uri = dbqueries::get_episode_local_uri_from_id(episode_id) let uri = dbqueries::get_episode_local_uri_from_id(episode_id)?
.ok() .ok_or_else(|| format_err!("Expected Some found None."))?;
.and_then(|x| x);
if let Some(uri) = local_uri { if Path::new(&uri).exists() {
if Path::new(&uri).exists() { info!("Opening {}", uri);
info!("Opening {}", uri); open::that(&uri)?;
open::that(&uri).err().map(|err| {
error!("Error while trying to open file: {}", uri);
error!("Error: {}", err);
});
}
} else { } else {
error!( bail!("File \"{}\" does not exist.", uri);
"Something went wrong evaluating the following path: {:?}",
local_uri
);
} }
Ok(())
} }
// Setup a callback that will update the progress bar. // Setup a callback that will update the progress bar.
@ -292,80 +297,102 @@ fn on_play_bttn_clicked(episode_id: i32) {
fn update_progressbar_callback( fn update_progressbar_callback(
prog: Arc<Mutex<manager::Progress>>, prog: Arc<Mutex<manager::Progress>>,
episode_rowid: i32, episode_rowid: i32,
progress_bar: gtk::ProgressBar, progress_bar: &gtk::ProgressBar,
local_size: gtk::Label, local_size: &gtk::Label,
) { ) {
timeout_add( timeout_add(
400, 400,
clone!(prog, progress_bar => move || { clone!(prog, progress_bar, progress_bar, local_size=> move || {
let (fraction, downloaded) = { progress_bar_helper(prog.clone(), episode_rowid, &progress_bar, &local_size)
let m = prog.lock().unwrap(); .unwrap_or(glib::Continue(false))
(m.get_fraction(), m.get_downloaded())
};
// Update local_size label
downloaded.file_size(SIZE_OPTS.clone()).ok().map(|x| local_size.set_text(&x));
// I hate floating points.
// Update the progress_bar.
if (fraction >= 0.0) && (fraction <= 1.0) && (!fraction.is_nan()) {
progress_bar.set_fraction(fraction);
}
// info!("Fraction: {}", progress_bar.get_fraction());
// info!("Fraction: {}", fraction);
// Check if the download is still active
let active = {
let m = manager::ACTIVE_DOWNLOADS.read().unwrap();
m.contains_key(&episode_rowid)
};
if (fraction >= 1.0) && (!fraction.is_nan()){
glib::Continue(false)
} else if !active {
glib::Continue(false)
} else {
glib::Continue(true)
}
}), }),
); );
} }
fn progress_bar_helper(
prog: Arc<Mutex<manager::Progress>>,
episode_rowid: i32,
progress_bar: &gtk::ProgressBar,
local_size: &gtk::Label,
) -> Result<glib::Continue, Error> {
let (fraction, downloaded) = {
let m = prog.lock()
.map_err(|_| format_err!("Failed to get a lock on the mutex."))?;
(m.get_fraction(), m.get_downloaded())
};
// Update local_size label
downloaded
.file_size(SIZE_OPTS.clone())
.map_err(|err| format_err!("{}", err))
.map(|x| local_size.set_text(&x))?;
// I hate floating points.
// Update the progress_bar.
if (fraction >= 0.0) && (fraction <= 1.0) && (!fraction.is_nan()) {
progress_bar.set_fraction(fraction);
}
// info!("Fraction: {}", progress_bar.get_fraction());
// info!("Fraction: {}", fraction);
// Check if the download is still active
let active = {
let m = manager::ACTIVE_DOWNLOADS
.read()
.map_err(|_| format_err!("Failed to get a lock on the mutex."))?;
m.contains_key(&episode_rowid)
};
if (fraction >= 1.0) && (!fraction.is_nan()) {
Ok(glib::Continue(false))
} else if !active {
Ok(glib::Continue(false))
} else {
Ok(glib::Continue(true))
}
}
// Setup a callback that will update the total_size label // Setup a callback that will update the total_size label
// with the http ContentLength header number rather than // with the http ContentLength header number rather than
// relying to the RSS feed. // relying to the RSS feed.
fn update_total_size_callback(prog: Arc<Mutex<manager::Progress>>, total_size: gtk::Label) { fn update_total_size_callback(prog: Arc<Mutex<manager::Progress>>, total_size: &gtk::Label) {
timeout_add( timeout_add(
500, 500,
clone!(prog, total_size => move || { clone!(prog, total_size => move || {
let total_bytes = { total_size_helper(prog.clone(), &total_size).unwrap_or(glib::Continue(true))
let m = prog.lock().unwrap();
m.get_total_size()
};
debug!("Total Size: {}", total_bytes);
if total_bytes != 0 {
// Update the total_size label
total_bytes.file_size(SIZE_OPTS.clone()).ok().map(|x| total_size.set_text(&x));
glib::Continue(false)
} else {
glib::Continue(true)
}
}), }),
); );
} }
// fn on_delete_bttn_clicked(episode_id: i32) { fn total_size_helper(
// let mut ep = dbqueries::get_episode_from_rowid(episode_id) prog: Arc<Mutex<manager::Progress>>,
// .unwrap() total_size: &gtk::Label,
// .into(); ) -> Result<glib::Continue, Error> {
// Get the total_bytes.
let total_bytes = {
let m = prog.lock()
.map_err(|_| format_err!("Failed to get a lock on the mutex."))?;
m.get_total_size()
};
// let e = delete_local_content(&mut ep); debug!("Total Size: {}", total_bytes);
// if let Err(err) = e { if total_bytes != 0 {
// error!("Error while trying to delete file: {:?}", ep.local_uri()); // Update the total_size label
// error!("Error: {}", err); total_bytes
// }; .file_size(SIZE_OPTS.clone())
.map_err(|err| format_err!("{}", err))
.map(|x| total_size.set_text(&x))?;
// Do not call again the callback
Ok(glib::Continue(false))
} else {
Ok(glib::Continue(true))
}
}
// fn on_delete_bttn_clicked(episode_id: i32) -> Result<(), Error> {
// let mut ep = dbqueries::get_episode_from_rowid(episode_id)?.into();
// delete_local_content(&mut ep).map_err(From::from).map(|_| ())
// } // }
pub fn episodes_listbox(pd: &Podcast, sender: Sender<Action>) -> Result<gtk::ListBox, Error> { pub fn episodes_listbox(pd: &Podcast, sender: Sender<Action>) -> Result<gtk::ListBox, Error> {

View File

@ -1,4 +1,5 @@
use dissolve; use dissolve;
use failure::Error;
use gtk; use gtk;
use gtk::prelude::*; use gtk::prelude::*;
use open; use open;
@ -65,20 +66,26 @@ impl ShowWidget {
self.unsub self.unsub
.connect_clicked(clone!(pd, sender => move |bttn| { .connect_clicked(clone!(pd, sender => move |bttn| {
on_unsub_button_clicked(&pd, bttn, sender.clone()); if let Err(err) = on_unsub_button_clicked(&pd, bttn, sender.clone()) {
error!("Error: {}", err);
}
})); }));
self.setup_listbox(pd, sender.clone()); self.setup_listbox(pd, sender.clone());
self.set_cover(pd);
self.set_description(pd.description()); self.set_description(pd.description());
if let Err(err) = self.set_cover(pd) {
error!("Failed to set a cover: {}", err)
}
let link = pd.link().to_owned(); let link = pd.link().to_owned();
self.link.set_tooltip_text(Some(link.as_str())); self.link.set_tooltip_text(Some(link.as_str()));
self.link.connect_clicked(move |_| { self.link.connect_clicked(move |_| {
info!("Opening link: {}", &link); info!("Opening link: {}", &link);
open::that(&link) if let Err(err) = open::that(&link) {
.err() error!("Failed to open link: {}", &link);
.map(|err| error!("Something went wrong: {}", err)); error!("Error: {}", err);
}
}); });
} }
@ -87,11 +94,11 @@ impl ShowWidget {
let listbox = episodes_listbox(pd, sender.clone()); let listbox = episodes_listbox(pd, sender.clone());
listbox.ok().map(|l| self.episodes.add(&l)); listbox.ok().map(|l| self.episodes.add(&l));
} }
/// Set the show cover. /// Set the show cover.
fn set_cover(&self, pd: &Podcast) { fn set_cover(&self, pd: &Podcast) -> Result<(), Error> {
let img = get_pixbuf_from_path(&pd.clone().into(), 128); let image = get_pixbuf_from_path(&pd.clone().into(), 128)?;
img.map(|i| self.cover.set_from_pixbuf(&i)); self.cover.set_from_pixbuf(&image);
Ok(())
} }
/// Set the descripton text. /// Set the descripton text.
@ -107,7 +114,11 @@ impl ShowWidget {
} }
} }
fn on_unsub_button_clicked(pd: &Podcast, unsub_button: &gtk::Button, sender: Sender<Action>) { fn on_unsub_button_clicked(
pd: &Podcast,
unsub_button: &gtk::Button,
sender: Sender<Action>,
) -> Result<(), Error> {
// hack to get away without properly checking for none. // hack to get away without properly checking for none.
// if pressed twice would panic. // if pressed twice would panic.
unsub_button.hide(); unsub_button.hide();
@ -118,16 +129,19 @@ fn on_unsub_button_clicked(pd: &Podcast, unsub_button: &gtk::Button, sender: Sen
error!("Error: {}", err); error!("Error: {}", err);
} }
})); }));
sender.send(Action::HeaderBarNormal).unwrap();
sender.send(Action::ShowShowsAnimated).unwrap(); sender.send(Action::HeaderBarNormal)?;
sender.send(Action::ShowShowsAnimated)?;
// Queue a refresh after the switch to avoid blocking the db. // Queue a refresh after the switch to avoid blocking the db.
sender.send(Action::RefreshShowsView).unwrap(); sender.send(Action::RefreshShowsView)?;
sender.send(Action::RefreshEpisodesView).unwrap(); sender.send(Action::RefreshEpisodesView)?;
Ok(())
} }
#[allow(dead_code)] #[allow(dead_code)]
fn on_played_button_clicked(pd: &Podcast, sender: Sender<Action>) { fn on_played_button_clicked(pd: &Podcast, sender: Sender<Action>) -> Result<(), Error> {
let _ = dbqueries::update_none_to_played_now(pd); dbqueries::update_none_to_played_now(pd)?;
sender.send(Action::RefreshWidget)?;
sender.send(Action::RefreshWidget).unwrap(); Ok(())
} }