use std::ffi::OsStr;
use std::io;
use std::path::Path;
use fs_err as fs;
use fs_err::tokio as tokio_fs;
use tempfile::TempDir;
#[must_use = "You need to call TempDir::close() explicitly."]
pub fn safe_delete_in_tmp(path: &Path, temp_dir: &Path) -> io::Result<TempDir> {
let (parent, _file_name) = split(path).ok_or_else(|| err_invalid_path(path))?;
fs::create_dir_all(temp_dir)?;
let to_delete = tempfile::Builder::new().prefix("").tempdir_in(temp_dir)?;
fs::rename(path, &to_delete)?;
if cfg!(unix) {
fs::File::open(parent)?.sync_all()?;
}
Ok(to_delete)
}
pub fn safe_delete_with_suffix(path: &Path) -> io::Result<()> {
let (parent, file_name) = split(path).ok_or_else(|| err_invalid_path(path))?;
let file_name = file_name.to_string_lossy();
let file_name = file_name.strip_suffix(".deleted").unwrap_or(&file_name);
let to_delete = tempfile::Builder::new()
.prefix(file_name)
.suffix(".deleted")
.tempdir_in(parent)?;
fs::rename(path, &to_delete)?;
if cfg!(unix) {
fs::File::open(parent)?.sync_all()?;
}
to_delete.close()
}
pub fn sync_parent_dir(path: &Path) -> io::Result<()> {
let (parent, _file_name) = split(path).ok_or_else(|| err_invalid_path(path))?;
if cfg!(unix) {
fs::File::open(parent)?.sync_all()?;
}
Ok(())
}
pub async fn sync_parent_dir_async(path: &Path) -> io::Result<()> {
let (parent, _file_name) = split(path).ok_or_else(|| err_invalid_path(path))?;
if cfg!(unix) {
tokio_fs::File::open(parent).await?.sync_all().await?;
}
Ok(())
}
fn split(path: &Path) -> Option<(&Path, &OsStr)> {
let parent = match path.parent()? {
p if p.as_os_str().is_empty() => Path::new("."),
p => p,
};
Some((parent, path.file_name()?))
}
fn err_invalid_path(path: &Path) -> io::Error {
let msg = format!("Invalid path to delete: {}", path.display());
io::Error::new(io::ErrorKind::InvalidInput, msg)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safe_delete() -> io::Result<()> {
let base = tempfile::tempdir()?;
let segments = base.path().join("segments");
let tmp = base.path().join("tmp");
let make_segment = |name: &str| {
let segment = segments.join(name);
fs::create_dir_all(&segment)?;
fs::write(segment.join("file"), b"test")?;
io::Result::Ok(segment)
};
let segment1 = make_segment("segment1")?;
let segment2 = make_segment("segment2")?;
let segment3 = make_segment("segment3")?;
let segment4 = make_segment("segment4")?;
let segment4d = make_segment("segment4.deleted")?; let nonexistent = segments.join("nonexistent");
safe_delete_in_tmp(&segment1, &tmp)?.close()?;
let to_delete = safe_delete_in_tmp(&segment2, &tmp)?;
let handle = std::thread::spawn(move || to_delete.close());
handle.join().unwrap()?;
safe_delete_with_suffix(&segment3)?;
safe_delete_with_suffix(&segment4)?;
safe_delete_with_suffix(&segment4d)?;
assert!(safe_delete_in_tmp(&nonexistent, &tmp).is_err());
assert!(safe_delete_with_suffix(&nonexistent).is_err());
assert_eq!(fs::read_dir(&segments)?.count(), 0);
assert_eq!(fs::read_dir(&tmp)?.count(), 0);
Ok(())
}
}