use std::collections::BTreeSet;
use std::path::Path;
use crate::pre::*;
#[inline(always)]
pub fn is_empty_dir(path: impl AsRef<Path>) -> crate::Result<bool> {
is_empty_dir_impl(path.as_ref())
}
fn is_empty_dir_impl(path: &Path) -> crate::Result<bool> {
crate::trace!("is_empty_dir '{}'", path.display());
let mut x = read_dir_impl(path)?;
Ok(x.next().is_none())
}
#[inline(always)]
pub fn make_dir(path: impl AsRef<Path>) -> crate::Result<()> {
make_dir_impl(path.as_ref())
}
fn make_dir_impl(path: &Path) -> crate::Result<()> {
crate::trace!("make_dir '{}'", path.display());
let (exists, is_dir) = match std::fs::metadata(path) {
Ok(m) => (true, m.is_dir()),
Err(_) => (false, false),
};
match (exists, is_dir) {
(true, false) => {
crate::bail!(
"failed to create directory: '{}' exists but is not a directory",
path.display()
);
}
(true, true) => {} (false, _) => {
crate::trace!("make_dir: creating '{}'", path.display());
crate::check!(
std::fs::create_dir_all(path),
"failed to create directory '{}'",
path.display()
)?;
}
}
Ok(())
}
#[cfg(feature = "coroutine")]
#[inline(always)]
pub async fn co_make_dir(path: impl AsRef<Path>) -> crate::Result<()> {
co_make_dir_impl(path.as_ref()).await
}
#[cfg(feature = "coroutine")]
async fn co_make_dir_impl(path: &Path) -> crate::Result<()> {
crate::trace!("co_make_dir '{}'", path.display());
let (exists, is_dir) = match std::fs::metadata(path) {
Ok(m) => (true, m.is_dir()),
Err(_) => (false, false),
};
match (exists, is_dir) {
(true, false) => {
crate::bail!(
"failed to create directory: '{}' exists but is not a directory",
path.display()
);
}
(true, true) => {} (false, _) => {
crate::trace!("co_make_dir: creating '{}'", path.display());
crate::check!(
tokio::fs::create_dir_all(path).await,
"failed to create directory '{}'",
path.display()
)?;
}
}
Ok(())
}
pub fn make_dir_empty(path: impl AsRef<Path>) -> crate::Result<()> {
let path = path.as_ref();
make_dir_impl(path)?;
remove_contents_impl(path)
}
#[cfg(feature = "coroutine")]
pub async fn co_make_dir_empty(path: impl AsRef<Path>) -> crate::Result<()> {
let path = path.as_ref();
co_make_dir_impl(path).await?;
co_remove_contents_impl(path).await
}
pub fn make_dir_absent_or_empty(path: impl AsRef<Path>) -> crate::Result<()> {
let path = path.as_ref();
if !path.exists() {
return Ok(());
}
remove_contents(path)
}
#[cfg(feature = "coroutine")]
pub async fn co_make_dir_absent_or_empty(path: impl AsRef<Path>) -> crate::Result<()> {
let path = path.as_ref();
if !path.exists() {
return Ok(());
}
co_remove_contents(path).await
}
#[inline(always)]
pub fn rec_remove(path: impl AsRef<Path>) -> crate::Result<()> {
rec_remove_impl(path.as_ref())
}
fn rec_remove_impl(path: &Path) -> crate::Result<()> {
if !path.exists() {
crate::trace!("rec_remove: is absent: '{}'", path.display());
return Ok(());
}
crate::trace!("rec_remove '{}'", path.display());
if !path.is_dir() {
crate::bail!("'{}' is not a directory", path.display());
}
crate::check!(
std::fs::remove_dir_all(path),
"failed to recursively remove '{}'",
path.display()
)
}
#[cfg(feature = "coroutine")]
#[inline(always)]
pub async fn co_rec_remove(path: impl AsRef<Path>) -> crate::Result<()> {
co_rec_remove_impl(path.as_ref()).await
}
#[cfg(feature = "coroutine")]
async fn co_rec_remove_impl(path: &Path) -> crate::Result<()> {
if !path.exists() {
crate::trace!("co_rec_remove: is absent: '{}'", path.display());
return Ok(());
}
crate::trace!("co_rec_remove '{}'", path.display());
if !path.is_dir() {
crate::bail!("'{}' is not a directory", path.display());
}
crate::check!(
tokio::fs::remove_dir_all(path).await,
"failed to recursively remove '{}'",
path.display()
)
}
#[inline(always)]
pub fn remove_contents(path: impl AsRef<Path>) -> crate::Result<()> {
remove_contents_impl(path.as_ref())
}
fn remove_contents_impl(path: &Path) -> crate::Result<()> {
crate::trace!("remove_contents '{}'", path.display());
if !path.is_dir() {
if !path.exists() {
crate::bail!("'{}' does not exist", path.display());
}
crate::bail!("'{}' is not a directory", path.display());
}
for entry in read_dir(path)? {
let entry = crate::check!(entry, "failed to read entry inside '{}'", path.display())?;
let entry_path = entry.path();
let file_type = crate::check!(
entry.file_type(),
"failed to read entry type for '{}'",
entry_path.display()
)?;
if file_type.is_dir() {
rec_remove(entry_path)?;
} else {
crate::fs::remove(entry_path)?;
}
}
Ok(())
}
#[cfg(feature = "coroutine")]
#[inline(always)]
pub async fn co_remove_contents(path: impl AsRef<Path>) -> crate::Result<()> {
co_remove_contents_impl(path.as_ref()).await
}
#[cfg(feature = "coroutine")]
async fn co_remove_contents_impl(path: &Path) -> crate::Result<()> {
crate::trace!("remove_contents '{}'", path.display());
if !path.is_dir() {
if !path.exists() {
crate::bail!("'{}' does not exist", path.display());
}
crate::bail!("'{}' is not a directory", path.display());
}
let mut x = co_read_dir(path).await?;
let mut join_set = tokio::task::JoinSet::new();
loop {
let entry = x.next_entry().await;
let entry = crate::check!(entry, "failed to read entry inside '{}'", path.display())?;
let Some(entry) = entry else {
break;
};
let entry_path = entry.path();
let file_type = crate::check!(
entry.file_type().await,
"failed to read entry type for '{}'",
entry_path.display()
)?;
if file_type.is_dir() {
join_set.spawn(async move { co_rec_remove(entry_path).await });
} else {
join_set.spawn(async move { crate::fs::co_remove(entry_path).await });
}
}
let mut has_failure = false;
while let Some(x) = join_set.join_next().await {
match x {
Err(e) => {
crate::debug!("failed to remove some entry in '{}': {e}", path.display());
has_failure = true;
}
Ok(Err(e)) => {
crate::debug!("failed to remove some entry in '{}': {e}", path.display());
has_failure = true;
}
Ok(Ok(())) => {}
}
}
if has_failure {
crate::bail!(
"failed to remove one or more entries in '{}'",
path.display()
);
}
Ok(())
}
pub type ReadDir = std::fs::ReadDir;
#[cfg(feature = "coroutine")]
pub type CoReadDir = tokio::fs::ReadDir;
pub type DirEntry = std::fs::DirEntry;
#[cfg(feature = "coroutine")]
pub type CoDirEntry = tokio::fs::DirEntry;
#[inline(always)]
pub fn read_dir(path: impl AsRef<Path>) -> crate::Result<ReadDir> {
read_dir_impl(path.as_ref())
}
fn read_dir_impl(path: &Path) -> crate::Result<ReadDir> {
crate::check!(
std::fs::read_dir(path),
"cannot read directory '{}'",
path.display()
)
}
#[cfg(feature = "coroutine")]
#[inline(always)]
pub async fn co_read_dir(path: impl AsRef<Path>) -> crate::Result<CoReadDir> {
co_read_dir_impl(path.as_ref()).await
}
#[cfg(feature = "coroutine")]
async fn co_read_dir_impl(path: &Path) -> crate::Result<CoReadDir> {
crate::check!(
tokio::fs::read_dir(path).await,
"cannot read directory '{}'",
path.display()
)
}
#[inline(always)]
pub fn rec_copy_inefficiently(from: impl AsRef<Path>, to: impl AsRef<Path>) -> crate::Result<()> {
rec_copy_inefficiently_impl(from.as_ref(), to.as_ref())
}
#[crate::context("failed to copy recursively copy '{}' to '{}'", from.display(), to.display())]
fn rec_copy_inefficiently_impl(from: &Path, to: &Path) -> crate::Result<()> {
crate::trace!(
"rec_copy_inefficiently from='{}' to='{}'",
from.display(),
to.display()
);
if !from.is_dir() {
crate::bail!("source does not exist or is not a directory");
}
let to_meta = to.metadata();
if to_meta.is_ok_and(|x| !x.is_dir()) {
crate::bail!("target exists and is not a directory");
}
cu::check!(
make_dir_impl(to),
"rec_copy: failed to create target directory"
)?;
let mut dir_cache = BTreeSet::new();
let mut walker = crate::fs::walker(from);
walker.include_dir_entries(true);
for entry in walker.walk()? {
let entry = entry?;
let rel_path = entry.rel_path()?;
if !entry.is_dir() {
let Some(file_name) = entry.file_name() else {
cu::trace!(
"skipping file with no file name: '{}'",
entry.path().display()
);
continue;
};
let containing = if entry.depth() > 1 {
let parent = cu::check!(
rel_path.parent(),
"failed to get relative path of containing directory for entry"
)?;
to.join(parent)
} else {
to.to_path_buf()
};
let target = containing.join(file_name);
if !dir_cache.contains(&containing) {
make_dir_impl(&containing)?;
dir_cache.insert(containing);
}
crate::fs::copy(entry.path(), target)?;
} else {
let target_path = to.join(rel_path);
make_dir_impl(&target_path)?;
dir_cache.insert(target_path);
}
}
Ok(())
}