use std::{
fs::{DirEntry, OpenOptions, TryLockError},
io::ErrorKind,
path::{Path, PathBuf},
time::{Duration, SystemTime},
};
use resolved_shared::RESOLVED_ROOT;
use tokio::fs::{create_dir_all, remove_dir_all, remove_file};
use crate::{Error, script_handler::MODULE_NAME};
const CHECK_REQUIREMENT: usize = 512;
const DIRECTORY_AGE: Duration = Duration::from_secs(12 * 60 * 60);
const LOCK_FILE: &str = ".lock";
pub async fn check() -> Result<(), Error> {
let base = RESOLVED_ROOT.clone();
if !base.exists() {
create_dir_all(&base).await?;
}
tokio::spawn(async move {
let lock_file = OpenOptions::new()
.create(true)
.truncate(true)
.read(true)
.write(true)
.open(base.join(LOCK_FILE))
.expect("failed to open lock file");
match lock_file.try_lock() {
Ok(()) => (),
Err(TryLockError::WouldBlock) => return,
Err(e) => {
eprintln!("failed to see lock: {e:?}");
return;
}
}
let dir_count = base.read_dir().map_or(0, std::iter::Iterator::count);
if dir_count <= CHECK_REQUIREMENT && !has_enough_old(&base).unwrap_or(false) {
return;
}
if let Err(e) = run_cleanup(base).await {
eprintln!("{e:?}");
}
lock_file.unlock().expect("failed to unlock lock file");
});
Ok(())
}
fn has_enough_old(base: &Path) -> Result<bool, Error> {
let now = SystemTime::now();
let mut old_dirs = 0;
for dir in base.read_dir()? {
let dir = dir?;
if is_old(&dir, now)? {
old_dirs += 1;
}
if old_dirs > CHECK_REQUIREMENT {
return Ok(true);
}
}
Ok(false)
}
fn is_old(dir: &DirEntry, now: SystemTime) -> Result<bool, Error> {
let created = dir.metadata()?.created()?;
if now.duration_since(created).is_ok_and(|d| d > DIRECTORY_AGE) {
Ok(true)
} else {
Ok(false)
}
}
async fn run_cleanup(base: PathBuf) -> Result<(), Error> {
let now = SystemTime::now();
let mut cleaned = 0;
for dir in base.read_dir()? {
let dir = dir?;
if dir.file_type()?.is_file() {
continue;
}
if !is_old(&dir, now)? {
continue;
}
let module_file = dir.path().join(format!("{MODULE_NAME}.dll"));
match remove_file(&module_file).await {
Err(e) if e.kind() == ErrorKind::NotFound => (),
Err(_) => {
continue;
}
Ok(()) => (),
}
remove_dir_all(dir.path()).await?; cleaned += 1;
}
#[cfg(feature = "tracing")]
tracing::trace!(?cleaned, "cleaned old instance directories");
let _ = cleaned;
Ok(())
}