heddle_pack/store/pack/
scratch.rs1use std::{
5 fs::{self, File, OpenOptions},
6 io,
7 path::Path,
8 time::{Duration, SystemTime},
9};
10
11pub(super) const LEASE_FILE: &str = ".lease";
12
13#[derive(Debug)]
15pub struct ScratchLease(File);
16impl ScratchLease {
17 pub fn acquire(path: &Path) -> io::Result<Self> {
18 let file = OpenOptions::new()
19 .read(true)
20 .write(true)
21 .create(true)
22 .truncate(false)
23 .open(path.join(LEASE_FILE))?;
24 file.lock_shared()?;
25 Ok(Self(file))
26 }
27}
28impl Drop for ScratchLease {
29 fn drop(&mut self) {
30 let _ = self.0.unlock();
31 }
32}
33
34#[derive(Debug)]
37pub struct ScratchDir {
38 _lease: ScratchLease,
39 directory: tempfile::TempDir,
40}
41impl ScratchDir {
42 pub fn new(root: &Path, prefix: &str) -> io::Result<Self> {
43 fs::create_dir_all(root)?;
44 let directory = tempfile::Builder::new().prefix(prefix).tempdir_in(root)?;
45 let lease = ScratchLease::acquire(directory.path())?;
46 Ok(Self {
47 _lease: lease,
48 directory,
49 })
50 }
51 pub fn path(&self) -> &Path {
52 self.directory.path()
53 }
54}
55
56pub fn sweep_scratch(root: &Path) {
60 let Ok(entries) = fs::read_dir(root) else {
61 return;
62 };
63 let now = SystemTime::now();
64 for entry in entries.flatten() {
65 let path = entry.path();
66 let Ok(metadata) = fs::symlink_metadata(&path) else {
67 continue;
68 };
69 if metadata.file_type().is_symlink() {
70 continue;
71 }
72 let expired = metadata
73 .modified()
74 .ok()
75 .and_then(|modified| now.duration_since(modified).ok())
76 .is_some_and(|age| age > Duration::from_secs(86400));
77 if !expired {
78 continue;
79 }
80 let lease_path = if metadata.is_dir() {
81 path.join(LEASE_FILE)
82 } else {
83 path.clone()
84 };
85 let lease = match File::open(&lease_path) {
88 Ok(file) => Some(file),
89 Err(error) if metadata.is_dir() && error.kind() == io::ErrorKind::NotFound => None,
90 Err(_) => continue,
91 };
92 if lease.as_ref().is_some_and(|file| file.try_lock().is_err()) {
93 continue;
94 }
95 if metadata.is_dir() {
96 let _ = fs::remove_dir_all(&path);
97 } else {
98 let _ = fs::remove_file(&path);
99 }
100 }
101}