use super::*;
pub fn count_entries(items: &[(PathBuf, bool)]) -> u64 {
fn walk(path: &Path) -> u64 {
let Ok(entries) = read_dir(path) else {
return 1;
};
1 + entries
.flatten()
.map(|entry| match entry.file_type() {
Ok(file_type) if file_type.is_dir() => walk(&entry.path()),
_ => 1,
})
.sum::<u64>()
}
items
.iter()
.map(|(path, is_dir)| {
let link = fs::symlink_metadata(path).map(|meta| meta.file_type().is_symlink()).unwrap_or(false);
if *is_dir && !link { walk(path) } else { 1 }
})
.sum()
}
pub fn delete_path(path: PathBuf, is_dir: bool, report: Report<'_>) -> Result<Transfer, Error> {
let is_symlink = path.symlink_metadata().map(|metadata| metadata.file_type().is_symlink()).unwrap_or(false);
let outcome = if is_dir && !is_symlink { delete_dir_recursive(&path, report)? } else { remove_one(&path, false, report)? };
Ok(outcome.into())
}
pub fn trash_path(path: PathBuf, is_dir: bool, report: Report<'_>) -> Result<Transfer, Error> {
if !report.step(Step::Starting(&path)) {
return Ok(Transfer::Cancelled);
}
loop {
let error = match trash::delete(&path) {
Ok(()) => break,
Err(error) => trash_error(error),
};
match report.failed(&path, &error) {
Reply::Retry => {}
Reply::Skip => break,
Reply::Abort => return Err(error),
Reply::Delete => {
let mut uncounted = Uncounted(&mut *report);
if delete_path(path, is_dir, &mut uncounted)? == Transfer::Cancelled {
return Ok(Transfer::Cancelled);
}
break;
}
}
}
Ok(if report.step(Step::Advanced(1)) { Transfer::Done } else { Transfer::Cancelled })
}
fn trash_error(error: trash::Error) -> Error {
match error {
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
trash::Error::FileSystem { source, .. } => source,
trash::Error::TargetedRoot => Error::other(t!("A root directory cannot go to the trash")),
trash::Error::CouldNotAccess { target } => Error::other(t!("Cannot reach {target}", target)),
trash::Error::Os { description, .. } | trash::Error::Unknown { description } => Error::other(description),
other => Error::other(t!("Cannot move to the trash: {why}", why = format!("{other:?}"))),
}
}
pub(super) struct Uncounted<'a, 'b>(pub(super) &'a mut (dyn Progress + 'b));
impl Progress for Uncounted<'_, '_> {
fn step(&mut self, step: Step<'_>) -> bool {
match step {
Step::Advanced(_) => self.0.step(Step::Advanced(0)),
starting => self.0.step(starting),
}
}
fn failed(&mut self, path: &Path, error: &Error) -> Reply {
self.0.failed(path, error)
}
fn exists(&mut self, source: &Path, dest: &Path) -> Clash {
self.0.exists(source, dest)
}
}
fn remove_one(path: &Path, is_dir: bool, report: Report<'_>) -> Result<Outcome, Error> {
if !report.step(Step::Starting(path)) {
return Ok(Outcome::Cancelled);
}
let removed = attempt(path, report, |_| if is_dir { remove_dir(path) } else { remove_entry(path) })?;
report.step(Step::Advanced(1));
Ok(if removed.is_some() { Outcome::Done } else { Outcome::Skipped })
}
#[cfg(windows)]
fn remove_entry(path: &Path) -> Result<(), Error> {
use std::os::windows::fs::FileTypeExt;
if path.symlink_metadata().is_ok_and(|metadata| metadata.file_type().is_symlink_dir()) {
remove_dir(path)
} else {
remove_file(path)
}
}
#[cfg(not(windows))]
fn remove_entry(path: &Path) -> Result<(), Error> {
remove_file(path)
}
fn delete_dir_recursive(path: &Path, report: Report<'_>) -> Result<Outcome, Error> {
let Some(entries) = attempt(path, report, |_| read_dir(path))? else {
return Ok(Outcome::Skipped);
};
let mut skipped = false;
for entry in entries {
let Some((entry, file_type)) = next_entry(path, entry, report)? else {
skipped = true;
continue;
};
let entry_path = entry.path();
let outcome = if file_type.is_dir() { delete_dir_recursive(&entry_path, &mut *report)? } else { remove_one(&entry_path, false, &mut *report)? };
match outcome {
Outcome::Cancelled => return Ok(Outcome::Cancelled),
Outcome::Skipped => skipped = true,
Outcome::Done => {}
}
}
if skipped {
return Ok(Outcome::Skipped);
}
remove_one(path, true, report)
}