From 5575392f7340e0cb97295ea1c85a213c7cd3f011 Mon Sep 17 00:00:00 2001 From: rmanach Date: Fri, 24 Oct 2025 17:18:43 +0200 Subject: [PATCH] rework main signature + impl builder for optimizer --- src/file.rs | 2 +- src/main.rs | 24 +++++++++++++----- src/optimizer.rs | 65 +++++++++++++++++++++++++++++++++--------------- 3 files changed, 64 insertions(+), 27 deletions(-) diff --git a/src/file.rs b/src/file.rs index 0fc3b1f..7a1e575 100644 --- a/src/file.rs +++ b/src/file.rs @@ -195,7 +195,7 @@ pub struct Directory { impl Directory { pub fn from_path(path: &str) -> Result> { if !std::path::Path::new(path).is_dir() { - return Err(format!("Directory path: {} must be a directory", path).into()); + return Err(format!("directory path: {} must be a directory", path).into()); } let mut nb_files = 0; diff --git a/src/main.rs b/src/main.rs index 8af81a6..587ba29 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use std::process::exit; + use clap::Parser; use env_logger::Env; @@ -22,17 +24,29 @@ struct Args { } #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let args = Args::parse(); - let directory = Directory::from_path(&args.src)?; + let directory = match Directory::from_path(&args.src) { + Ok(d) => d, + Err(e) => { + log::error!( + "unable to load directory from path: {}, details: {}", + args.src, + e + ); + exit(1); + } + }; - let optimizer = ImgOptimizer::new(&args.dest, args.workers); + let optimizer = ImgOptimizer::builder(&args.dest, args.workers) + .with_progress() + .build(); let file_group = directory.get_file_group(Some(FileImgMimetype::Jpeg), Some(FileSizeRange::Tiny)); - let result = optimizer.optimize(&file_group).await?; + let result = optimizer.optimize(&file_group).await; let (optimized, percent, size) = result.stats(); log::info!( @@ -41,6 +55,4 @@ async fn main() -> Result<(), Box> { percent, size ); - - Ok(()) } diff --git a/src/optimizer.rs b/src/optimizer.rs index 08eb8ae..26c84cb 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -133,17 +133,49 @@ pub struct ImgOptimizer { queue: Arc>, cancel_token: CancellationToken, tracker: TaskTracker, + progress: Arc, } -impl ImgOptimizer { +pub struct ImgOptimizerBuilder(ImgOptimizer); + +impl ImgOptimizerBuilder { pub fn new(dest_dir: &str, nb_workers: usize) -> Self { - ImgOptimizer { + ImgOptimizerBuilder(ImgOptimizer { dest_dir: dest_dir.to_string(), nb_workers, queue: Arc::new(Queue::new()), cancel_token: CancellationToken::new(), tracker: TaskTracker::new(), + progress: Arc::new(ProgressBar::hidden()), + }) + } + + pub fn with_progress(mut self) -> Self { + let pb = Arc::new(ProgressBar::new(0)); + + let tmpl_res = + ProgressStyle::default_bar().template("{msg} [{bar:40}] {pos}/{len} ({eta})"); + + if let Err(e) = tmpl_res { + log::error!("unable to set the progress bar, {}", e); + return self; } + + let style = tmpl_res.unwrap().progress_chars("##-"); + pb.set_style(style); + + self.0.progress = pb; + self + } + + pub fn build(self) -> ImgOptimizer { + self.0 + } +} + +impl ImgOptimizer { + pub fn builder(dest_dir: &str, nb_workers: usize) -> ImgOptimizerBuilder { + ImgOptimizerBuilder::new(dest_dir, nb_workers) } pub async fn stop(&self) { @@ -151,10 +183,7 @@ impl ImgOptimizer { self.cancel_token.cancel(); } - pub async fn optimize( - &self, - file_group: &FileGroup, - ) -> Result> { + pub async fn optimize(&self, file_group: &FileGroup) -> OptimizerResult { let start = std::time::Instant::now(); let results = Arc::new(RwLock::new(FileGroup::new())); @@ -164,19 +193,13 @@ impl ImgOptimizer { self.queue.enqueue(file).await; } - let pb = Arc::new(ProgressBar::new(file_group.len() as u64)); - pb.set_style( - ProgressStyle::default_bar() - .template("{msg} [{bar:40}] {pos}/{len} ({eta})")? - .progress_chars("##-"), - ); - pb.set_message("optimizing..."); + self.progress.set_message("optimizing..."); for _ in 0..self.nb_workers { let queue = Arc::clone(&self.queue); let results = Arc::clone(&results); let optimized = Arc::clone(&optimized); - let pb = Arc::clone(&pb); + let pb = Arc::clone(&self.progress); let cancel_token = self.cancel_token.clone(); let dest_dir = self.dest_dir.clone(); @@ -211,9 +234,8 @@ impl ImgOptimizer { }); } - // TODO(rmanach): move it on main not here + // TODO(rmanach): move it on main not here (overwrite progress bar message) tokio::spawn({ - let pb = Arc::clone(&pb); let optimizer = self.clone(); async move { if let Err(e) = tokio::signal::ctrl_c().await { @@ -222,23 +244,26 @@ impl ImgOptimizer { log::warn!("interrupt signal received"); optimizer.stop().await; - pb.finish_with_message("optimization interrupted"); + optimizer + .progress + .finish_with_message("optimization interrupted"); } }); self.tracker.close(); self.tracker.wait().await; - pb.finish_with_message("optimization complete"); + self.progress.finish_with_message("optimization complete"); + log::info!( "optimization finished in {:.2}s", start.elapsed().as_secs_f64() ); - Ok(OptimizerResult { + OptimizerResult { orig: file_group.clone(), opti: results.read().await.clone(), optimized: optimized.load(Ordering::Relaxed), - }) + } } }