rust_docs_mcp/cache/
transaction.rs1use anyhow::{Context, Result};
7use std::path::PathBuf;
8
9use crate::cache::storage::CacheStorage;
10
11pub struct CacheTransaction<'a> {
13 storage: &'a CacheStorage,
14 crate_name: String,
15 version: String,
16 backup_path: Option<PathBuf>,
17}
18
19impl<'a> CacheTransaction<'a> {
20 pub fn new(storage: &'a CacheStorage, crate_name: &str, version: &str) -> Self {
22 Self {
23 storage,
24 crate_name: crate_name.to_string(),
25 version: version.to_string(),
26 backup_path: None,
27 }
28 }
29
30 pub fn begin(&mut self) -> Result<()> {
32 if self.storage.is_cached(&self.crate_name, &self.version) {
33 let backup_path = self
34 .storage
35 .backup_crate_to_temp(&self.crate_name, &self.version)
36 .context("Failed to create backup")?;
37 self.backup_path = Some(backup_path);
38
39 self.storage
41 .remove_crate(&self.crate_name, &self.version)
42 .context("Failed to remove existing cache")?;
43 }
44 Ok(())
45 }
46
47 pub fn commit(mut self) -> Result<()> {
49 if let Some(backup_path) = self.backup_path.take() {
50 let _ = self.storage.cleanup_backup(&backup_path);
52 }
53 Ok(())
54 }
55
56 pub fn rollback(&mut self) -> Result<()> {
58 if let Some(backup_path) = self.backup_path.take() {
59 if !backup_path.exists() {
61 anyhow::bail!(
62 "Backup path does not exist: {}. Cannot rollback.",
63 backup_path.display()
64 );
65 }
66
67 self.storage
68 .restore_crate_from_backup(&self.crate_name, &self.version, &backup_path)
69 .context("Failed to restore from backup")?;
70
71 let _ = self.storage.cleanup_backup(&backup_path);
73 }
74 Ok(())
75 }
76}
77
78impl<'a> Drop for CacheTransaction<'a> {
79 fn drop(&mut self) {
80 if self.backup_path.is_some() {
82 let _ = self.rollback();
83 }
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use std::fs;
91 use tempfile::TempDir;
92
93 #[test]
94 fn test_transaction_commit() -> Result<()> {
95 let temp_dir = TempDir::new()?;
96 let storage = CacheStorage::new(Some(temp_dir.path().to_path_buf()))?;
97
98 let source_path = storage.source_path("test-crate", "1.0.0")?;
100 storage.ensure_dir(&source_path)?;
101 fs::write(source_path.join("file.txt"), "original content")?;
102 storage.save_metadata("test-crate", "1.0.0")?;
103
104 let mut transaction = CacheTransaction::new(&storage, "test-crate", "1.0.0");
106 transaction.begin()?;
107
108 assert!(!storage.is_cached("test-crate", "1.0.0"));
110
111 let new_source_path = storage.source_path("test-crate", "1.0.0")?;
113 storage.ensure_dir(&new_source_path)?;
114 fs::write(new_source_path.join("file.txt"), "new content")?;
115 storage.save_metadata("test-crate", "1.0.0")?;
116
117 transaction.commit()?;
119
120 assert!(storage.is_cached("test-crate", "1.0.0"));
122 let content = fs::read_to_string(new_source_path.join("file.txt"))?;
123 assert_eq!(content, "new content");
124
125 Ok(())
126 }
127
128 #[test]
129 fn test_transaction_rollback() -> Result<()> {
130 let temp_dir = TempDir::new()?;
131 let storage = CacheStorage::new(Some(temp_dir.path().to_path_buf()))?;
132
133 let source_path = storage.source_path("test-crate", "1.0.0")?;
135 storage.ensure_dir(&source_path)?;
136 fs::write(source_path.join("file.txt"), "original content")?;
137
138 storage.save_metadata("test-crate", "1.0.0")?;
140
141 assert!(storage.is_cached("test-crate", "1.0.0"));
143
144 let mut transaction = CacheTransaction::new(&storage, "test-crate", "1.0.0");
146 transaction.begin()?;
147 assert!(!storage.is_cached("test-crate", "1.0.0"));
149
150 transaction.rollback()?;
152
153 assert!(storage.is_cached("test-crate", "1.0.0"));
155 let restored_source_path = storage.source_path("test-crate", "1.0.0")?;
156 let content = fs::read_to_string(restored_source_path.join("file.txt"))?;
157 assert_eq!(content, "original content");
158
159 Ok(())
160 }
161}