use crate::{
Config, Countable, Dimension, DimensionError, WallSwitchError, WallSwitchResult,
compute_hashes_parallel, probe_image_dimension,
};
use std::{fmt, path::PathBuf};
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
pub struct FileInfo {
pub number: usize,
pub total: usize,
pub dimension: Option<Dimension>,
pub is_valid: Option<bool>,
pub size: u64,
pub mtime: u64,
pub hash: String,
pub path: PathBuf,
}
impl FileInfo {
pub fn path_contains(&self, string: &str) -> bool {
match self.path.to_str() {
Some(p) => p.contains(string),
None => false,
}
}
pub fn check_dimension(&self, config: &Config) -> Option<Result<(), DimensionError>> {
let dim = self.dimension.as_ref()?;
if dim.is_valid(config) {
Some(Ok(()))
} else {
Some(Err(DimensionError::DimensionFormatError {
dimension: dim.clone(),
log_min: dim.get_log_min(config),
log_max: dim.get_log_max(config),
path: self.path.clone(),
}))
}
}
pub fn check_name(&self, config: &Config) -> Result<(), WallSwitchError> {
if self.path.file_name() != config.wallpaper.file_name() {
Ok(())
} else {
Err(WallSwitchError::InvalidFilename(self.path.clone()))
}
}
pub fn check_size(&self, config: &Config) -> Result<(), WallSwitchError> {
if self.size >= config.min_size && self.size <= config.max_size {
Ok(())
} else {
Err(WallSwitchError::InvalidSize {
min_size: config.min_size,
size: self.size,
max_size: config.max_size,
})
}
}
pub fn update_info(&mut self, config: &Config) -> WallSwitchResult<()> {
self.dimension = Some(probe_image_dimension(&self.path, config.verbose)?);
Ok(())
}
pub fn dimension_is_valid(&self, config: &Config) -> bool {
match self.check_dimension(config) {
Some(Ok(())) => true,
Some(Err(err)) => {
if config.verbose {
eprintln!("{}", WallSwitchError::InvalidDimension(err));
}
false
}
None => false, }
}
pub fn name_is_valid(&self, config: &Config) -> bool {
match self.check_name(config) {
Ok(()) => true,
Err(err) => {
if config.verbose {
eprintln!("{}\n", err);
}
false
}
}
}
pub fn size_is_valid(&self, config: &Config) -> bool {
match self.check_size(config) {
Ok(()) => true,
Err(err) => {
if config.verbose {
println!("{}", SliceDisplay(std::slice::from_ref(self)));
eprintln!("{}", err);
eprintln!("{}\n", WallSwitchError::DisregardPath(self.path.clone()));
}
false
}
}
}
}
pub trait FileInfoExt {
fn get_width_min(&self) -> Option<u64>;
fn get_max_size(&self) -> Option<u64>;
fn get_max_number(&self) -> Option<usize>;
fn get_max_dimension(&self) -> Option<u64>;
fn sizes_are_valid(&self, config: &Config) -> bool;
fn update_number(&mut self);
fn update_hash(&mut self) -> WallSwitchResult<()>;
}
impl FileInfoExt for [FileInfo] {
fn get_width_min(&self) -> Option<u64> {
self.iter()
.filter_map(|f| f.dimension.as_ref().map(|d| d.width))
.min()
}
fn get_max_size(&self) -> Option<u64> {
self.iter().map(|file_info| file_info.size).max()
}
fn get_max_number(&self) -> Option<usize> {
self.iter().map(|file_info| file_info.number).max()
}
fn get_max_dimension(&self) -> Option<u64> {
self.iter()
.filter_map(|f| f.dimension.as_ref().map(|d| d.maximum()))
.max()
}
fn update_number(&mut self) {
let total = self.len();
self.iter_mut().enumerate().for_each(|(index, file)| {
file.number = index + 1;
file.total = total;
});
}
fn sizes_are_valid(&self, config: &Config) -> bool {
self.iter().all(|file_info| {
let is_valid = file_info.size_is_valid(config);
if !is_valid {
print!("{}", SliceDisplay(self));
}
is_valid
})
}
fn update_hash(&mut self) -> WallSwitchResult<()> {
compute_hashes_parallel(self);
Ok(())
}
}
pub struct SliceDisplay<'a>(pub &'a [FileInfo]);
impl fmt::Display for SliceDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let digits_n: Option<usize> = self.0.get_max_number().map(|n| n.count_chars());
let digits_s: Option<usize> = self.0.get_max_size().map(|s| s.count_chars());
let digits_d: Option<usize> = self.0.get_max_dimension().map(|d| d.count_chars());
match (digits_n, digits_s, digits_d) {
(Some(num_digits_number), Some(num_digits_size), num_digits_dimension) => {
let d_padding = num_digits_dimension.unwrap_or(4);
for file in self.0 {
let dim_str = match &file.dimension {
Some(dim) => format!(
"Dimension {{ width: {width:>d$}, height: {height:>d$} }}",
width = dim.width,
height = dim.height,
d = d_padding,
),
None => format!(
"Dimension {{ {:>width$} }}",
"Pending probe",
width = d_padding * 2 + 13
),
};
writeln!(
f,
"images[{number:0n$}/{t}]: {dim_str}, size: {size:>s$}, path: {p:?}",
number = file.number,
n = num_digits_number,
t = file.total,
size = file.size,
s = num_digits_size,
p = file.path,
)?;
}
}
_ => return Err(std::fmt::Error),
}
Ok(())
}
}
#[cfg(test)]
mod test_info {
#[test]
fn get_min_value_of_vec_v1() {
let values: Vec<i32> = vec![5, 6, 8, 4, 2, 7];
let min_value: Option<i32> = values.iter().min().copied();
println!("values: {values:?}");
println!("min_value: {min_value:?}");
assert_eq!(min_value, Some(2));
}
#[test]
fn get_min_value_of_vec_v2() {
let values: Vec<i32> = vec![5, 6, 8, 4, 2, 7];
let min_value: i32 = values
.iter()
.fold(i32::MAX, |arg0: i32, other: &i32| i32::min(arg0, *other));
println!("values: {values:?}");
println!("min_value: {min_value}");
assert_eq!(min_value, 2);
}
}