use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
use std::fs::{self, File};
use std::path::PathBuf;
use vtcode_commons::VtCodePaths;
use super::super::install_support::{
acquire_lock_file, cache_is_stale, load_json_cache, lock_is_active, save_json_cache, unix_timestamp_now,
};
const INSTALL_LOCK_MAX_AGE_SECS: u64 = 1_800;
const INSTALL_CACHE_STALE_AFTER_SECS: u64 = 86_400;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(super) struct InstallationCache {
last_attempt: u64,
pub(super) status: String,
method_attempted: Option<String>,
pub(super) failure_reason: Option<String>,
}
#[derive(Debug)]
pub(super) struct InstallLockGuard {
path: PathBuf,
_file: File,
}
impl InstallationCache {
fn state_dir() -> Result<PathBuf> {
VtCodePaths::resolve()?.ensure_cache_child_dir("ripgrep")
}
fn cache_path() -> Result<PathBuf> {
let paths = VtCodePaths::resolve()?;
paths.ensure_cache_child_dir("ripgrep")?;
paths.cache_path("ripgrep/ripgrep_install_cache.json")
}
pub(super) fn is_stale() -> bool {
match Self::load() {
Ok(cache) => cache_is_stale(cache.last_attempt, INSTALL_CACHE_STALE_AFTER_SECS),
Err(_) => true,
}
}
pub(super) fn load() -> Result<Self> {
load_json_cache(&Self::cache_path()?, "ripgrep installation cache")
}
fn save(&self) -> Result<()> {
let state_dir = Self::state_dir()?;
let cache_path = Self::cache_path()?;
save_json_cache(&state_dir, &cache_path, self, "ripgrep installation cache")
}
pub(super) fn mark_failed(method: &str, reason: &str) {
let cache = InstallationCache {
last_attempt: unix_timestamp_now(),
status: "failed".to_string(),
method_attempted: Some(method.to_string()),
failure_reason: Some(reason.to_string()),
};
let _ = cache.save();
}
pub(super) fn mark_success(method: &str) {
let cache = InstallationCache {
last_attempt: unix_timestamp_now(),
status: "success".to_string(),
method_attempted: Some(method.to_string()),
failure_reason: None,
};
let _ = cache.save();
}
}
impl InstallLockGuard {
pub(super) fn acquire() -> Result<Self> {
let path = lock_path()?;
match acquire_lock_file(&path, INSTALL_LOCK_MAX_AGE_SECS)? {
Some(file) => Ok(Self { path, _file: file }),
None => Err(anyhow!("Ripgrep installation already in progress")),
}
}
pub(super) fn is_install_in_progress() -> bool {
lock_path().is_ok_and(|path| lock_is_active(&path, INSTALL_LOCK_MAX_AGE_SECS))
}
}
impl Drop for InstallLockGuard {
fn drop(&mut self) {
let _ = fs::remove_file(&self.path);
}
}
fn lock_path() -> Result<PathBuf> {
VtCodePaths::resolve()?
.ensure_runtime_child_dir("ripgrep")
.map(|path| path.join("install.lock"))
}