use std::io;
use std::path::Path;
use crate::CleanReporter;
pub fn rm_rf(path: impl AsRef<Path>) -> io::Result<Removal> {
Remover::default().rm_rf(path, false)
}
#[derive(Default)]
pub(crate) struct Remover {
reporter: Option<Box<dyn CleanReporter>>,
}
impl Remover {
pub(crate) fn new(reporter: Box<dyn CleanReporter>) -> Self {
Self {
reporter: Some(reporter),
}
}
pub(crate) fn rm_rf(
&self,
path: impl AsRef<Path>,
skip_locked_file: bool,
) -> io::Result<Removal> {
let mut removal = Removal::default();
removal.rm_rf(path.as_ref(), self.reporter.as_deref(), skip_locked_file)?;
Ok(removal)
}
}
#[derive(Debug, Default)]
pub struct Removal {
pub num_files: u64,
pub num_dirs: u64,
pub total_bytes: u64,
}
impl Removal {
fn rm_rf(
&mut self,
path: &Path,
reporter: Option<&dyn CleanReporter>,
skip_locked_file: bool,
) -> io::Result<()> {
let metadata = match fs_err::symlink_metadata(path) {
Ok(metadata) => metadata,
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(()),
Err(err) => return Err(err),
};
if !metadata.is_dir() {
self.num_files += 1;
self.total_bytes += metadata.len();
if metadata.is_symlink() {
#[cfg(windows)]
{
use std::os::windows::fs::FileTypeExt;
if metadata.file_type().is_symlink_dir() {
remove_dir(path)?;
} else {
remove_file(path)?;
}
}
#[cfg(not(windows))]
{
remove_file(path)?;
}
} else {
remove_file(path)?;
}
reporter.map(CleanReporter::on_clean);
return Ok(());
}
for entry in walkdir::WalkDir::new(path).contents_first(true) {
if let Err(ref err) = entry {
if err
.io_error()
.is_some_and(|err| err.kind() == io::ErrorKind::PermissionDenied)
{
if let Some(dir) = err.path() {
if set_readable(dir).unwrap_or(false) {
return self.rm_rf(path, reporter, skip_locked_file);
}
}
}
}
let entry = entry?;
if skip_locked_file
&& entry.file_name() == ".lock"
&& entry
.path()
.strip_prefix(path)
.is_ok_and(|suffix| suffix == Path::new(".lock"))
{
continue;
}
if entry.file_type().is_symlink() && {
#[cfg(windows)]
{
use std::os::windows::fs::FileTypeExt;
entry.file_type().is_symlink_dir()
}
#[cfg(not(windows))]
{
false
}
} {
self.num_files += 1;
remove_dir(entry.path())?;
} else if entry.file_type().is_dir() {
if skip_locked_file && entry.path() == path {
continue;
}
self.num_dirs += 1;
remove_dir_all(entry.path())?;
} else {
self.num_files += 1;
if let Ok(meta) = entry.metadata() {
self.total_bytes += meta.len();
}
remove_file(entry.path())?;
}
reporter.map(CleanReporter::on_clean);
}
reporter.map(CleanReporter::on_complete);
Ok(())
}
}
impl std::ops::AddAssign for Removal {
fn add_assign(&mut self, other: Self) {
self.num_files += other.num_files;
self.num_dirs += other.num_dirs;
self.total_bytes += other.total_bytes;
}
}
#[cfg_attr(windows, allow(unused_variables, clippy::unnecessary_wraps))]
fn set_readable(path: &Path) -> io::Result<bool> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs_err::metadata(path)?.permissions();
if perms.mode() & 0o500 == 0 {
perms.set_mode(perms.mode() | 0o500);
fs_err::set_permissions(path, perms)?;
return Ok(true);
}
}
Ok(false)
}
fn set_not_readonly(path: &Path) -> io::Result<bool> {
let mut perms = fs_err::metadata(path)?.permissions();
if !perms.readonly() {
return Ok(false);
}
#[allow(clippy::permissions_set_readonly_false)]
perms.set_readonly(false);
fs_err::set_permissions(path, perms)?;
Ok(true)
}
fn remove_file(path: &Path) -> io::Result<()> {
match fs_err::remove_file(path) {
Ok(()) => Ok(()),
Err(err)
if err.kind() == io::ErrorKind::PermissionDenied
&& set_not_readonly(path).unwrap_or(false) =>
{
fs_err::remove_file(path)
}
Err(err) => Err(err),
}
}
fn remove_dir(path: &Path) -> io::Result<()> {
match fs_err::remove_dir(path) {
Ok(()) => Ok(()),
Err(err)
if err.kind() == io::ErrorKind::PermissionDenied
&& set_readable(path).unwrap_or(false) =>
{
fs_err::remove_dir(path)
}
Err(err) => Err(err),
}
}
fn remove_dir_all(path: &Path) -> io::Result<()> {
match fs_err::remove_dir_all(path) {
Ok(()) => Ok(()),
Err(err)
if err.kind() == io::ErrorKind::PermissionDenied
&& set_readable(path).unwrap_or(false) =>
{
fs_err::remove_dir_all(path)
}
Err(err) => Err(err),
}
}