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:
commit
8cbae4050e
@ -212,7 +212,7 @@ impl Source {
|
||||
/// Updates the validator Http Headers.
|
||||
///
|
||||
/// 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(
|
||||
self,
|
||||
client: &Client<HttpsConnector<HttpConnector>>,
|
||||
|
||||
@ -172,65 +172,59 @@ pub fn get_episode(
|
||||
ep.save()?;
|
||||
};
|
||||
|
||||
let res = download_into(
|
||||
let path = download_into(
|
||||
download_folder,
|
||||
&ep.rowid().to_string(),
|
||||
ep.uri().unwrap(),
|
||||
progress,
|
||||
);
|
||||
)?;
|
||||
|
||||
if let Ok(path) = res {
|
||||
// If download succedes set episode local_uri to dlpath.
|
||||
ep.set_local_uri(Some(&path));
|
||||
// If download succedes set episode local_uri to dlpath.
|
||||
ep.set_local_uri(Some(&path));
|
||||
|
||||
// Over-write episode lenght
|
||||
let size = fs::metadata(path);
|
||||
if let Ok(s) = size {
|
||||
ep.set_length(Some(s.len() as i32))
|
||||
};
|
||||
// Over-write episode lenght
|
||||
let size = fs::metadata(path);
|
||||
if let Ok(s) = size {
|
||||
ep.set_length(Some(s.len() as i32))
|
||||
};
|
||||
|
||||
ep.save()?;
|
||||
Ok(())
|
||||
} else {
|
||||
error!("Something whent wrong while downloading.");
|
||||
Err(res.unwrap_err())
|
||||
}
|
||||
ep.save()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cache_image(pd: &PodcastCoverQuery) -> Option<String> {
|
||||
let url = pd.image_uri()?.to_owned();
|
||||
pub fn cache_image(pd: &PodcastCoverQuery) -> Result<String, DownloadError> {
|
||||
let url = pd.image_uri()
|
||||
.ok_or_else(|| DownloadError::NoImageLocation)?
|
||||
.to_owned();
|
||||
|
||||
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.
|
||||
if let Ok(mut foo) = glob(&format!("{}/cover.*", cache_download_fold)) {
|
||||
// For some reason there is no .first() method so nth(0) is used
|
||||
let path = foo.nth(0).and_then(|x| x.ok());
|
||||
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.
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(&cache_download_fold)
|
||||
.ok()?;
|
||||
.create(&cache_download_fold)?;
|
||||
|
||||
match download_into(&cache_download_fold, "cover", &url, None) {
|
||||
Ok(path) => {
|
||||
info!("Cached img into: {}", &path);
|
||||
Some(path)
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to get feed image.");
|
||||
error!("Error: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
let path = download_into(&cache_download_fold, "cover", &url, None)?;
|
||||
info!("Cached img into: {}", &path);
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -240,6 +234,8 @@ mod tests {
|
||||
use hammond_data::dbqueries;
|
||||
use hammond_data::pipeline;
|
||||
|
||||
use std::fs;
|
||||
|
||||
#[test]
|
||||
// This test inserts an rss feed to your `XDG_DATA/hammond/hammond.db` so we make it explicit
|
||||
// to run it.
|
||||
@ -262,6 +258,7 @@ mod tests {
|
||||
HAMMOND_CACHE.to_str().unwrap(),
|
||||
pd.title()
|
||||
);
|
||||
assert_eq!(img_path, Some(foo_));
|
||||
assert_eq!(img_path.unwrap(), foo_);
|
||||
fs::remove_file(foo_).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,10 +10,16 @@ pub enum DownloadError {
|
||||
DataError(#[cause] DataError),
|
||||
#[fail(display = "Io error: {}", _0)]
|
||||
IoError(#[cause] io::Error),
|
||||
#[fail(display = "The Download was cancelled")]
|
||||
DownloadCancelled,
|
||||
#[fail(display = "Unexpected server response: {}", _0)]
|
||||
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 {
|
||||
|
||||
@ -46,7 +46,7 @@ pub struct App {
|
||||
impl App {
|
||||
pub fn new() -> App {
|
||||
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.
|
||||
glib::set_application_name("Hammond");
|
||||
@ -65,7 +65,8 @@ impl App {
|
||||
let (sender, receiver) = channel();
|
||||
|
||||
// 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
|
||||
let header = Arc::new(Header::new(content.clone(), &window, sender.clone()));
|
||||
@ -87,7 +88,7 @@ impl App {
|
||||
let sender = self.sender.clone();
|
||||
// Update the feeds right after the Application is initialized.
|
||||
gtk::timeout_add_seconds(2, move || {
|
||||
utils::refresh_feed(None, sender.clone());
|
||||
utils::refresh_feed_wrapper(None, sender.clone());
|
||||
glib::Continue(false)
|
||||
});
|
||||
|
||||
@ -95,7 +96,7 @@ impl App {
|
||||
// Auto-updater, runs every hour.
|
||||
// TODO: expose the interval in which it run to a user setting.
|
||||
gtk::timeout_add_seconds(3600, move || {
|
||||
utils::refresh_feed(None, sender.clone());
|
||||
utils::refresh_feed_wrapper(None, sender.clone());
|
||||
glib::Continue(true)
|
||||
});
|
||||
|
||||
@ -122,9 +123,9 @@ impl App {
|
||||
match receiver.recv_timeout(Duration::from_millis(10)) {
|
||||
Ok(Action::UpdateSources(source)) => {
|
||||
if let Some(s) = source {
|
||||
utils::refresh_feed(Some(vec![s]), sender.clone())
|
||||
utils::refresh_feed_wrapper(Some(vec![s]), sender.clone());
|
||||
} else {
|
||||
utils::refresh_feed(None, sender.clone())
|
||||
utils::refresh_feed_wrapper(None, sender.clone());
|
||||
}
|
||||
}
|
||||
Ok(Action::RefreshAllViews) => content.update(),
|
||||
@ -134,7 +135,12 @@ impl App {
|
||||
Ok(Action::RefreshWidgetIfSame(id)) => content.update_widget_if_same(id),
|
||||
Ok(Action::RefreshEpisodesView) => content.update_episode_view(),
|
||||
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::ShowShowsAnimated) => content.get_shows().switch_podcasts_animated(),
|
||||
Ok(Action::HeaderBarShowTile(title)) => headerbar.switch_to_back(&title),
|
||||
|
||||
@ -2,6 +2,8 @@ use gtk;
|
||||
use gtk::Cast;
|
||||
use gtk::prelude::*;
|
||||
|
||||
use failure::Error;
|
||||
|
||||
use hammond_data::Podcast;
|
||||
use hammond_data::dbqueries;
|
||||
|
||||
@ -24,20 +26,20 @@ pub struct 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 episodes = Arc::new(EpisodeStack::new(sender.clone()));
|
||||
let shows = Arc::new(ShowStack::new(sender.clone()));
|
||||
let episodes = Arc::new(EpisodeStack::new(sender.clone())?);
|
||||
let shows = Arc::new(ShowStack::new(sender.clone())?);
|
||||
|
||||
stack.add_titled(&episodes.stack, "episodes", "Episodes");
|
||||
stack.add_titled(&shows.stack, "shows", "Shows");
|
||||
|
||||
Content {
|
||||
Ok(Content {
|
||||
stack,
|
||||
shows,
|
||||
episodes,
|
||||
sender,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&self) {
|
||||
@ -46,33 +48,46 @@ impl Content {
|
||||
self.update_widget()
|
||||
}
|
||||
|
||||
// TODO: Maybe propagate the error?
|
||||
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) {
|
||||
if self.stack.get_visible_child_name() != Some("episodes".into()) {
|
||||
self.episodes.update();
|
||||
self.update_episode_view();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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.update_widget();
|
||||
self.update_widget();
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,7 +107,7 @@ pub struct ShowStack {
|
||||
}
|
||||
|
||||
impl ShowStack {
|
||||
fn new(sender: Sender<Action>) -> ShowStack {
|
||||
fn new(sender: Sender<Action>) -> Result<ShowStack, Error> {
|
||||
let stack = gtk::Stack::new();
|
||||
|
||||
let show = ShowStack {
|
||||
@ -100,7 +115,7 @@ impl ShowStack {
|
||||
sender: sender.clone(),
|
||||
};
|
||||
|
||||
let pop = ShowsPopulated::new(sender.clone());
|
||||
let pop = ShowsPopulated::new(sender.clone())?;
|
||||
let widget = ShowWidget::default();
|
||||
let empty = EmptyView::new();
|
||||
|
||||
@ -114,7 +129,7 @@ impl ShowStack {
|
||||
show.stack.set_visible_child_name("podcasts")
|
||||
}
|
||||
|
||||
show
|
||||
Ok(show)
|
||||
}
|
||||
|
||||
// pub fn update(&self) {
|
||||
@ -122,29 +137,27 @@ impl ShowStack {
|
||||
// self.update_podcasts();
|
||||
// }
|
||||
|
||||
pub fn update_podcasts(&self) {
|
||||
let vis = self.stack.get_visible_child_name().unwrap();
|
||||
pub fn update_podcasts(&self) -> Result<(), Error> {
|
||||
let vis = self.stack
|
||||
.get_visible_child_name()
|
||||
.ok_or_else(|| format_err!("Failed to get visible child name."))?;
|
||||
|
||||
let old = self.stack
|
||||
.get_child_by_name("podcasts")
|
||||
// This is guaranted to exists, based on `ShowStack::new()`.
|
||||
.unwrap()
|
||||
.ok_or_else(|| format_err!("Faild to get \"podcasts\" child from the stack."))?
|
||||
.downcast::<gtk::Box>()
|
||||
// This is guaranted to be a Box based on the `ShowsPopulated` impl.
|
||||
.unwrap();
|
||||
.map_err(|_| format_err!("Failed to downcast stack child to a Box."))?;
|
||||
debug!("Name: {:?}", WidgetExt::get_name(&old));
|
||||
|
||||
let scrolled_window = old.get_children()
|
||||
.first()
|
||||
// This is guaranted to exist based on the show_widget.ui file.
|
||||
.unwrap()
|
||||
.ok_or_else(|| format_err!("Box container has no childs."))?
|
||||
.clone()
|
||||
.downcast::<gtk::ScrolledWindow>()
|
||||
// This is guaranted based on the show_widget.ui file.
|
||||
.unwrap();
|
||||
.map_err(|_| format_err!("Failed to downcast stack child to a ScrolledWindow."))?;
|
||||
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.
|
||||
scrolled_window
|
||||
.get_vadjustment()
|
||||
@ -162,16 +175,15 @@ impl ShowStack {
|
||||
}
|
||||
|
||||
old.destroy();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn replace_widget(&self, pd: &Podcast) {
|
||||
pub fn replace_widget(&self, pd: &Podcast) -> Result<(), Error> {
|
||||
let old = self.stack
|
||||
.get_child_by_name("widget")
|
||||
// This is guaranted to exists, based on `ShowStack::new()`.
|
||||
.unwrap()
|
||||
.ok_or_else(|| format_err!("Faild to get \"widget\" child from the stack."))?
|
||||
.downcast::<gtk::Box>()
|
||||
// This is guaranted to be a Box based on the `ShowWidget` impl.
|
||||
.unwrap();
|
||||
.map_err(|_| format_err!("Failed to downcast stack child to a Box."))?;
|
||||
debug!("Name: {:?}", WidgetExt::get_name(&old));
|
||||
|
||||
let new = ShowWidget::new(pd, self.sender.clone());
|
||||
@ -185,12 +197,10 @@ impl ShowStack {
|
||||
if newid == oldid {
|
||||
let scrolled_window = old.get_children()
|
||||
.first()
|
||||
// This is guaranted to exist based on the show_widget.ui file.
|
||||
.unwrap()
|
||||
.ok_or_else(|| format_err!("Box container has no childs."))?
|
||||
.clone()
|
||||
.downcast::<gtk::ScrolledWindow>()
|
||||
// This is guaranted based on the show_widget.ui file.
|
||||
.unwrap();
|
||||
.map_err(|_| format_err!("Failed to downcast stack child to a ScrolledWindow."))?;
|
||||
debug!("Name: {:?}", WidgetExt::get_name(&scrolled_window));
|
||||
|
||||
// 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.add_named(&new.container, "widget");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_widget(&self) {
|
||||
let vis = self.stack.get_visible_child_name().unwrap();
|
||||
let old = self.stack.get_child_by_name("widget").unwrap();
|
||||
pub fn update_widget(&self) -> Result<(), Error> {
|
||||
let vis = self.stack
|
||||
.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);
|
||||
if id == Some("GtkBox".to_string()) || id.is_none() {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let pd = dbqueries::get_podcast_from_id(id.unwrap().parse::<i32>().unwrap());
|
||||
if let Ok(pd) = pd {
|
||||
self.replace_widget(&pd);
|
||||
self.stack.set_visible_child_name(&vis);
|
||||
old.destroy();
|
||||
}
|
||||
let id = id.ok_or_else(|| format_err!("Failed to get widget's name."))?;
|
||||
let pd = dbqueries::get_podcast_from_id(id.parse::<i32>()?)?;
|
||||
self.replace_widget(&pd)?;
|
||||
self.stack.set_visible_child_name(&vis);
|
||||
old.destroy();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Only update widget if it's podcast_id is equal to pid.
|
||||
pub fn update_widget_if_same(&self, pid: i32) {
|
||||
let old = self.stack.get_child_by_name("widget").unwrap();
|
||||
pub fn update_widget_if_same(&self, pid: i32) -> Result<(), Error> {
|
||||
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);
|
||||
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) {
|
||||
@ -253,8 +271,8 @@ pub struct EpisodeStack {
|
||||
}
|
||||
|
||||
impl EpisodeStack {
|
||||
fn new(sender: Sender<Action>) -> EpisodeStack {
|
||||
let episodes = EpisodesView::new(sender.clone());
|
||||
fn new(sender: Sender<Action>) -> Result<EpisodeStack, Error> {
|
||||
let episodes = EpisodesView::new(sender.clone())?;
|
||||
let empty = EmptyView::new();
|
||||
let stack = gtk::Stack::new();
|
||||
|
||||
@ -267,30 +285,27 @@ impl EpisodeStack {
|
||||
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
|
||||
.get_child_by_name("episodes")
|
||||
// This is guaranted to exists, based on `EpisodeStack::new()`.
|
||||
.unwrap()
|
||||
.ok_or_else(|| format_err!("Faild to get \"episodes\" child from the stack."))?
|
||||
.downcast::<gtk::Box>()
|
||||
// This is guaranted to be a Box based on the `EpisodesView` impl.
|
||||
.unwrap();
|
||||
.map_err(|_| format_err!("Failed to downcast stack child to a Box."))?;
|
||||
debug!("Name: {:?}", WidgetExt::get_name(&old));
|
||||
|
||||
let scrolled_window = old.get_children()
|
||||
.first()
|
||||
// This is guaranted to exist based on the episodes_view.ui file.
|
||||
.unwrap()
|
||||
.ok_or_else(|| format_err!("Box container has no childs."))?
|
||||
.clone()
|
||||
.downcast::<gtk::ScrolledWindow>()
|
||||
// This is guaranted based on the episodes_view.ui file.
|
||||
.unwrap();
|
||||
.map_err(|_| format_err!("Failed to downcast stack child to a ScrolledWindow."))?;
|
||||
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.
|
||||
scrolled_window
|
||||
.get_vadjustment()
|
||||
@ -306,5 +321,7 @@ impl EpisodeStack {
|
||||
}
|
||||
|
||||
old.destroy();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
use failure::Error;
|
||||
use failure::ResultExt;
|
||||
use gtk;
|
||||
use gtk::prelude::*;
|
||||
use url::Url;
|
||||
|
||||
use hammond_data::Source;
|
||||
use hammond_data::dbqueries;
|
||||
use url::Url;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Sender;
|
||||
@ -55,6 +57,7 @@ impl Default for Header {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Refactor components into smaller state machines
|
||||
impl Header {
|
||||
pub fn new(content: Arc<Content>, window: >k::Window, sender: Sender<Action>) -> Header {
|
||||
let h = Header::default();
|
||||
@ -72,18 +75,24 @@ impl Header {
|
||||
self.switch.set_stack(&content.get_stack());
|
||||
|
||||
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 |_| {
|
||||
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();
|
||||
}));
|
||||
|
||||
self.add_toggle.set_popover(&add_popover);
|
||||
|
||||
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
|
||||
@ -142,28 +151,32 @@ impl Header {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_add_bttn_clicked(entry: >k::Entry, sender: Sender<Action>) {
|
||||
fn on_add_bttn_clicked(entry: >k::Entry, sender: Sender<Action>) -> Result<(), Error> {
|
||||
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() {
|
||||
entry.set_text("");
|
||||
sender.send(Action::UpdateSources(source.ok())).unwrap();
|
||||
} else {
|
||||
error!("Something went wrong.");
|
||||
error!("Error: {:?}", source.unwrap_err());
|
||||
}
|
||||
sender
|
||||
.send(Action::UpdateSources(Some(source)))
|
||||
.context("App channel blew up.")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_url_change(entry: >k::Entry, result: >k::Label, add_button: >k::Button) {
|
||||
let uri = entry.get_text().unwrap();
|
||||
fn on_url_change(
|
||||
entry: >k::Entry,
|
||||
result: >k::Label,
|
||||
add_button: >k::Button,
|
||||
) -> Result<(), Error> {
|
||||
let uri = entry
|
||||
.get_text()
|
||||
.ok_or_else(|| format_err!("GtkEntry blew up somehow."))?;
|
||||
debug!("Url: {}", uri);
|
||||
|
||||
let url = Url::parse(&uri);
|
||||
// TODO: refactor to avoid duplication
|
||||
match url {
|
||||
Ok(u) => {
|
||||
if !dbqueries::source_exists(u.as_str()).unwrap() {
|
||||
if !dbqueries::source_exists(u.as_str())? {
|
||||
add_button.set_sensitive(true);
|
||||
result.hide();
|
||||
result.set_label("");
|
||||
@ -172,6 +185,7 @@ fn on_url_change(entry: >k::Entry, result: >k::Label, add_button: >k::Butt
|
||||
result.set_label("Show already exists.");
|
||||
result.show();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
add_button.set_sensitive(false);
|
||||
@ -182,6 +196,7 @@ fn on_url_change(entry: >k::Entry, result: >k::Label, add_button: >k::Butt
|
||||
} else {
|
||||
result.hide();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,8 +9,8 @@ extern crate gtk;
|
||||
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
extern crate failure_derive;
|
||||
// #[macro_use]
|
||||
// extern crate failure_derive;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
@ -65,15 +65,15 @@ use app::App;
|
||||
|
||||
fn main() {
|
||||
// TODO: make the the logger a cli -vv option
|
||||
loggerv::init_with_level(Level::Info).unwrap();
|
||||
gtk::init().expect("Error initializing gtk");
|
||||
loggerv::init_with_level(Level::Info).expect("Error initializing loggerv.");
|
||||
gtk::init().expect("Error initializing gtk.");
|
||||
static_resource::init().expect("Something went wrong with the resource file initialization.");
|
||||
|
||||
// Add custom style
|
||||
let provider = gtk::CssProvider::new();
|
||||
gtk::CssProvider::load_from_resource(&provider, "/org/gnome/hammond/gtk/style.css");
|
||||
gtk::StyleContext::add_provider_for_screen(
|
||||
&gdk::Screen::get_default().unwrap(),
|
||||
&gdk::Screen::get_default().expect("Error initializing gtk css provider."),
|
||||
&provider,
|
||||
600,
|
||||
);
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
use failure::Error;
|
||||
|
||||
// use hammond_data::Episode;
|
||||
use hammond_data::dbqueries;
|
||||
use hammond_downloader::downloader::{get_episode, DownloadProgress};
|
||||
@ -11,6 +13,9 @@ use std::sync::mpsc::Sender;
|
||||
// use std::path::PathBuf;
|
||||
use std::thread;
|
||||
|
||||
// This is messy, undocumented and hacky af.
|
||||
// I am terrible at writting downloaders and download managers.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Progress {
|
||||
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.
|
||||
let prog = Arc::new(Mutex::new(Progress::default()));
|
||||
|
||||
{
|
||||
if let Ok(mut m) = ACTIVE_DOWNLOADS.write() {
|
||||
m.insert(id, prog.clone());
|
||||
}
|
||||
let mut m = ACTIVE_DOWNLOADS
|
||||
.write()
|
||||
.map_err(|_| format_err!("Failed to get a lock on the mutex."))?;
|
||||
m.insert(id, prog.clone());
|
||||
}
|
||||
|
||||
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) {
|
||||
let pid = episode.podcast_id();
|
||||
let id = episode.rowid();
|
||||
get_episode(&mut episode.into(), dir.as_str(), Some(prog))
|
||||
.err()
|
||||
.map(|err| {
|
||||
error!("Error while trying to download an episode");
|
||||
error!("Error: {}", err);
|
||||
});
|
||||
|
||||
if let Err(err) = get_episode(&mut episode.into(), dir.as_str(), Some(prog)) {
|
||||
error!("Error while trying to download an episode");
|
||||
error!("Error: {}", err);
|
||||
}
|
||||
|
||||
{
|
||||
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.send(Action::RefreshWidgetIfSame(pid)).unwrap();
|
||||
sender
|
||||
.send(Action::RefreshEpisodesView)
|
||||
.expect("Action channel blew up.");
|
||||
sender
|
||||
.send(Action::RefreshWidgetIfSame(pid))
|
||||
.expect("Action channel blew up.");
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -152,7 +163,7 @@ mod tests {
|
||||
let (sender, _rx) = channel();
|
||||
|
||||
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);
|
||||
|
||||
// Give it soem time to download the file
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
|
||||
|
||||
use failure::Error;
|
||||
use gdk_pixbuf::Pixbuf;
|
||||
use send_cell::SendCell;
|
||||
|
||||
@ -16,14 +17,23 @@ use std::thread;
|
||||
|
||||
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`.
|
||||
/// 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.
|
||||
pub fn refresh_feed(source: Option<Vec<Source>>, sender: Sender<Action>) {
|
||||
sender.send(Action::HeaderBarShowUpdateIndicator).unwrap();
|
||||
fn refresh_feed(source: Option<Vec<Source>>, sender: Sender<Action>) -> Result<(), Error> {
|
||||
sender.send(Action::HeaderBarShowUpdateIndicator)?;
|
||||
|
||||
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.
|
||||
// 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) {
|
||||
error!("Error While trying to update the database.");
|
||||
error!("Error msg: {}", err);
|
||||
let source = dbqueries::get_source_from_id(id).unwrap();
|
||||
|
||||
if let Err(err) = pipeline::index_single_source(source, false) {
|
||||
error!("Error While trying to update the database.");
|
||||
error!("Error msg: {}", err);
|
||||
if let Ok(source) = dbqueries::get_source_from_id(id) {
|
||||
if let Err(err) = pipeline::index_single_source(source, false) {
|
||||
error!("Error While trying to update the database.");
|
||||
error!("Error msg: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -54,9 +64,14 @@ pub fn refresh_feed(source: Option<Vec<Source>>, sender: Sender<Action>) {
|
||||
}
|
||||
}
|
||||
|
||||
sender.send(Action::HeaderBarHideUpdateIndicator).unwrap();
|
||||
sender.send(Action::RefreshAllViews).unwrap();
|
||||
sender
|
||||
.send(Action::HeaderBarHideUpdateIndicator)
|
||||
.expect("Action channel blew up.");
|
||||
sender
|
||||
.send(Action::RefreshAllViews)
|
||||
.expect("Action channel blew up.");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
@ -72,24 +87,25 @@ 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) -> Option<Pixbuf> {
|
||||
pub fn get_pixbuf_from_path(pd: &PodcastCoverQuery, size: u32) -> Result<Pixbuf, Error> {
|
||||
{
|
||||
let hashmap = CACHED_PIXBUFS.read().unwrap();
|
||||
let res = hashmap.get(&(pd.id(), size));
|
||||
if let Some(px) = res {
|
||||
let m = px.lock().unwrap();
|
||||
return Some(m.clone().into_inner());
|
||||
let hashmap = CACHED_PIXBUFS
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if let Some(px) = px {
|
||||
let mut hashmap = CACHED_PIXBUFS.write().unwrap();
|
||||
hashmap.insert((pd.id(), size), Mutex::new(SendCell::new(px.clone())));
|
||||
return Some(px);
|
||||
}
|
||||
None
|
||||
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)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -113,6 +129,6 @@ mod tests {
|
||||
// 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_some());
|
||||
assert!(pxbuf.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use chrono::prelude::*;
|
||||
use failure::Error;
|
||||
use gtk;
|
||||
use gtk::prelude::*;
|
||||
|
||||
@ -9,7 +10,6 @@ use app::Action;
|
||||
use utils::get_pixbuf_from_path;
|
||||
use widgets::episode::EpisodeWidget;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -75,9 +75,9 @@ impl Default for EpisodesView {
|
||||
|
||||
// TODO: REFACTOR ME
|
||||
impl EpisodesView {
|
||||
pub fn new(sender: Sender<Action>) -> Arc<EpisodesView> {
|
||||
pub fn new(sender: Sender<Action>) -> Result<EpisodesView, Error> {
|
||||
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();
|
||||
|
||||
episodes.into_iter().for_each(|mut ep| {
|
||||
@ -124,7 +124,7 @@ impl EpisodesView {
|
||||
}
|
||||
|
||||
view.container.show_all();
|
||||
Arc::new(view)
|
||||
Ok(view)
|
||||
}
|
||||
|
||||
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");
|
||||
let container: gtk::Box = builder.get_object("container").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());
|
||||
container.pack_start(&ep.container, true, true, 6);
|
||||
|
||||
EpisodesViewWidget {
|
||||
let view = EpisodesViewWidget {
|
||||
container,
|
||||
image,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use failure::Error;
|
||||
use gtk;
|
||||
use gtk::prelude::*;
|
||||
|
||||
@ -32,41 +33,34 @@ impl Default for ShowsPopulated {
|
||||
}
|
||||
|
||||
impl ShowsPopulated {
|
||||
pub fn new(sender: Sender<Action>) -> ShowsPopulated {
|
||||
pub fn new(sender: Sender<Action>) -> Result<ShowsPopulated, Error> {
|
||||
let pop = ShowsPopulated::default();
|
||||
pop.init(sender);
|
||||
pop
|
||||
pop.init(sender)?;
|
||||
Ok(pop)
|
||||
}
|
||||
|
||||
pub fn init(&self, sender: Sender<Action>) {
|
||||
use gtk::WidgetExt;
|
||||
|
||||
// TODO: handle unwraps.
|
||||
pub fn init(&self, sender: Sender<Action>) -> Result<(), Error> {
|
||||
self.flowbox.connect_child_activated(move |_, child| {
|
||||
// This is such an ugly hack...
|
||||
let id = WidgetExt::get_name(child).unwrap().parse::<i32>().unwrap();
|
||||
let pd = dbqueries::get_podcast_from_id(id).unwrap();
|
||||
|
||||
sender
|
||||
.send(Action::HeaderBarShowTile(pd.title().into()))
|
||||
.unwrap();
|
||||
sender.send(Action::ReplaceWidget(pd)).unwrap();
|
||||
sender.send(Action::ShowWidgetAnimated).unwrap();
|
||||
if let Err(err) = on_child_activate(child, sender.clone()) {
|
||||
error!(
|
||||
"Something went wrong during flowbox child activation: {}.",
|
||||
err
|
||||
)
|
||||
};
|
||||
});
|
||||
// Populate the flowbox with the Podcasts.
|
||||
self.populate_flowbox();
|
||||
self.populate_flowbox()
|
||||
}
|
||||
|
||||
fn populate_flowbox(&self) {
|
||||
let podcasts = dbqueries::get_podcasts();
|
||||
fn populate_flowbox(&self) -> Result<(), Error> {
|
||||
let podcasts = dbqueries::get_podcasts()?;
|
||||
|
||||
if let Ok(pds) = podcasts {
|
||||
pds.iter().for_each(|parent| {
|
||||
let flowbox_child = ShowsChild::new(parent);
|
||||
self.flowbox.add(&flowbox_child.child);
|
||||
});
|
||||
self.flowbox.show_all();
|
||||
}
|
||||
podcasts.iter().for_each(|parent| {
|
||||
let flowbox_child = ShowsChild::new(parent);
|
||||
self.flowbox.add(&flowbox_child.child);
|
||||
});
|
||||
self.flowbox.show_all();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
@ -79,6 +73,21 @@ impl ShowsPopulated {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_child_activate(child: >k::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)]
|
||||
struct ShowsChild {
|
||||
container: gtk::Box,
|
||||
@ -114,11 +123,16 @@ impl ShowsChild {
|
||||
fn init(&self, pd: &Podcast) {
|
||||
self.container.set_tooltip_text(pd.title());
|
||||
|
||||
let cover = get_pixbuf_from_path(&pd.clone().into(), 256);
|
||||
if let Some(img) = cover {
|
||||
self.cover.set_from_pixbuf(&img);
|
||||
};
|
||||
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: &Podcast) -> Result<(), Error> {
|
||||
let image = get_pixbuf_from_path(&pd.clone().into(), 256)?;
|
||||
self.cover.set_from_pixbuf(&image);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,13 +123,20 @@ impl EpisodeWidget {
|
||||
self.show_buttons(episode.local_uri());
|
||||
|
||||
// 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;
|
||||
self.play
|
||||
.connect_clicked(clone!(episode, title, sender => move |_| {
|
||||
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() {
|
||||
title
|
||||
.get_style_context()
|
||||
@ -141,7 +148,12 @@ impl EpisodeWidget {
|
||||
self.download
|
||||
.connect_clicked(clone!(episode, sender => move |dl| {
|
||||
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
|
||||
fn determine_progess_bar(&self) {
|
||||
// Something Something State-Machine?
|
||||
fn determine_progess_bar(&self) -> Result<(), Error> {
|
||||
let id = WidgetExt::get_name(&self.container)
|
||||
.unwrap()
|
||||
.parse::<i32>()
|
||||
.unwrap();
|
||||
.ok_or_else(|| format_err!("Failed to get widget Name"))?
|
||||
.parse::<i32>()?;
|
||||
|
||||
let prog_struct = || -> Option<_> {
|
||||
if let Ok(m) = manager::ACTIVE_DOWNLOADS.read() {
|
||||
if !m.contains_key(&id) {
|
||||
return None;
|
||||
};
|
||||
return m.get(&id).cloned();
|
||||
}
|
||||
None
|
||||
}();
|
||||
let active_dl = || -> Result<Option<_>, Error> {
|
||||
let m = manager::ACTIVE_DOWNLOADS
|
||||
.read()
|
||||
.map_err(|_| format_err!("Failed to get a lock on the mutex."))?;
|
||||
|
||||
let progress_bar = self.progress.clone();
|
||||
let total_size = self.total_size.clone();
|
||||
let local_size = self.local_size.clone();
|
||||
if let Some(prog) = prog_struct {
|
||||
Ok(m.get(&id).cloned())
|
||||
}()?;
|
||||
|
||||
if let Some(prog) = active_dl {
|
||||
// FIXME: Document me?
|
||||
self.download.hide();
|
||||
self.progress.show();
|
||||
self.local_size.show();
|
||||
@ -232,13 +240,17 @@ impl EpisodeWidget {
|
||||
self.prog_separator.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.
|
||||
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
|
||||
// with the http ContentLength header number rather than
|
||||
// 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| {
|
||||
if let Ok(mut m) = prog.lock() {
|
||||
@ -247,44 +259,37 @@ impl EpisodeWidget {
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: Sender<Action>) {
|
||||
let download_fold = dbqueries::get_podcast_from_id(ep.podcast_id())
|
||||
.ok()
|
||||
.map(|pd| get_download_folder(&pd.title().to_owned()).ok())
|
||||
.and_then(|x| x);
|
||||
fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: Sender<Action>) -> Result<(), Error> {
|
||||
let pd = dbqueries::get_podcast_from_id(ep.podcast_id())?;
|
||||
let download_fold = get_download_folder(&pd.title().to_owned())?;
|
||||
|
||||
// Start a new download.
|
||||
if let Some(fold) = download_fold {
|
||||
manager::add(ep.rowid(), &fold, sender.clone());
|
||||
}
|
||||
manager::add(ep.rowid(), &download_fold, sender.clone())?;
|
||||
|
||||
// Update Views
|
||||
sender.send(Action::RefreshEpisodesView).unwrap();
|
||||
sender.send(Action::RefreshWidgetIfVis).unwrap();
|
||||
sender.send(Action::RefreshEpisodesView)?;
|
||||
sender.send(Action::RefreshWidgetIfVis)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_play_bttn_clicked(episode_id: i32) {
|
||||
let local_uri = dbqueries::get_episode_local_uri_from_id(episode_id)
|
||||
.ok()
|
||||
.and_then(|x| x);
|
||||
fn on_play_bttn_clicked(episode_id: i32) -> Result<(), Error> {
|
||||
let uri = dbqueries::get_episode_local_uri_from_id(episode_id)?
|
||||
.ok_or_else(|| format_err!("Expected Some found None."))?;
|
||||
|
||||
if let Some(uri) = local_uri {
|
||||
if Path::new(&uri).exists() {
|
||||
info!("Opening {}", uri);
|
||||
open::that(&uri).err().map(|err| {
|
||||
error!("Error while trying to open file: {}", uri);
|
||||
error!("Error: {}", err);
|
||||
});
|
||||
}
|
||||
if Path::new(&uri).exists() {
|
||||
info!("Opening {}", uri);
|
||||
open::that(&uri)?;
|
||||
} else {
|
||||
error!(
|
||||
"Something went wrong evaluating the following path: {:?}",
|
||||
local_uri
|
||||
);
|
||||
bail!("File \"{}\" does not exist.", uri);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 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(
|
||||
prog: Arc<Mutex<manager::Progress>>,
|
||||
episode_rowid: i32,
|
||||
progress_bar: gtk::ProgressBar,
|
||||
local_size: gtk::Label,
|
||||
progress_bar: >k::ProgressBar,
|
||||
local_size: >k::Label,
|
||||
) {
|
||||
timeout_add(
|
||||
400,
|
||||
clone!(prog, progress_bar => move || {
|
||||
let (fraction, downloaded) = {
|
||||
let m = prog.lock().unwrap();
|
||||
(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)
|
||||
}
|
||||
clone!(prog, progress_bar, progress_bar, local_size=> move || {
|
||||
progress_bar_helper(prog.clone(), episode_rowid, &progress_bar, &local_size)
|
||||
.unwrap_or(glib::Continue(false))
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn progress_bar_helper(
|
||||
prog: Arc<Mutex<manager::Progress>>,
|
||||
episode_rowid: i32,
|
||||
progress_bar: >k::ProgressBar,
|
||||
local_size: >k::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
|
||||
// with the http ContentLength header number rather than
|
||||
// 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: >k::Label) {
|
||||
timeout_add(
|
||||
500,
|
||||
clone!(prog, total_size => move || {
|
||||
let total_bytes = {
|
||||
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)
|
||||
}
|
||||
total_size_helper(prog.clone(), &total_size).unwrap_or(glib::Continue(true))
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// fn on_delete_bttn_clicked(episode_id: i32) {
|
||||
// let mut ep = dbqueries::get_episode_from_rowid(episode_id)
|
||||
// .unwrap()
|
||||
// .into();
|
||||
fn total_size_helper(
|
||||
prog: Arc<Mutex<manager::Progress>>,
|
||||
total_size: >k::Label,
|
||||
) -> 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);
|
||||
// if let Err(err) = e {
|
||||
// error!("Error while trying to delete file: {:?}", ep.local_uri());
|
||||
// error!("Error: {}", err);
|
||||
// };
|
||||
debug!("Total Size: {}", total_bytes);
|
||||
if total_bytes != 0 {
|
||||
// Update the total_size label
|
||||
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> {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use dissolve;
|
||||
use failure::Error;
|
||||
use gtk;
|
||||
use gtk::prelude::*;
|
||||
use open;
|
||||
@ -65,20 +66,26 @@ impl ShowWidget {
|
||||
|
||||
self.unsub
|
||||
.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.set_cover(pd);
|
||||
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();
|
||||
self.link.set_tooltip_text(Some(link.as_str()));
|
||||
self.link.connect_clicked(move |_| {
|
||||
info!("Opening link: {}", &link);
|
||||
open::that(&link)
|
||||
.err()
|
||||
.map(|err| error!("Something went wrong: {}", err));
|
||||
if let Err(err) = open::that(&link) {
|
||||
error!("Failed to open link: {}", &link);
|
||||
error!("Error: {}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -87,11 +94,11 @@ impl ShowWidget {
|
||||
let listbox = episodes_listbox(pd, sender.clone());
|
||||
listbox.ok().map(|l| self.episodes.add(&l));
|
||||
}
|
||||
|
||||
/// Set the show cover.
|
||||
fn set_cover(&self, pd: &Podcast) {
|
||||
let img = get_pixbuf_from_path(&pd.clone().into(), 128);
|
||||
img.map(|i| self.cover.set_from_pixbuf(&i));
|
||||
fn set_cover(&self, pd: &Podcast) -> Result<(), Error> {
|
||||
let image = get_pixbuf_from_path(&pd.clone().into(), 128)?;
|
||||
self.cover.set_from_pixbuf(&image);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the descripton text.
|
||||
@ -107,7 +114,11 @@ impl ShowWidget {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_unsub_button_clicked(pd: &Podcast, unsub_button: >k::Button, sender: Sender<Action>) {
|
||||
fn on_unsub_button_clicked(
|
||||
pd: &Podcast,
|
||||
unsub_button: >k::Button,
|
||||
sender: Sender<Action>,
|
||||
) -> Result<(), Error> {
|
||||
// hack to get away without properly checking for none.
|
||||
// if pressed twice would panic.
|
||||
unsub_button.hide();
|
||||
@ -118,16 +129,19 @@ fn on_unsub_button_clicked(pd: &Podcast, unsub_button: >k::Button, sender: Sen
|
||||
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.
|
||||
sender.send(Action::RefreshShowsView).unwrap();
|
||||
sender.send(Action::RefreshEpisodesView).unwrap();
|
||||
sender.send(Action::RefreshShowsView)?;
|
||||
sender.send(Action::RefreshEpisodesView)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn on_played_button_clicked(pd: &Podcast, sender: Sender<Action>) {
|
||||
let _ = dbqueries::update_none_to_played_now(pd);
|
||||
|
||||
sender.send(Action::RefreshWidget).unwrap();
|
||||
fn on_played_button_clicked(pd: &Podcast, sender: Sender<Action>) -> Result<(), Error> {
|
||||
dbqueries::update_none_to_played_now(pd)?;
|
||||
sender.send(Action::RefreshWidget)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user