use std::{ ffi::OsStr, fs::{self, DirEntry, ReadDir}, io, path::Path, }; use crate::{logger::log_error, mac_meta}; pub struct Walker; impl Walker { pub fn new() -> Self { Self } pub fn prune(&self, path: &Path) -> io::Result { let entries = match self.read_entries(path)? { EntryStream::Entries(entries) => entries, EntryStream::Skip => return Ok(false), }; let mut is_empty = true; for entry_result in entries { if !self.process_entry(path, entry_result) { is_empty = false; } } Ok(is_empty) } fn read_entries(&self, path: &Path) -> io::Result { match fs::read_dir(path) { Ok(entries) => Ok(EntryStream::Entries(entries)), Err(err) => { if err.kind() == io::ErrorKind::PermissionDenied { eprintln!("无法读取 `{}`:{}", path.display(), err); Ok(EntryStream::Skip) } else { Err(err) } } } } fn process_entry(&self, parent: &Path, entry_result: io::Result) -> bool { let entry = match entry_result { Ok(entry) => entry, Err(err) => { log_error(parent, &err); return false; } }; let child_path = entry.path(); let file_type = match entry.file_type() { Ok(file_type) => file_type, Err(err) => { log_error(&child_path, &err); return false; } }; if file_type.is_dir() { return self.handle_directory(&child_path); } if file_type.is_file() || file_type.is_symlink() { let name = entry.file_name(); return self.handle_metadata_file(&name, &child_path); } false } fn handle_directory(&self, child_path: &Path) -> bool { match self.prune(child_path) { Ok(true) => match fs::remove_dir(child_path) { Ok(_) => { println!("删除空目录 `{}`", child_path.display()); true } Err(err) => { log_error(child_path, &err); false } }, Ok(false) => false, Err(err) => { log_error(child_path, &err); false } } } fn handle_metadata_file(&self, name: &OsStr, path: &Path) -> bool { if !should_remove_file(name, path) { return false; } println!("删除元数据文件 `{}`", path.display()); match fs::remove_file(path) { Ok(_) => true, Err(err) => { log_error(path, &err); false } } } } enum EntryStream { Entries(ReadDir), Skip, } fn should_remove_file(name: &OsStr, path: &Path) -> bool { if name == OsStr::new(".DS_Store") { return true; } if let Some(name_str) = name.to_str() { if name_str.starts_with("._") { return mac_meta::is_mac_meta_file(path); } } false }