Skip to main content

claude_utils/turbo/
checkpoint_store.rs

1use chrono::Utc;
2use std::collections::VecDeque;
3use std::path::PathBuf;
4use tokio::fs;
5use tracing::{error, info};
6
7use super::rollback::Checkpoint;
8
9const CHECKPOINT_FILE: &str = "checkpoints.json";
10
11pub struct CheckpointStore {
12    storage_dir: PathBuf,
13}
14
15impl Default for CheckpointStore {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21impl CheckpointStore {
22    pub fn new() -> Self {
23        let storage_dir = dirs::data_dir()
24            .unwrap_or_else(|| PathBuf::from("."))
25            .join("claude-utils")
26            .join("turbo");
27        
28        Self { storage_dir }
29    }
30
31    async fn ensure_storage_dir(&self) -> crate::Result<()> {
32        fs::create_dir_all(&self.storage_dir).await?;
33        Ok(())
34    }
35
36    fn checkpoint_file(&self) -> PathBuf {
37        self.storage_dir.join(CHECKPOINT_FILE)
38    }
39
40    pub async fn load_checkpoints(&self) -> crate::Result<VecDeque<Checkpoint>> {
41        self.ensure_storage_dir().await?;
42        
43        let file_path = self.checkpoint_file();
44        if !file_path.exists() {
45            return Ok(VecDeque::new());
46        }
47
48        let content = fs::read_to_string(&file_path).await?;
49        let checkpoints: VecDeque<Checkpoint> = serde_json::from_str(&content)
50            .unwrap_or_else(|e| {
51                error!("Failed to parse checkpoints: {}", e);
52                VecDeque::new()
53            });
54
55        Ok(checkpoints)
56    }
57
58    pub async fn save_checkpoints(&self, checkpoints: &VecDeque<Checkpoint>) -> crate::Result<()> {
59        self.ensure_storage_dir().await?;
60        
61        let content = serde_json::to_string_pretty(checkpoints)?;
62        fs::write(self.checkpoint_file(), content).await?;
63        
64        info!("Saved {} checkpoints to disk", checkpoints.len());
65        Ok(())
66    }
67
68    pub async fn clear_old_checkpoints(&self, max_age_days: i64) -> crate::Result<()> {
69        let mut checkpoints = self.load_checkpoints().await?;
70        let cutoff = Utc::now() - chrono::Duration::days(max_age_days);
71        
72        let original_count = checkpoints.len();
73        checkpoints.retain(|cp| cp.timestamp > cutoff);
74        
75        if checkpoints.len() < original_count {
76            info!("Removed {} old checkpoints", original_count - checkpoints.len());
77            self.save_checkpoints(&checkpoints).await?;
78        }
79        
80        Ok(())
81    }
82}