1use crate::{
2 Config, Countable, Dimension, DimensionError, WallSwitchError, WallSwitchResult, exec_cmd,
3};
4use std::{
5 fmt,
6 fs::File,
7 hash::{DefaultHasher, Hasher},
8 io::{BufReader, Read},
9 path::PathBuf,
10 process::Command,
11 thread,
12};
13
14const BUFFER_SIZE: usize = 64 * 1024;
15
16#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
18pub struct FileInfo {
19 pub number: usize,
21 pub total: usize,
23 pub dimension: Dimension,
25 pub size: u64,
27 pub hash: String,
29 pub path: PathBuf,
31}
32
33impl FileInfo {
34 pub fn path_contains(&self, string: &str) -> bool {
40 match self.path.to_str() {
41 Some(p) => p.contains(string),
42 None => false,
43 }
44 }
45
46 pub fn update_info(&mut self, config: &Config) -> WallSwitchResult<()> {
48 let mut cmd = Command::new("identify");
50 let identify_cmd = cmd
51 .arg("-format")
52 .arg("%wx%h") .arg(&self.path);
54
55 let identify_out = exec_cmd(identify_cmd, config.verbose, "identify")?;
56
57 let sdt_output = String::from_utf8(identify_out.stdout)?;
58
59 self.dimension = Dimension::new(&sdt_output)?;
60
61 Ok(())
62 }
63
64 pub fn dimension_is_valid(&self, config: &Config) -> bool {
66 let is_valid = self.dimension.is_valid(config);
67
68 if !is_valid {
69 let dim_error = DimensionError::DimensionFormatError {
71 dimension: self.dimension.clone(),
72 log_min: self.dimension.get_log_min(config),
73 log_max: self.dimension.get_log_max(config),
74 path: self.path.clone(),
75 };
76
77 eprintln!("{}", WallSwitchError::InvalidDimension(dim_error));
78 }
79
80 is_valid
81 }
82
83 pub fn size_is_valid(&self, config: &Config) -> bool {
85 self.size >= config.min_size && self.size <= config.max_size
86 }
87
88 pub fn name_is_valid(&self, config: &Config) -> bool {
89 let is_valid = self.path.file_name() != config.wallpaper.file_name();
90
91 if !is_valid && let Some(path) = self.path.file_name() {
92 eprintln!("{}\n", WallSwitchError::InvalidFilename(path.into()));
93 }
94
95 is_valid
96 }
97}
98
99pub trait FileInfoExt {
101 fn get_width_min(&self) -> Option<u64>;
102 fn get_max_size(&self) -> Option<u64>;
103 fn get_max_number(&self) -> Option<usize>;
104 fn get_max_dimension(&self) -> Option<u64>;
105 fn sizes_are_valid(&self, config: &Config) -> bool;
106 fn update_number(&mut self);
107 fn update_hash(&mut self) -> WallSwitchResult<()>;
108}
109
110impl FileInfoExt for [FileInfo] {
111 fn get_width_min(&self) -> Option<u64> {
112 self.iter().map(|file_info| file_info.dimension.width).min()
113 }
114
115 fn get_max_size(&self) -> Option<u64> {
116 self.iter().map(|file_info| file_info.size).max()
117 }
118
119 fn get_max_number(&self) -> Option<usize> {
120 self.iter().map(|file_info| file_info.number).max()
121 }
122
123 fn get_max_dimension(&self) -> Option<u64> {
124 self.iter()
125 .map(|file_info| file_info.dimension.maximum())
126 .max()
127 }
128
129 fn sizes_are_valid(&self, config: &Config) -> bool {
130 self.iter().all(|file_info| {
131 let is_valid = file_info.size_is_valid(config);
132
133 if !is_valid {
134 let size = file_info.size;
135
136 let min_size = config.min_size;
137 let max_size = config.max_size;
138
139 let path = file_info.path.clone();
140
141 print!("{}", SliceDisplay(self));
143
144 eprintln!(
145 "{}",
146 WallSwitchError::InvalidSize {
147 min_size,
148 size,
149 max_size,
150 }
151 );
152 eprintln!("{}\n", WallSwitchError::DisregardPath(path));
153 }
154
155 is_valid
156 })
157 }
158
159 fn update_number(&mut self) {
161 let total = self.len();
162 self.iter_mut().enumerate().for_each(|(index, file)| {
163 file.number = index + 1;
164 file.total = total;
165 });
166 }
167
168 fn update_hash(&mut self) -> WallSwitchResult<()> {
170 thread::scope(|scope| {
172 for file_info in self {
173 scope.spawn(move || -> WallSwitchResult<()> {
174 let file = File::open(&file_info.path)?;
178 let reader = BufReader::with_capacity(BUFFER_SIZE, file);
179 let hash = get_hash(reader)?;
180
181 file_info.hash = hash;
184
185 Ok(())
186 });
187 }
188 });
189
190 Ok(())
191 }
192}
193
194pub fn get_hash(mut reader: impl Read) -> WallSwitchResult<String> {
200 let mut buffer = [0_u8; BUFFER_SIZE];
201 let mut hasher = DefaultHasher::new();
202
203 loop {
204 let count = reader.read(&mut buffer)?;
206 if count == 0 {
207 break;
208 }
209 hasher.write(&buffer[..count]);
210 }
211
212 Ok(hasher.finish().to_string())
213}
214
215pub struct SliceDisplay<'a>(pub &'a [FileInfo]);
223
224impl fmt::Display for SliceDisplay<'_> {
225 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
226 let digits_n: Option<usize> = self.0.get_max_number().map(|n| n.count_chars());
228 let digits_s: Option<usize> = self.0.get_max_size().map(|s| s.count_chars());
229 let digits_d: Option<usize> = self.0.get_max_dimension().map(|d| d.count_chars());
230
231 match (digits_n, digits_s, digits_d) {
232 (Some(num_digits_number), Some(num_digits_size), Some(num_digits_dimension)) => {
233 for file in self.0 {
234 let dim = format!(
235 "Dimension {{ width: {width:>d$}, height: {height:>d$} }}",
236 width = file.dimension.width,
237 height = file.dimension.height,
238 d = num_digits_dimension,
239 );
240
241 writeln!(
242 f,
243 "images[{number:0n$}/{t}]: {dim}, size: {size:>s$}, path: {p:?}",
244 number = file.number,
245 n = num_digits_number,
246 t = file.total,
247 size = file.size,
248 s = num_digits_size,
249 p = file.path,
250 )?;
251 }
252 }
253 _ => return Err(std::fmt::Error),
254 }
255
256 Ok(())
257 }
258}
259
260#[cfg(test)]
261mod test_info {
262 #[test]
263 fn get_min_value_of_vec_v1() {
265 let values: Vec<i32> = vec![5, 6, 8, 4, 2, 7];
266
267 let min_value: Option<i32> = values.iter().min().copied();
268
269 println!("values: {values:?}");
270 println!("min_value: {min_value:?}");
271
272 assert_eq!(min_value, Some(2));
273 }
274
275 #[test]
276 fn get_min_value_of_vec_v2() {
280 let values: Vec<i32> = vec![5, 6, 8, 4, 2, 7];
281
282 let min_value: i32 = values
287 .iter()
288 .fold(i32::MAX, |arg0: i32, other: &i32| i32::min(arg0, *other));
291
292 println!("values: {values:?}");
293 println!("min_value: {min_value}");
294
295 assert_eq!(min_value, 2);
296 }
297}