1use crate::error::Result;
2use crate::format::PackedSnapshot;
3use crate::metadata::SnapshotMetadata;
4use crate::storage::{SnapshotWriter, SnapshotReader, SnapshotStore};
5use std::path::Path;
6use ahash::AHashMap;
7
8#[derive(Debug, Clone)]
9pub struct Checkpoint {
10 pub id: String,
11 pub snapshot: PackedSnapshot,
12 pub metadata: SnapshotMetadata,
13 pub parent_id: Option<String>,
14}
15
16impl Checkpoint {
17 pub fn new(id: String, snapshot: PackedSnapshot) -> Self {
18 let metadata = SnapshotMetadata::new(id.clone());
19
20 Self {
21 id: id.clone(),
22 snapshot,
23 metadata,
24 parent_id: None,
25 }
26 }
27
28 pub fn with_parent(mut self, parent_id: String) -> Self {
29 self.parent_id = Some(parent_id);
30 self
31 }
32
33 pub fn with_metadata(mut self, metadata: SnapshotMetadata) -> Self {
34 self.metadata = metadata;
35 self
36 }
37}
38
39pub struct CheckpointManager {
40 store: SnapshotStore,
41 writer: SnapshotWriter,
42 reader: SnapshotReader,
43 checkpoints: AHashMap<String, Checkpoint>,
44 checkpoint_chain: Vec<String>,
45}
46
47impl CheckpointManager {
48 pub fn new<P: AsRef<Path>>(root_dir: P) -> Result<Self> {
49 let store = SnapshotStore::new(root_dir)?;
50 let writer = SnapshotWriter::new();
51 let reader = SnapshotReader::new();
52
53 Ok(Self {
54 store,
55 writer,
56 reader,
57 checkpoints: AHashMap::new(),
58 checkpoint_chain: Vec::new(),
59 })
60 }
61
62 pub fn with_writer(mut self, writer: SnapshotWriter) -> Self {
63 self.writer = writer;
64 self
65 }
66
67 pub fn with_reader(mut self, reader: SnapshotReader) -> Self {
68 self.reader = reader;
69 self
70 }
71
72 pub fn create_checkpoint(
73 &mut self,
74 id: String,
75 snapshot: PackedSnapshot,
76 ) -> Result<()> {
77 let parent_id = self.checkpoint_chain.last().cloned();
78
79 let mut checkpoint = Checkpoint::new(id.clone(), snapshot);
80 if let Some(parent) = parent_id {
81 checkpoint = checkpoint.with_parent(parent);
82 }
83
84 self.store.save(&checkpoint.snapshot, &checkpoint.metadata, &self.writer)?;
85
86 self.checkpoint_chain.push(id.clone());
87 self.checkpoints.insert(id, checkpoint);
88
89 Ok(())
90 }
91
92 pub fn load_checkpoint(&mut self, id: &str) -> Result<Checkpoint> {
93 if let Some(checkpoint) = self.checkpoints.get(id) {
94 return Ok(checkpoint.clone());
95 }
96
97 let (snapshot, metadata) = self.store.load(id, &self.reader)?;
98
99 let checkpoint = Checkpoint {
100 id: id.to_string(),
101 snapshot,
102 metadata,
103 parent_id: None,
104 };
105
106 self.checkpoints.insert(id.to_string(), checkpoint.clone());
107
108 Ok(checkpoint)
109 }
110
111 pub fn delete_checkpoint(&mut self, id: &str) -> Result<()> {
112 self.store.delete(id)?;
113 self.checkpoints.remove(id);
114 self.checkpoint_chain.retain(|cid| cid != id);
115 Ok(())
116 }
117
118 pub fn list_checkpoints(&self) -> Result<Vec<String>> {
119 self.store.list()
120 }
121
122 pub fn get_checkpoint_chain(&self) -> &[String] {
123 &self.checkpoint_chain
124 }
125
126 pub fn get_latest_checkpoint(&self) -> Option<&str> {
127 self.checkpoint_chain.last().map(|s| s.as_str())
128 }
129
130 pub fn prune_old_checkpoints(&mut self, keep_count: usize) -> Result<()> {
131 let chain_len = self.checkpoint_chain.len();
132
133 if chain_len <= keep_count {
134 return Ok(());
135 }
136
137 let to_remove = chain_len - keep_count;
138
139 for _ in 0..to_remove {
140 if let Some(id) = self.checkpoint_chain.first().cloned() {
141 self.delete_checkpoint(&id)?;
142 }
143 }
144
145 Ok(())
146 }
147
148 pub fn clear_all_checkpoints(&mut self) -> Result<()> {
149 for id in self.checkpoint_chain.clone() {
150 self.delete_checkpoint(&id)?;
151 }
152
153 Ok(())
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use tempfile::TempDir;
161
162 #[test]
163 fn test_checkpoint_manager() {
164 let temp_dir = TempDir::new().unwrap();
165 let mut manager = CheckpointManager::new(temp_dir.path()).unwrap();
166
167 let snapshot1 = PackedSnapshot::new();
168 manager.create_checkpoint("cp1".to_string(), snapshot1).unwrap();
169
170 let snapshot2 = PackedSnapshot::new();
171 manager.create_checkpoint("cp2".to_string(), snapshot2).unwrap();
172
173 assert_eq!(manager.get_checkpoint_chain().len(), 2);
174 assert_eq!(manager.get_latest_checkpoint(), Some("cp2"));
175
176 let loaded = manager.load_checkpoint("cp1").unwrap();
177 assert_eq!(loaded.id, "cp1");
178
179 manager.prune_old_checkpoints(1).unwrap();
180 assert_eq!(manager.get_checkpoint_chain().len(), 1);
181 assert_eq!(manager.get_latest_checkpoint(), Some("cp2"));
182 }
183
184 #[test]
185 fn test_checkpoint_clear() {
186 let temp_dir = TempDir::new().unwrap();
187 let mut manager = CheckpointManager::new(temp_dir.path()).unwrap();
188
189 for i in 0..5 {
190 let snapshot = PackedSnapshot::new();
191 manager.create_checkpoint(format!("cp{}", i), snapshot).unwrap();
192 }
193
194 assert_eq!(manager.get_checkpoint_chain().len(), 5);
195
196 manager.clear_all_checkpoints().unwrap();
197 assert_eq!(manager.get_checkpoint_chain().len(), 0);
198 }
199}