use chrono::Utc;
use std::collections::VecDeque;
use std::path::PathBuf;
use tokio::fs;
use tracing::{error, info};
use super::rollback::Checkpoint;
const CHECKPOINT_FILE: &str = "checkpoints.json";
pub struct CheckpointStore {
storage_dir: PathBuf,
}
impl Default for CheckpointStore {
fn default() -> Self {
Self::new()
}
}
impl CheckpointStore {
pub fn new() -> Self {
let storage_dir = dirs::data_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("claude-utils")
.join("turbo");
Self { storage_dir }
}
async fn ensure_storage_dir(&self) -> crate::Result<()> {
fs::create_dir_all(&self.storage_dir).await?;
Ok(())
}
fn checkpoint_file(&self) -> PathBuf {
self.storage_dir.join(CHECKPOINT_FILE)
}
pub async fn load_checkpoints(&self) -> crate::Result<VecDeque<Checkpoint>> {
self.ensure_storage_dir().await?;
let file_path = self.checkpoint_file();
if !file_path.exists() {
return Ok(VecDeque::new());
}
let content = fs::read_to_string(&file_path).await?;
let checkpoints: VecDeque<Checkpoint> = serde_json::from_str(&content)
.unwrap_or_else(|e| {
error!("Failed to parse checkpoints: {}", e);
VecDeque::new()
});
Ok(checkpoints)
}
pub async fn save_checkpoints(&self, checkpoints: &VecDeque<Checkpoint>) -> crate::Result<()> {
self.ensure_storage_dir().await?;
let content = serde_json::to_string_pretty(checkpoints)?;
fs::write(self.checkpoint_file(), content).await?;
info!("Saved {} checkpoints to disk", checkpoints.len());
Ok(())
}
pub async fn clear_old_checkpoints(&self, max_age_days: i64) -> crate::Result<()> {
let mut checkpoints = self.load_checkpoints().await?;
let cutoff = Utc::now() - chrono::Duration::days(max_age_days);
let original_count = checkpoints.len();
checkpoints.retain(|cp| cp.timestamp > cutoff);
if checkpoints.len() < original_count {
info!("Removed {} old checkpoints", original_count - checkpoints.len());
self.save_checkpoints(&checkpoints).await?;
}
Ok(())
}
}