use crate::{Error, Result};
use std::fs;
use std::path::{Path, PathBuf};
pub(crate) fn create_dir_all_durable(path: &Path, action: &str) -> Result<()> {
let absolute = absolute_path(path)?;
let existing_ancestor = nearest_existing_ancestor(&absolute, action)?;
fs::create_dir_all(&absolute).map_err(|error| Error::io(action, error))?;
let mut current = absolute;
loop {
sync_directory(¤t)?;
if current == existing_ancestor {
break;
}
current = current.parent().map(Path::to_path_buf).ok_or_else(|| {
Error::new("io: durable directory creation lost its existing ancestor")
})?;
}
Ok(())
}
pub(crate) fn sync_tree_directories(root: &Path) -> Result<()> {
let mut directories = vec![root.to_path_buf()];
collect_directories(root, &mut directories)?;
directories.sort_by_key(|path| std::cmp::Reverse(path.components().count()));
for directory in directories {
sync_directory(&directory)?;
}
Ok(())
}
pub(crate) fn sync_directory(path: &Path) -> Result<()> {
let directory =
fs::File::open(path).map_err(|error| Error::io("open directory for sync", error))?;
directory
.sync_all()
.map_err(|error| Error::io("sync directory", error))
}
fn collect_directories(directory: &Path, output: &mut Vec<PathBuf>) -> Result<()> {
let entries =
fs::read_dir(directory).map_err(|error| Error::io("read directory for sync", error))?;
for entry in entries {
let entry = entry.map_err(|error| Error::io("read directory entry for sync", error))?;
let metadata = entry
.metadata()
.map_err(|error| Error::io("inspect directory entry for sync", error))?;
if metadata.is_dir() {
let path = entry.path();
output.push(path.clone());
collect_directories(&path, output)?;
}
}
Ok(())
}
fn absolute_path(path: &Path) -> Result<PathBuf> {
if path.is_absolute() {
return Ok(path.to_path_buf());
}
std::env::current_dir()
.map(|current| current.join(path))
.map_err(|error| Error::io("resolve durable directory path", error))
}
fn nearest_existing_ancestor(path: &Path, action: &str) -> Result<PathBuf> {
let mut current = path.to_path_buf();
loop {
match fs::symlink_metadata(¤t) {
Ok(_) => return Ok(current),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
current = current.parent().map(Path::to_path_buf).ok_or_else(|| {
Error::new(format!(
"io: {action}: path has no existing filesystem ancestor"
))
})?;
}
Err(error) => return Err(Error::io(action, error)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn durably_creates_nested_directories() {
let root = TempDir::new().unwrap();
let nested = root.path().join("one/two/three");
create_dir_all_durable(&nested, "create nested test directory").unwrap();
assert!(nested.is_dir());
}
}