use crate::{
error::{Error, ErrorKind, Result, with_io_context},
sandbox::DryRun,
};
use std::{borrow::Cow, fmt::Display, io, path::Path};
#[derive(Debug, Clone, Copy)]
pub(super) struct FsHandle {
dry_run: DryRun,
}
impl FsHandle {
pub const fn new(dry_run: DryRun) -> Self {
Self { dry_run }
}
pub(super) fn sync_dir(self, initial_source: &Path, initial_dest: &Path) -> Result<()> {
if matches!(self.dry_run, DryRun::Enabled) {
return Ok(());
}
if !initial_dest.is_dir() {
return Err(with_io_context(
format!("from {} to {}", initial_source.display(), initial_dest.display()),
"unable to sync to non-directory output",
io::ErrorKind::NotADirectory,
));
}
let mut queue = vec![(Cow::Borrowed(initial_source), Cow::Borrowed(initial_dest))];
while let Some((current_source, current_dest)) = queue.pop() {
self.create_dir(
¤t_dest,
format_args!("syncing directory {}", current_source.display()),
)?;
let entries = current_source.read_dir().map_err(|e| {
with_io_context(
format!("{} under {}", current_source.display(), initial_source.display()),
"unable to read dir for syncing",
e,
)
})?;
for entry in entries {
let entry = entry.map_err(|e| {
with_io_context(
format!("{} under {}", current_source.display(), initial_source.display()),
"unable to read entry from when syncing",
e,
)
})?;
let path = entry.path();
let output = current_dest.join(entry.file_name());
if path.is_dir() {
queue.push((Cow::Owned(path), Cow::Owned(output)));
} else {
std::fs::copy(&path, current_dest.join(entry.file_name())).map_err(|e| {
with_io_context(
format!("{} to {}", path.display(), output.display()),
"unable to copy when syncing",
e,
)
})?;
}
}
}
Ok(())
}
pub(super) fn validate_is_dir(self, path: &Path, context: &'static str) -> Result<()> {
if matches!(self.dry_run, DryRun::Enabled) {
return Ok(());
}
if !path.is_dir() {
return Err(with_io_context(path.display(), context, io::ErrorKind::NotADirectory));
}
Ok(())
}
pub(super) fn validate_relative_path(
self,
path: &Path,
context: impl Display,
) -> Result<(), Error> {
if matches!(self.dry_run, DryRun::Enabled) {
return Ok(());
}
if path.is_absolute() {
return Err(Error {
kind: ErrorKind::NonRelativePath(path.into()),
context: context.to_string(),
});
}
Ok(())
}
pub(super) fn create_dir(
self,
passthrough_dest: &Path,
context: impl Display,
) -> Result<(), Error> {
if matches!(self.dry_run, DryRun::Enabled) {
return Ok(());
}
std::fs::create_dir_all(passthrough_dest)
.map_err(|e| with_io_context(passthrough_dest.display(), context, e.kind()))?;
Ok(())
}
}