mermaid_cli/utils/
atomic.rs1use std::fs::{self, File};
11use std::io::Write;
12use std::path::Path;
13use std::sync::atomic::{AtomicU64, Ordering};
14
15static COUNTER: AtomicU64 = AtomicU64::new(0);
16
17pub fn write_atomic(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
21 let parent = path.parent().unwrap_or_else(|| Path::new("."));
22 fs::create_dir_all(parent)?;
23
24 let stem = path.file_name().and_then(|n| n.to_str()).unwrap_or("tmp");
25 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
26 let tmp = parent.join(format!(".{}.{}.{}.tmp", stem, std::process::id(), n));
27
28 {
29 let mut f = File::create(&tmp)?;
30 f.write_all(bytes)?;
31 f.sync_all()?;
32 }
33
34 if let Err(e) = fs::rename(&tmp, path) {
35 let _ = fs::remove_file(&tmp);
36 return Err(e);
37 }
38
39 if let Ok(dir) = File::open(parent) {
42 let _ = dir.sync_all();
43 }
44 Ok(())
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 #[test]
52 fn atomic_write_replaces_existing_and_no_temp_left() {
53 let dir = std::env::temp_dir().join(format!("mermaid_atomic_{}", std::process::id()));
54 let _ = fs::create_dir_all(&dir);
55 let target = dir.join("conv.json");
56 write_atomic(&target, b"first").unwrap();
57 assert_eq!(fs::read_to_string(&target).unwrap(), "first");
58 write_atomic(&target, b"second").unwrap();
59 assert_eq!(fs::read_to_string(&target).unwrap(), "second");
60 let leftovers = fs::read_dir(&dir)
62 .unwrap()
63 .flatten()
64 .filter(|e| e.file_name().to_string_lossy().ends_with(".tmp"))
65 .count();
66 assert_eq!(leftovers, 0);
67 let _ = fs::remove_dir_all(&dir);
68 }
69}