diff --git a/hammond-downloader/src/downloader.rs b/hammond-downloader/src/downloader.rs index 9980b9e..b985a26 100644 --- a/hammond-downloader/src/downloader.rs +++ b/hammond-downloader/src/downloader.rs @@ -8,6 +8,7 @@ use std::fs::{rename, DirBuilder, File}; use std::io::{BufWriter, Read, Write}; use std::path::Path; use std::fs; +use std::sync::{Arc, Mutex}; use errors::*; use hammond_data::{EpisodeWidgetQuery, PodcastCoverQuery}; @@ -16,6 +17,11 @@ use hammond_data::xdg_dirs::{DL_DIR, HAMMOND_CACHE}; // TODO: Replace path that are of type &str with std::path. // TODO: Have a convention/document absolute/relative paths, if they should end with / or not. +pub trait DownloadProgress { + fn set_downloaded(&mut self, downloaded: u64); + fn set_size(&mut self, bytes: u64); +} + // Adapted from https://github.com/mattgathu/rget . // I never wanted to write a custom downloader. // Sorry to those who will have to work with that code. @@ -23,7 +29,12 @@ use hammond_data::xdg_dirs::{DL_DIR, HAMMOND_CACHE}; // or bindings for a lib like youtube-dl(python), // But cant seem to find one. // TODO: Write unit-tests. -fn download_into(dir: &str, file_title: &str, url: &str) -> Result { +fn download_into( + dir: &str, + file_title: &str, + url: &str, + progress: Option>>, +) -> Result { info!("GET request to: {}", url); let client = reqwest::Client::builder().referer(false).build()?; let mut resp = client.get(url).send()?; @@ -47,8 +58,15 @@ fn download_into(dir: &str, file_title: &str, url: &str) -> Result { let tempdir = TempDir::new_in(HAMMOND_CACHE.to_str().unwrap(), "temp_download")?; let out_file = format!("{}/temp.part", tempdir.path().to_str().unwrap(),); + ct_len.map(|x| { + if let Some(p) = progress.clone() { + let mut m = p.lock().unwrap(); + m.set_size(x); + } + }); + // Save requested content into the file. - save_io(&out_file, &mut resp, ct_len)?; + save_io(&out_file, &mut resp, ct_len, progress)?; // Construct the desired path. let target = format!("{}/{}.{}", dir, file_title, ext); @@ -73,8 +91,14 @@ fn get_ext(content: Option) -> Option { } // TODO: Write unit-tests. +// TODO: Refactor... Somehow. /// Handles the I/O of fetching a remote file and saving into a Buffer and A File. -fn save_io(file: &str, resp: &mut reqwest::Response, content_lenght: Option) -> Result<()> { +fn save_io( + file: &str, + resp: &mut reqwest::Response, + content_lenght: Option, + progress: Option>>, +) -> Result<()> { info!("Downloading into: {}", file); let chunk_size = match content_lenght { Some(x) => x as usize / 99, @@ -89,6 +113,14 @@ fn save_io(file: &str, resp: &mut reqwest::Response, content_lenght: Option buffer.truncate(bcount); if !buffer.is_empty() { writer.write_all(buffer.as_slice())?; + if let Some(prog) = progress.clone() { + // This sucks. + let len = writer.get_ref().metadata().map(|x| x.len()); + if let Ok(l) = len { + let mut m = prog.lock().unwrap(); + m.set_downloaded(l); + } + } } else { break; } @@ -107,7 +139,11 @@ pub fn get_download_folder(pd_title: &str) -> Result { } // TODO: Refactor -pub fn get_episode(ep: &mut EpisodeWidgetQuery, download_folder: &str) -> Result<()> { +pub fn get_episode( + ep: &mut EpisodeWidgetQuery, + download_folder: &str, + progress: Option>>, +) -> Result<()> { // Check if its alrdy downloaded if ep.local_uri().is_some() { if Path::new(ep.local_uri().unwrap()).exists() { @@ -119,7 +155,12 @@ pub fn get_episode(ep: &mut EpisodeWidgetQuery, download_folder: &str) -> Result ep.save()?; }; - let res = download_into(download_folder, &ep.rowid().to_string(), ep.uri().unwrap()); + let res = 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. @@ -166,7 +207,7 @@ pub fn cache_image(pd: &PodcastCoverQuery) -> Option { .create(&cache_download_fold) .unwrap(); - match download_into(&cache_download_fold, "cover", &url) { + match download_into(&cache_download_fold, "cover", &url, None) { Ok(path) => { info!("Cached img into: {}", &path); Some(path) diff --git a/hammond-gtk/src/manager.rs b/hammond-gtk/src/manager.rs index 9a21819..fa74bbc 100644 --- a/hammond-gtk/src/manager.rs +++ b/hammond-gtk/src/manager.rs @@ -1,6 +1,7 @@ // use hammond_data::Episode; use hammond_data::dbqueries; use hammond_downloader::downloader::get_episode; +use hammond_downloader::downloader::DownloadProgress; use app::Action; @@ -18,15 +19,33 @@ pub struct Progress { } impl Progress { - pub fn new(size: u64) -> Self { + pub fn get_fraction(&self) -> f64 { + info!("Progress: {:?}", self); + let ratio = self.downloaded_bytes as f64 / self.total_bytes as f64; + + if ratio >= 1.0 { + return 1.0; + }; + ratio + } +} + +impl Default for Progress { + fn default() -> Self { Progress { - total_bytes: size, + total_bytes: 0, downloaded_bytes: 0, } } +} - pub fn get_fraction(&self) -> f64 { - self.downloaded_bytes as f64 / self.total_bytes as f64 +impl DownloadProgress for Progress { + fn set_downloaded(&mut self, downloaded: u64) { + self.downloaded_bytes = downloaded + } + + fn set_size(&mut self, bytes: u64) { + self.total_bytes = bytes; } } @@ -36,17 +55,23 @@ lazy_static! { }; } -pub fn add(id: i32, directory: &str, sender: Sender, prog: Arc>) { +pub fn add(id: i32, directory: &str, sender: Sender) { + // Create a new `Progress` struct to keep track of dl progress. + let prog = Arc::new(Mutex::new(Progress::default())); + { let mut m = ACTIVE_DOWNLOADS.write().unwrap(); m.insert(id, prog.clone()); } + { + let m = ACTIVE_DOWNLOADS.read().unwrap(); + info!("ACTIVE DOWNLOADS: {:#?}", m); + } let dir = directory.to_owned(); thread::spawn(move || { - info!("{:?}", prog); // just checking that it compiles let episode = dbqueries::get_episode_from_rowid(id).unwrap(); - let e = get_episode(&mut episode.into(), dir.as_str()); + let e = get_episode(&mut episode.into(), dir.as_str(), Some(prog)); if let Err(err) = e { error!("Error: {}", err); }; @@ -105,10 +130,9 @@ mod tests { }; let (sender, _rx) = channel(); - let prog = Arc::new(Mutex::new(Progress::new(42))); let download_fold = downloader::get_download_folder(&pd.title()).unwrap(); - add(episode.rowid(), download_fold.as_str(), sender, prog); + add(episode.rowid(), download_fold.as_str(), sender); // Give it soem time to download the file thread::sleep(time::Duration::from_secs(40)); diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index fae1ed7..75d9885 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -17,7 +17,6 @@ use app::Action; use manager; use std::sync::mpsc::Sender; -use std::sync::{Arc, Mutex}; use std::path::Path; #[derive(Debug, Clone)] @@ -80,8 +79,6 @@ impl EpisodeWidget { widget } - // TODO: calculate lenght. - // TODO: wire the progress_bar to the downloader. // TODO: wire the cancel button. fn init(&self, episode: &mut EpisodeWidgetQuery, sender: Sender) { WidgetExt::set_name(&self.container, &episode.rowid().to_string()); @@ -218,13 +215,24 @@ impl EpisodeWidget { let m = prog.lock().unwrap(); m.get_fraction() }; - progress_bar.set_fraction(fraction); - // info!("Fraction: {}", progress_bar.get_fraction()); - if fraction != 1.0{ - glib::Continue(true) - } else { + // I hate floating points. + if (fraction >= 0.0) && (fraction <= 1.0) && (!fraction.is_nan()) { + progress_bar.set_fraction(fraction); + } + // info!("Fraction: {}", progress_bar.get_fraction()); + // info!("Fraction: {}", fraction); + let active = { + let m = manager::ACTIVE_DOWNLOADS.read().unwrap(); + m.contains_key(&id) + }; + + if (fraction >= 1.0) && (!fraction.is_nan()){ glib::Continue(false) + } else if !active { + glib::Continue(false) + }else { + glib::Continue(true) } }), ); @@ -236,10 +244,8 @@ fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: Sender) { let pd = dbqueries::get_podcast_from_id(ep.podcast_id()).unwrap(); let download_fold = downloader::get_download_folder(&pd.title().to_owned()).unwrap(); - // Create a new `Progress` struct to keep track of dl progress. - let prog = Arc::new(Mutex::new(manager::Progress::new(42))); // Start a new download. - manager::add(ep.rowid(), &download_fold, sender.clone(), prog.clone()); + manager::add(ep.rowid(), &download_fold, sender.clone()); // Update Views sender.send(Action::RefreshEpisodesView).unwrap();