ricecoder_storage/
project_store.rs1use crate::error::{IoOperation, StorageError, StorageResult};
6use crate::types::ResourceType;
7use std::fs;
8use std::path::{Path, PathBuf};
9
10pub struct ProjectStore {
12 base_path: PathBuf,
14}
15
16impl ProjectStore {
17 pub fn new(base_path: PathBuf) -> Self {
19 ProjectStore { base_path }
20 }
21
22 pub fn with_default_path() -> Self {
24 ProjectStore {
25 base_path: PathBuf::from(".agent"),
26 }
27 }
28
29 pub fn base_path(&self) -> &PathBuf {
31 &self.base_path
32 }
33
34 pub fn initialize(&self) -> StorageResult<()> {
46 self.create_dir_if_not_exists(&self.base_path)?;
48
49 for resource_type in &[
51 ResourceType::Template,
52 ResourceType::Standard,
53 ResourceType::Spec,
54 ResourceType::Steering,
55 ResourceType::Boilerplate,
56 ResourceType::Rule,
57 ] {
58 let resource_path = self.resource_path(*resource_type);
59 self.create_dir_if_not_exists(&resource_path)?;
60 }
61
62 let history_path = self.base_path.join("history");
64 self.create_dir_if_not_exists(&history_path)?;
65
66 let cache_path = self.base_path.join("cache");
68 self.create_dir_if_not_exists(&cache_path)?;
69
70 Ok(())
71 }
72
73 pub fn resource_path(&self, resource_type: ResourceType) -> PathBuf {
75 self.base_path.join(resource_type.dir_name())
76 }
77
78 pub fn store_resource(
80 &self,
81 resource_type: ResourceType,
82 name: &str,
83 content: &[u8],
84 ) -> StorageResult<PathBuf> {
85 let resource_dir = self.resource_path(resource_type);
86 let file_path = resource_dir.join(name);
87
88 self.create_dir_if_not_exists(&resource_dir)?;
90
91 fs::write(&file_path, content)
93 .map_err(|e| StorageError::io_error(file_path.clone(), IoOperation::Write, e))?;
94
95 Ok(file_path)
96 }
97
98 pub fn retrieve_resource(
100 &self,
101 resource_type: ResourceType,
102 name: &str,
103 ) -> StorageResult<Vec<u8>> {
104 let resource_dir = self.resource_path(resource_type);
105 let file_path = resource_dir.join(name);
106
107 fs::read(&file_path).map_err(|e| StorageError::io_error(file_path, IoOperation::Read, e))
108 }
109
110 pub fn list_resources(&self, resource_type: ResourceType) -> StorageResult<Vec<String>> {
112 let resource_dir = self.resource_path(resource_type);
113
114 if !resource_dir.exists() {
115 return Ok(Vec::new());
116 }
117
118 let mut resources = Vec::new();
119 let entries = fs::read_dir(&resource_dir)
120 .map_err(|e| StorageError::io_error(resource_dir.clone(), IoOperation::Read, e))?;
121
122 for entry in entries {
123 let entry = entry
124 .map_err(|e| StorageError::io_error(resource_dir.clone(), IoOperation::Read, e))?;
125
126 let path = entry.path();
127 if path.is_file() {
128 if let Some(file_name) = path.file_name() {
129 if let Some(name_str) = file_name.to_str() {
130 resources.push(name_str.to_string());
131 }
132 }
133 }
134 }
135
136 Ok(resources)
137 }
138
139 pub fn delete_resource(&self, resource_type: ResourceType, name: &str) -> StorageResult<()> {
141 let resource_dir = self.resource_path(resource_type);
142 let file_path = resource_dir.join(name);
143
144 if file_path.exists() {
145 fs::remove_file(&file_path)
146 .map_err(|e| StorageError::io_error(file_path, IoOperation::Delete, e))?;
147 }
148
149 Ok(())
150 }
151
152 pub fn resource_exists(&self, resource_type: ResourceType, name: &str) -> bool {
154 let resource_dir = self.resource_path(resource_type);
155 let file_path = resource_dir.join(name);
156 file_path.exists()
157 }
158
159 pub fn create_folder(&self, folder_name: &str) -> StorageResult<PathBuf> {
164 let folder_path = self.base_path.join(folder_name);
165 self.create_dir_if_not_exists(&folder_path)?;
166 Ok(folder_path)
167 }
168
169 pub fn folder_exists(&self, folder_name: &str) -> bool {
171 let folder_path = self.base_path.join(folder_name);
172 folder_path.is_dir()
173 }
174
175 fn create_dir_if_not_exists(&self, path: &Path) -> StorageResult<()> {
177 if !path.exists() {
178 fs::create_dir_all(path)
179 .map_err(|e| StorageError::directory_creation_failed(path.to_path_buf(), e))?;
180 }
181 Ok(())
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use tempfile::TempDir;
189
190 #[test]
191 fn test_project_store_initialization() {
192 let temp_dir = TempDir::new().expect("Failed to create temp dir");
193 let store = ProjectStore::new(temp_dir.path().to_path_buf());
194
195 store.initialize().expect("Failed to initialize store");
196
197 assert!(store.resource_path(ResourceType::Template).exists());
199 assert!(store.resource_path(ResourceType::Standard).exists());
200 assert!(store.resource_path(ResourceType::Spec).exists());
201 assert!(store.resource_path(ResourceType::Steering).exists());
202 assert!(store.resource_path(ResourceType::Boilerplate).exists());
203 assert!(store.resource_path(ResourceType::Rule).exists());
204 assert!(temp_dir.path().join("history").exists());
205 assert!(temp_dir.path().join("cache").exists());
206 }
207
208 #[test]
209 fn test_store_and_retrieve_resource() {
210 let temp_dir = TempDir::new().expect("Failed to create temp dir");
211 let store = ProjectStore::new(temp_dir.path().to_path_buf());
212 store.initialize().expect("Failed to initialize store");
213
214 let content = b"test content";
215 let name = "test.txt";
216
217 let path = store
219 .store_resource(ResourceType::Template, name, content)
220 .expect("Failed to store resource");
221
222 assert!(path.exists());
223
224 let retrieved = store
226 .retrieve_resource(ResourceType::Template, name)
227 .expect("Failed to retrieve resource");
228
229 assert_eq!(retrieved, content);
230 }
231
232 #[test]
233 fn test_create_folder_on_demand() {
234 let temp_dir = TempDir::new().expect("Failed to create temp dir");
235 let store = ProjectStore::new(temp_dir.path().to_path_buf());
236 store.initialize().expect("Failed to initialize store");
237
238 let folder_name = "custom_folder";
239 assert!(!store.folder_exists(folder_name));
240
241 let folder_path = store
242 .create_folder(folder_name)
243 .expect("Failed to create folder");
244
245 assert!(folder_path.exists());
246 assert!(store.folder_exists(folder_name));
247 }
248
249 #[test]
250 fn test_list_resources() {
251 let temp_dir = TempDir::new().expect("Failed to create temp dir");
252 let store = ProjectStore::new(temp_dir.path().to_path_buf());
253 store.initialize().expect("Failed to initialize store");
254
255 store
257 .store_resource(ResourceType::Template, "template1.txt", b"content1")
258 .expect("Failed to store");
259 store
260 .store_resource(ResourceType::Template, "template2.txt", b"content2")
261 .expect("Failed to store");
262
263 let resources = store
265 .list_resources(ResourceType::Template)
266 .expect("Failed to list resources");
267
268 assert_eq!(resources.len(), 2);
269 assert!(resources.contains(&"template1.txt".to_string()));
270 assert!(resources.contains(&"template2.txt".to_string()));
271 }
272
273 #[test]
274 fn test_delete_resource() {
275 let temp_dir = TempDir::new().expect("Failed to create temp dir");
276 let store = ProjectStore::new(temp_dir.path().to_path_buf());
277 store.initialize().expect("Failed to initialize store");
278
279 let name = "test.txt";
280 store
281 .store_resource(ResourceType::Template, name, b"content")
282 .expect("Failed to store");
283
284 assert!(store.resource_exists(ResourceType::Template, name));
285
286 store
287 .delete_resource(ResourceType::Template, name)
288 .expect("Failed to delete");
289
290 assert!(!store.resource_exists(ResourceType::Template, name));
291 }
292
293 #[test]
294 fn test_resource_exists() {
295 let temp_dir = TempDir::new().expect("Failed to create temp dir");
296 let store = ProjectStore::new(temp_dir.path().to_path_buf());
297 store.initialize().expect("Failed to initialize store");
298
299 let name = "test.txt";
300 assert!(!store.resource_exists(ResourceType::Template, name));
301
302 store
303 .store_resource(ResourceType::Template, name, b"content")
304 .expect("Failed to store");
305
306 assert!(store.resource_exists(ResourceType::Template, name));
307 }
308}