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");
}
let mut dir_cache = BTreeSet::new();
let mut walk = crate::fs::walk(from)?;
while let Some(entry) = walk.next() {
let entry = entry?;
if !entry.is_dir() {
if !dir_cache.contains(entry.rel_containing) {
let dir_path = to.join(entry.rel_containing);
make_dir_impl(&dir_path)?;
dir_cache.insert(dir_path);
}
crate::fs::copy(
entry.path(),
to.join(entry.rel_containing).join(&entry.file_name),
)?;
} else {
let dir_path = to.join(entry.rel_containing);
make_dir_impl(&dir_path)?;
}
}
Ok(())
}