use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
pub struct UndoStore {
root: PathBuf,
}
static NEXT_SEQ: AtomicU64 = AtomicU64::new(0);
const MAX_ATTEMPTS: u32 = 64;
impl UndoStore {
pub fn new(root: impl Into<PathBuf>) -> UndoStore {
UndoStore { root: root.into() }
}
pub async fn stash(&self, victim: &Path, now_ms: u64) -> Option<PathBuf> {
let meta = tokio::fs::symlink_metadata(victim).await.ok()?;
let name = victim.file_name()?;
let slot = self.claim_slot(now_ms).await?;
let dest = slot.join(name);
if tokio::fs::rename(victim, &dest).await.is_ok() {
return Some(dest);
}
if meta.file_type().is_symlink() {
return None;
}
tokio::fs::copy(victim, &dest).await.ok()?;
let _ = tokio::fs::remove_file(victim).await;
Some(dest)
}
async fn claim_slot(&self, now_ms: u64) -> Option<PathBuf> {
tokio::fs::create_dir_all(&self.root).await.ok()?;
for _ in 0..MAX_ATTEMPTS {
let seq = NEXT_SEQ.fetch_add(1, Ordering::Relaxed);
let candidate = self.root.join(format!("{now_ms}-{seq}"));
match tokio::fs::create_dir(&candidate).await {
Ok(()) => return Some(candidate),
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(_) => return None,
}
}
None
}
}