From 51f52c3408f94bf955327a549fc6d927fc9551cd Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Sun, 22 Oct 2017 01:02:48 +0300 Subject: [PATCH] Async update of the download and play buttons upon download finished. --- hammond-cli/src/main.rs | 3 +- hammond-downloader/src/downloader.rs | 70 +++++++++++++---------- hammond-gtk/src/widgets/episode.rs | 83 +++++++++++++++++++++------- hammond-gtk/src/widgets/podcast.rs | 3 +- 4 files changed, 107 insertions(+), 52 deletions(-) diff --git a/hammond-cli/src/main.rs b/hammond-cli/src/main.rs index deebc72..ca00979 100644 --- a/hammond-cli/src/main.rs +++ b/hammond-cli/src/main.rs @@ -56,7 +56,8 @@ fn run() -> Result<()> { if args.dl >= 0 { let db = hammond_data::establish_connection(); - downloader::latest_dl(&db, args.dl as u32).unwrap(); + let db = Arc::new(Mutex::new(db)); + downloader::latest_dl(db, args.dl as u32).unwrap(); } if args.latest { diff --git a/hammond-downloader/src/downloader.rs b/hammond-downloader/src/downloader.rs index 9d9f784..1de6896 100644 --- a/hammond-downloader/src/downloader.rs +++ b/hammond-downloader/src/downloader.rs @@ -1,3 +1,6 @@ +#![cfg_attr(feature = "cargo-clippy", allow(clone_on_ref_ptr))] +#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] + use reqwest; use hyper::header::*; use diesel::prelude::*; @@ -5,7 +8,7 @@ use diesel::prelude::*; use std::fs::{rename, DirBuilder, File}; use std::io::{BufWriter, Read, Write}; use std::path::Path; -use std::thread; +use std::sync::{Arc, Mutex}; use errors::*; use hammond_data::dbqueries; @@ -58,22 +61,28 @@ pub fn download_to(target: &str, url: &str) -> Result<()> { // Initial messy prototype, queries load alot of not needed stuff. // TODO: Refactor -pub fn latest_dl(connection: &SqliteConnection, limit: u32) -> Result<()> { - let pds = dbqueries::get_podcasts(connection)?; +pub fn latest_dl(connection: Arc>, limit: u32) -> Result<()> { + let pds = { + let tempdb = connection.lock().unwrap(); + dbqueries::get_podcasts(&tempdb)? + }; let _: Vec<_> = pds.iter() .map(|x| -> Result<()> { - let mut eps = if limit == 0 { - dbqueries::get_pd_episodes(connection, x)? - } else { - dbqueries::get_pd_episodes_limit(connection, x, limit)? + let mut eps = { + let tempdb = connection.lock().unwrap(); + if limit == 0 { + dbqueries::get_pd_episodes(&tempdb, x)? + } else { + dbqueries::get_pd_episodes_limit(&tempdb, x, limit)? + } }; let dl_fold = get_dl_folder(x.title())?; // Download the episodes eps.iter_mut().for_each(|ep| { - let x = get_episode(connection, ep, &dl_fold); + let x = get_episode(connection.clone(), ep, &dl_fold); if let Err(err) = x { error!("An Error occured while downloading an episode."); error!("Error: {}", err); @@ -97,14 +106,22 @@ pub fn get_dl_folder(pd_title: &str) -> Result { Ok(dl_fold) } -pub fn get_episode(connection: &SqliteConnection, ep: &mut Episode, dl_folder: &str) -> Result<()> { +// TODO: Refactor +pub fn get_episode( + connection: Arc>, + ep: &mut Episode, + dl_folder: &str, +) -> Result<()> { // Check if its alrdy downloaded if ep.local_uri().is_some() { if Path::new(ep.local_uri().unwrap()).exists() { return Ok(()); } - ep.set_local_uri(None); - ep.save_changes::(connection)?; + { + let db = connection.lock().unwrap(); + ep.set_local_uri(None); + ep.save_changes::(&*db)?; + } }; // FIXME: Unreliable and hacky way to extract the file extension from the url. @@ -113,28 +130,23 @@ pub fn get_episode(connection: &SqliteConnection, ep: &mut Episode, dl_folder: & // Construct the download path. // TODO: Check if its a valid path let dlpath = format!("{}/{}.{}", dl_folder, ep.title().unwrap().to_owned(), ext); + let dlpath1 = dlpath.clone(); // info!("Downloading {:?} into: {}", y.title(), dlpath); - // If download succedes set episode local_uri to dlpath. - // This should be at the end after download is finished, - // but its handy atm while the gtk client doesnt yet have custom signals. - ep.set_local_uri(Some(&dlpath)); - ep.save_changes::(connection)?; - let uri = ep.uri().to_owned(); + let res = download_to(&dlpath1, uri.as_str()); - // This would not be needed in general but I want to be able to call - // this function from the gtk client. - // should get removed probably once custom callbacks are implemented. - thread::spawn(move || { - let res = download_to(&dlpath, uri.as_str()); - if let Err(err) = res { - error!("Something whent wrong while downloading."); - error!("Error: {}", err); - } else { - info!("Download of {} finished.", uri); - } - }); + if let Err(err) = res { + error!("Something whent wrong while downloading."); + error!("Error: {}", err); + } else { + info!("Download of {} finished.", uri); + }; + + // If download succedes set episode local_uri to dlpath. + ep.set_local_uri(Some(&dlpath)); + let db = connection.lock().unwrap(); + ep.save_changes::(&*db)?; Ok(()) } diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 2eb1bb2..d75dfca 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -10,13 +10,22 @@ use hammond_downloader::downloader; use dissolve::strip_html_tags; use std::thread; +use std::cell::RefCell; use std::sync::{Arc, Mutex}; +use std::sync::mpsc::{channel, Receiver}; use std::path::Path; +use glib; use gtk; use gtk::prelude::*; use gtk::ContainerExt; +thread_local!( + static GLOBAL: RefCell))>> = RefCell::new(None)); + +// TODO: REFACTOR AND MODULATE ME. fn epidose_widget( connection: Arc>, episode: &mut Episode, @@ -65,35 +74,67 @@ fn epidose_widget( } } - let pd_title_cloned = pd_title.to_owned(); + // TODO: figure out how to use the gtk-clone macro, + // to make it less tedious. + let pd_title_clone = pd_title.to_owned(); let db = connection.clone(); let ep_clone = episode.clone(); let play_button_clone = play_button.clone(); - dl_button.connect_clicked(move |dl| { - // ugly hack to bypass the borrowchecker - let pd_title = pd_title_cloned.clone(); - let db_clone = db.clone(); - let mut ep_clone = ep_clone.clone(); - - // TODO: emit a signal and show notification when dl is finished and block play_bttn till - // then. - thread::spawn(move || { - let dl_fold = downloader::get_dl_folder(&pd_title).unwrap(); - let tempdb = db_clone.lock().unwrap(); - let e = downloader::get_episode(&tempdb, &mut ep_clone, dl_fold.as_str()); - drop(tempdb); - if let Err(err) = e { - error!("Error while trying to download: {}", ep_clone.uri()); - error!("Error: {}", err); - }; - }); - dl.hide(); - play_button_clone.show(); + let dl_button_clone = dl_button.clone(); + dl_button.connect_clicked(move |_| { + on_dl_clicked( + db.clone(), + &pd_title_clone, + &mut ep_clone.clone(), + dl_button_clone.clone(), + play_button_clone.clone(), + ); }); ep } +// TODO: show notification when dl is finished and block play_bttn till then. +fn on_dl_clicked( + db: Arc>, + pd_title: &str, + ep: &mut Episode, + dl_bttn: gtk::Button, + play_bttn: gtk::Button, +) { + // Create a async channel. + let (sender, receiver) = channel(); + + // Pass the desired arguments into the Local Thread Storage. + GLOBAL.with(move |global| { + *global.borrow_mut() = Some((dl_bttn, play_bttn, receiver)); + }); + + let pd_title = pd_title.to_owned(); + let mut ep = ep.clone(); + thread::spawn(move || { + let dl_fold = downloader::get_dl_folder(&pd_title).unwrap(); + let e = downloader::get_episode(db, &mut ep, dl_fold.as_str()); + if let Err(err) = e { + error!("Error while trying to download: {}", ep.uri()); + error!("Error: {}", err); + }; + sender.send(true).expect("Couldn't send data to channel");; + glib::idle_add(receive); + }); +} + +fn receive() -> glib::Continue { + GLOBAL.with(|global| { + if let Some((ref dl_bttn, ref play_bttn, ref reciever)) = *global.borrow() { + if reciever.try_recv().is_ok() { + dl_bttn.hide(); + play_bttn.show(); + } + } + }); + glib::Continue(false) +} pub fn episodes_listbox(connection: Arc>, pd_title: &str) -> gtk::ListBox { // TODO: handle unwraps. diff --git a/hammond-gtk/src/widgets/podcast.rs b/hammond-gtk/src/widgets/podcast.rs index d0689f7..794adc2 100644 --- a/hammond-gtk/src/widgets/podcast.rs +++ b/hammond-gtk/src/widgets/podcast.rs @@ -102,10 +102,11 @@ pub fn podcast_liststore(connection: &SqliteConnection) -> gtk::ListStore { // &Podcast){ // let old = stack.get_child_by_name("pdw").unwrap(); // let pdw = pd_widget_from_diesel_model(&db.clone(), pd, &stack.clone()); +// let vis = stack.get_visible_child_name().unwrap(); // stack.remove(&old); // stack.add_named(&pdw, "pdw"); -// stack.set_visible_child_full("pdw", StackTransitionType::None); +// stack.set_visible_child_name(&vis); // } pub fn pd_widget_from_diesel_model(db: Arc>, pd: &Podcast) -> gtk::Box {