132 lines
3.3 KiB
Rust
132 lines
3.3 KiB
Rust
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<bool> {
|
||
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<EntryStream> {
|
||
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<DirEntry>) -> 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
|
||
}
|