use std::path::{Path, PathBuf};
pub struct ImageEntry {
pub path: PathBuf,
pub filename: String,
pub file_size: u64,
}
const SUPPORTED_EXTENSIONS: &[&str] = &[
"png", "jpg", "jpeg", "webp", "gif", "bmp", "tiff", "tif", "ico",
];
pub fn is_supported_image(path: &Path) -> bool {
path.extension()
.and_then(|ext| ext.to_str())
.map(|ext| SUPPORTED_EXTENSIONS.contains(&ext.to_lowercase().as_str()))
.unwrap_or(false)
}
pub fn scan_directory(dir: &Path) -> anyhow::Result<Vec<ImageEntry>> {
let mut entries: Vec<ImageEntry> = std::fs::read_dir(dir)?
.filter_map(|res| res.ok())
.map(|e| e.path())
.filter(|p| p.is_file() && is_supported_image(p))
.map(|path| {
let filename = path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
let file_size = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
ImageEntry { path, filename, file_size }
})
.collect();
entries.sort_by(|a, b| a.filename.cmp(&b.filename));
Ok(entries)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
fn create_fake_png(dir: &Path, name: &str) {
let img = image::RgbImage::from_fn(1, 1, |_, _| image::Rgb([255, 0, 0]));
img.save(dir.join(name)).unwrap();
}
#[test]
fn test_is_supported_image_png() {
assert!(is_supported_image(Path::new("photo.png")));
}
#[test]
fn test_is_supported_image_jpg() {
assert!(is_supported_image(Path::new("photo.jpg")));
assert!(is_supported_image(Path::new("photo.jpeg")));
}
#[test]
fn test_is_supported_image_webp() {
assert!(is_supported_image(Path::new("photo.webp")));
}
#[test]
fn test_is_supported_image_gif() {
assert!(is_supported_image(Path::new("anim.gif")));
}
#[test]
fn test_is_supported_image_bmp() {
assert!(is_supported_image(Path::new("photo.bmp")));
}
#[test]
fn test_is_supported_image_tiff() {
assert!(is_supported_image(Path::new("photo.tiff")));
}
#[test]
fn test_is_supported_image_ico() {
assert!(is_supported_image(Path::new("icon.ico")));
}
#[test]
fn test_is_supported_image_rejects_txt() {
assert!(!is_supported_image(Path::new("readme.txt")));
}
#[test]
fn test_is_supported_image_case_insensitive() {
assert!(is_supported_image(Path::new("photo.PNG")));
assert!(is_supported_image(Path::new("photo.JPG")));
}
#[test]
fn test_scan_directory_returns_sorted_entries() {
let dir = tempdir().unwrap();
create_fake_png(dir.path(), "zebra.png");
create_fake_png(dir.path(), "apple.png");
create_fake_png(dir.path(), "mango.png");
let entries = scan_directory(dir.path()).unwrap();
let names: Vec<&str> = entries.iter().map(|e| e.filename.as_str()).collect();
assert_eq!(names, vec!["apple.png", "mango.png", "zebra.png"]);
}
#[test]
fn test_scan_directory_filters_non_images() {
let dir = tempdir().unwrap();
create_fake_png(dir.path(), "photo.png");
fs::write(dir.path().join("readme.txt"), b"hello").unwrap();
fs::write(dir.path().join("script.sh"), b"#!/bin/sh").unwrap();
let entries = scan_directory(dir.path()).unwrap();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].filename, "photo.png");
}
#[test]
fn test_scan_directory_not_recursive() {
let dir = tempdir().unwrap();
create_fake_png(dir.path(), "top.png");
let subdir = dir.path().join("subdir");
fs::create_dir(&subdir).unwrap();
create_fake_png(&subdir, "nested.png");
let entries = scan_directory(dir.path()).unwrap();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].filename, "top.png");
}
#[test]
fn test_scan_empty_directory() {
let dir = tempdir().unwrap();
let entries = scan_directory(dir.path()).unwrap();
assert!(entries.is_empty());
}
}