use crate::{
Colors, Config, DigitWidth, Dimension, DimensionError, WallSwitchResult,
compute_hashes_parallel, probe_image_dimension,
};
use std::{fmt, path::PathBuf};
#[derive(Debug)]
pub enum FileValidationError {
InvalidName {
path: PathBuf,
},
InvalidSize {
min_size: u64,
actual_size: u64,
max_size: u64,
},
InvalidDimension(DimensionError),
}
impl fmt::Display for FileValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidName { path } => {
write!(
f,
"{}: Invalid file name --> Disregard the path: '{}'.",
"Error".red().bold(),
path.display().to_string().yellow()
)
}
Self::InvalidSize {
min_size,
actual_size,
max_size,
} => {
write!(
f,
"{}: invalid file size '{}' bytes. The condition ({} <= {} <= {}) is false.",
"Error".red().bold(),
actual_size.to_string().yellow(),
min_size.to_string().green(),
actual_size.to_string().yellow(),
max_size.to_string().green()
)
}
Self::InvalidDimension(err) => write!(f, "{err}"),
}
}
}
#[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 {
self.path.to_str().is_some_and(|p| p.contains(string))
}
pub fn validate(&self, config: &Config) -> Result<(), FileValidationError> {
self.check_name(config)?;
self.check_size(config)?;
self.check_dimension(config)?;
Ok(())
}
pub fn check_name(&self, config: &Config) -> Result<(), FileValidationError> {
if self.path.file_name() != config.wallpaper.file_name() {
Ok(())
} else {
Err(FileValidationError::InvalidName {
path: self.path.clone(),
})
}
}
pub fn check_size(&self, config: &Config) -> Result<(), FileValidationError> {
if self.size >= config.min_size && self.size <= config.max_size {
Ok(())
} else {
Err(FileValidationError::InvalidSize {
min_size: config.min_size,
actual_size: self.size,
max_size: config.max_size,
})
}
}
pub fn check_dimension(&self, config: &Config) -> Result<(), FileValidationError> {
let dim = match &self.dimension {
Some(d) => d,
None => return Ok(()),
};
if dim.is_valid(config) {
Ok(())
} else {
Err(FileValidationError::InvalidDimension(
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 update_info(&mut self, config: &Config) -> WallSwitchResult<()> {
self.dimension = Some(probe_image_dimension(&self.path, config.verbose)?);
Ok(())
}
}
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 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(|f| f.size).max()
}
fn get_max_number(&self) -> Option<usize> {
self.iter().map(|f| f.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 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 = self.0.get_max_number().map(|n| n.digit_width());
let digits_s = self.0.get_max_size().map(|s| s.digit_width());
let digits_d = self.0.get_max_dimension().map(|d| d.digit_width());
if let (Some(num_digits_number), Some(num_digits_size)) = (digits_n, digits_s) {
let d_padding = digits_d.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,
)?;
}
} else {
return Err(std::fmt::Error);
}
Ok(())
}
}