use std::path::{Path, PathBuf}; use chrono::{DateTime, Utc}; use walkdir::WalkDir; use crate::filter::FilterConfig; use crate::FileEntry; pub struct FileScanner { pub root: PathBuf, pub filters: FilterConfig, } impl FileScanner { pub fn new(root: PathBuf, filters: FilterConfig) -> Self { Self { root, filters } } pub fn scan(&self) -> Vec { let max_depth = self.filters.subfolder_depth.map(|d| d + 1).unwrap_or(usize::MAX); let walker = WalkDir::new(&self.root) .max_depth(max_depth) .follow_links(false); let mut entries: Vec = Vec::new(); for result in walker { let dir_entry = match result { Ok(e) => e, Err(_) => continue, }; // skip the root directory itself if dir_entry.path() == self.root { continue; } let metadata = match dir_entry.metadata() { Ok(m) => m, Err(_) => continue, }; let path = dir_entry.path().to_path_buf(); let name = dir_entry.file_name().to_string_lossy().to_string(); let is_dir = metadata.is_dir(); let is_hidden = is_hidden_file(&path); let (stem, extension) = if is_dir { (name.clone(), String::new()) } else { split_filename(&name) }; let created = file_created(&metadata); let modified = file_modified(&metadata); let accessed = file_accessed(&metadata); let entry = FileEntry { path, name, stem, extension, size: metadata.len(), is_dir, is_hidden, created, modified, accessed, }; if self.filters.matches(&entry) { entries.push(entry); } } // natural sort entries.sort_by(|a, b| natord::compare(&a.name, &b.name)); entries } } fn split_filename(name: &str) -> (String, String) { match name.rsplit_once('.') { Some((stem, ext)) if !stem.is_empty() => (stem.to_string(), ext.to_string()), _ => (name.to_string(), String::new()), } } fn is_hidden_file(path: &Path) -> bool { if path.file_name() .map(|n| n.to_string_lossy().starts_with('.')) .unwrap_or(false) { return true; } #[cfg(target_os = "windows")] { use std::os::windows::fs::MetadataExt; if let Ok(meta) = std::fs::metadata(path) { const FILE_ATTRIBUTE_HIDDEN: u32 = 0x2; if meta.file_attributes() & FILE_ATTRIBUTE_HIDDEN != 0 { return true; } } } false } fn file_created(meta: &std::fs::Metadata) -> Option> { meta.created().ok().map(|t| DateTime::::from(t)) } fn file_modified(meta: &std::fs::Metadata) -> Option> { meta.modified().ok().map(|t| DateTime::::from(t)) } fn file_accessed(meta: &std::fs::Metadata) -> Option> { meta.accessed().ok().map(|t| DateTime::::from(t)) }