diff --git a/src/commands/clone.rs b/src/commands/clone.rs index d75e0d6..6afbeb3 100644 --- a/src/commands/clone.rs +++ b/src/commands/clone.rs @@ -9,7 +9,6 @@ use crate::utils::api::ApiProps; use crate::global::global::{DIR_PATH, set_dir_path}; use crate::services::api::ApiError; use crate::services::req_props::{ReqProps, ObjProps}; -use crate::services::download_files::DownloadFiles; use crate::store::object::{tree, blob}; use crate::commands::config; use crate::commands::init; @@ -127,6 +126,7 @@ pub fn clone(remote: Values<'_>) { let downloader = Downloader::new() .set_api_props(api_props.clone()) .set_files(files) + .should_log() .download(ref_path.clone(), Some(&save_blob)); } diff --git a/src/services/download_files.rs b/src/services/download_files.rs index 6b1236d..9df04a7 100644 --- a/src/services/download_files.rs +++ b/src/services/download_files.rs @@ -40,7 +40,7 @@ impl DownloadFiles { } } - pub fn save_stream(&mut self, ref_p: PathBuf) -> Result<(), ApiError> { + pub fn save_stream(&mut self, ref_p: PathBuf, callback: Option) -> Result<(), ApiError> { let abs_p = ref_p.join(PathBuf::from(self.relative_ps.clone())); let mut file = File::create(abs_p).unwrap(); @@ -48,11 +48,18 @@ impl DownloadFiles { let res = self.send().await.map_err(ApiError::RequestError)?; if res.status().is_success() { let mut stream = res.bytes_stream(); + while let Some(chunk) = stream.next().await { - if let Err(err) = file.write_all(&chunk.unwrap()) { + let unwrap_chunk = chunk.unwrap(); + // save chunk inside file + if let Err(err) = file.write_all(&unwrap_chunk) { return Err(ApiError::Unexpected(err.to_string())); + } else if let Some(fct) = &callback { + // call callback with size of this chunk + fct(unwrap_chunk.len().try_into().unwrap()); } } + Ok(()) } else { Err(ApiError::IncorrectRequest(res)) diff --git a/src/services/downloader.rs b/src/services/downloader.rs index 26da4c4..44c88b9 100644 --- a/src/services/downloader.rs +++ b/src/services/downloader.rs @@ -1,4 +1,6 @@ use std::path::PathBuf; +use indicatif::{ProgressBar, MultiProgress, ProgressStyle, HumanBytes}; + use crate::utils::api::ApiProps; use crate::services::api::ApiError; use crate::services::download_files::DownloadFiles; @@ -10,6 +12,8 @@ pub struct Downloader { files: Vec, should_log: bool, api_props: Option, + progress_bars: Vec, + multi_progress: Option, } impl Downloader { @@ -18,6 +22,8 @@ impl Downloader { files: vec![], should_log: false, api_props: None, + progress_bars: vec![], + multi_progress: None, } } @@ -41,35 +47,95 @@ impl Downloader { self } + fn init_log(&mut self, nb_objs: u64, total_size: u64) { + self.multi_progress = Some(MultiProgress::new()); + + self.progress_bars.push( + self.multi_progress + .clone() + .unwrap() + .add(ProgressBar::new(nb_objs).with_message("Objects"))); + + let msg = format!("0B/{}", HumanBytes(total_size).to_string()); + self.progress_bars.push( + self.multi_progress + .clone() + .unwrap() + .add(ProgressBar::new(total_size).with_message(msg))); + + self.progress_bars[0].set_style( + ProgressStyle::with_template("{_:>10} [{bar:40}] {pos}/{len} {msg}") + .unwrap() + .progress_chars("=> ")); + + self.progress_bars[1].set_style( + ProgressStyle::with_template("[{elapsed_precise}] [{bar:40}] {msg}") + .unwrap() + .progress_chars("=> ")); + + self.progress_bars[0].tick(); + self.progress_bars[1].tick(); + } + + fn update_bytes_bar(&self, size: u64) { + let bytes_bar = &self.progress_bars[1]; + bytes_bar.inc(size); + let msg = format!( + "{}/{}", + HumanBytes(bytes_bar.position()).to_string(), + HumanBytes(bytes_bar.length().unwrap()).to_string()); + bytes_bar.set_message(msg); + } + pub fn download(&mut self, ref_p: PathBuf, callback: Option<&dyn Fn(ObjProps)>) { + if self.should_log { + let mut total_size = 0; + let nb_objs = self.files.len(); + + self.files + .iter() + .for_each(|f| + if let Some(size) = f.contentlength { + total_size += size + } + ); + + self.init_log(nb_objs.try_into().unwrap(), total_size); + } + for file in self.files.clone() { let relative_s = &file.clone().relative_s.unwrap(); let mut download = DownloadFiles::new(); download.set_url(&relative_s, &self.api_props.clone().unwrap()); - let res = { + let should_use_stream = { if let Some(size) = file.contentlength { if size > SIZE_TO_STREAM { - download.save_stream(ref_p.clone()) + true } else { - download.save(ref_p.clone()) + false } + } else { + false + } + }; + + // download + let res = { + if should_use_stream { + // todo should increment here + download.save_stream(ref_p.clone(), Some(|a| self.update_bytes_bar(a))) } else { download.save(ref_p.clone()) } }; - + // deal with error match res { Ok(()) => { if let Some(fct) = callback { - fct(file); + fct(file.clone()); } - //let relative_p = PathBuf::from(&relative_s); - //let lastmodified = obj.clone().lastmodified.unwrap().timestamp_millis(); - //if let Err(err) = blob::add(relative_p, &lastmodified.to_string()) { - // eprintln!("err: saving ref of {} ({})", relative_s.clone(), err); - //} }, Err(ApiError::Unexpected(_)) => { eprintln!("err: writing {}", relative_s); @@ -84,6 +150,22 @@ impl Downloader { std::process::exit(1); } } + + // increment loading bars + if self.should_log { + self.progress_bars[0].inc(1); // increment object + + // increment bytes only if + // not incremented continuously by stream + if !should_use_stream { + self.update_bytes_bar(file.contentlength.unwrap()); + } + } + } + + // finish all bars + for bar in &self.progress_bars { + bar.finish(); } } }