image_reducer/workspace/
mod.rs1use std::{
2 collections::BTreeSet,
3 env::current_exe,
4 fs::{read, write},
5 path::{Path, PathBuf},
6 time::SystemTime,
7};
8
9use async_walkdir::{DirEntry, WalkDir};
10use bytesize::ByteSize;
11use futures::StreamExt;
12use log::LevelFilter;
13
14use colored::Colorize;
15use find_target::find_directory_or_create;
16
17use crate::{
18 utils::{hash_file, logger, optimize_png},
19 TinyResult,
20};
21
22mod config;
23
24pub struct TinyConfig {
25 pub writable: bool,
26 pub database: bool,
27 pub log_level: LevelFilter,
28}
29
30pub struct TinyWorkspace {
31 workspace: PathBuf,
32 writable: bool,
33 database: PathBuf,
34 reduced: u64,
35 start: SystemTime,
36 files: BTreeSet<u64>,
37}
38
39impl TinyWorkspace {
40 pub async fn check_all_pngs(&mut self) -> TinyResult {
41 let mut entries = WalkDir::new(&self.workspace);
42 loop {
43 let path = match entries.next().await {
44 Some(out) => match continue_search(out) {
45 Some(path) => path,
46 None => continue,
47 },
48 None => break,
49 };
50 if let Err(e) = self.optimize_png(&path) {
51 log::error!("{e}")
52 }
53 }
54 let reduced = ByteSize::b(self.reduced);
55 let timing = SystemTime::now().duration_since(self.start)?;
56 log::info!("Reduce {} in {:?}", reduced, timing);
57 Ok(())
58 }
59 pub fn optimize_png(&mut self, path: &Path) -> TinyResult {
60 let bytes = read(path)?;
61 let hash = hash_file(&bytes);
62 if self.files.contains(&hash) {
63 log::info!("Skip Optimized \n{}", path.display());
64 return Ok(());
65 }
66 match optimize_png(&bytes) {
67 Ok(o) => {
68 self.reduced += o.before.0 - o.after.0;
69 let reduce = format!("({:+.2}%)", o.reduce).green();
70 let file = self.relative_path(&path);
71 if self.writable {
72 let overwrite = "(overwrite)".bold();
73 log::info!("{} => {} {reduce}\n{} {overwrite}", o.before, o.after, file.display());
74 write(path, &o.output)?;
75 self.files.insert(hash_file(&o.output));
76 }
77 else {
78 log::info!("{} => {} {reduce}\n{}", o.before, o.after, file.display());
79 }
80 }
81 Err(_) => {
82 if self.writable {
83 self.files.insert(hash_file(&bytes));
84 }
85 }
86 };
87 Ok(())
88 }
89 fn relative_path(&self, target: &Path) -> PathBuf {
90 match pathdiff::diff_paths(target, &self.workspace) {
91 Some(s) => s,
92 None => target.to_path_buf(),
93 }
94 }
95}
96
97fn continue_search(r: Result<DirEntry, std::io::Error>) -> Option<PathBuf> {
98 let path = match r {
99 Ok(o) => o.path(),
100 Err(e) => {
101 log::error!("{e}");
102 return None;
103 }
104 };
105 if !path.is_file() {
106 return None;
107 }
108 let name = path.file_name()?.to_str()?;
109 if name.ends_with(".png") { Some(path) } else { None }
110}