use std::path::Path;
use std::{fmt, fs};
use crate::common::operation_error::{OperationError, OperationResult};
/// Move all files and directories from the `dir` directory to the `dest_dir` directory.
///
/// - `
/child/directory` will be merged with `/child/directory` if one already exists
/// - `/some/file` will overwrite `/some/file` if one already exists
pub fn move_all(dir: &Path, dest_dir: &Path) -> OperationResult<()> {
assert_is_dir(dir)?;
assert_is_dir(dest_dir)?;
move_all_impl(dir, dir, dest_dir).map_err(|err| failed_to_move_error(dir, dest_dir, err))
}
fn move_all_impl(base: &Path, dir: &Path, dest_dir: &Path) -> OperationResult<()> {
let entries = dir.read_dir().map_err(|err| {
if base != dir {
failed_to_read_dir_error(dir, err)
} else {
err.into()
}
})?;
for entry in entries {
let entry = entry.map_err(|err| {
if base != dir {
failed_to_read_dir_error(dir, err)
} else {
err.into()
}
})?;
let path = entry.path();
let name = path
.file_name()
.ok_or_else(|| failed_to_move_error(&path, dest_dir, "source path ends with .."))?;
let dest_path = dest_dir.join(name);
if path.is_dir() && dest_path.exists() {
move_all_impl(base, &path, &dest_path)?;
std::fs::remove_dir(path)?;
} else {
if let Some(dir) = dest_path.parent() {
if !dir.exists() {
fs::create_dir_all(dir).map_err(|err| {
failed_to_move_error(
&path,
&dest_path,
format!("failed to create {dir:?} directory: {err}"),
)
})?;
}
}
fs::rename(&path, &dest_path)
.map_err(|err| failed_to_move_error(&path, &dest_path, err))?;
}
}
Ok(())
}
fn assert_is_dir(dir: &Path) -> OperationResult<()> {
if dir.is_dir() {
Ok(())
} else {
Err(not_a_dir_error(dir))
}
}
fn not_a_dir_error(dir: &Path) -> OperationError {
OperationError::service_error(format!(
"path {dir:?} is not a directory (or does not exist)"
))
}
fn failed_to_read_dir_error(dir: &Path, err: impl fmt::Display) -> OperationError {
OperationError::service_error(format!("failed to read {dir:?} directory: {err}"))
}
fn failed_to_move_error(path: &Path, dest: &Path, err: impl fmt::Display) -> OperationError {
OperationError::service_error(format!("failed to move {path:?} to {dest:?}: {err}"))
}