ricecoder_storage/
global_store.rs

1//! Global storage implementation for RiceCoder
2//!
3//! Manages the global knowledge base stored in ~/Documents/.ricecoder/
4
5use crate::error::{IoOperation, StorageError, StorageResult};
6use crate::manager::PathResolver;
7use crate::types::ResourceType;
8use std::fs;
9use std::path::{Path, PathBuf};
10
11/// Global store for managing global knowledge base
12pub struct GlobalStore {
13    /// Path to the global storage directory
14    base_path: PathBuf,
15}
16
17impl GlobalStore {
18    /// Create a new global store
19    pub fn new(base_path: PathBuf) -> Self {
20        GlobalStore { base_path }
21    }
22
23    /// Create a new global store with default path resolution
24    pub fn with_default_path() -> StorageResult<Self> {
25        let base_path = PathResolver::resolve_global_path()?;
26        Ok(GlobalStore { base_path })
27    }
28
29    /// Get the base path
30    pub fn base_path(&self) -> &PathBuf {
31        &self.base_path
32    }
33
34    /// Initialize the global store directory structure
35    ///
36    /// Creates the base directory and all resource subdirectories:
37    /// - templates/
38    /// - standards/
39    /// - specs/
40    /// - steering/
41    /// - boilerplates/
42    /// - rules/
43    /// - cache/
44    pub fn initialize(&self) -> StorageResult<()> {
45        // Create base directory
46        self.create_dir_if_not_exists(&self.base_path)?;
47
48        // Create resource directories
49        for resource_type in &[
50            ResourceType::Template,
51            ResourceType::Standard,
52            ResourceType::Spec,
53            ResourceType::Steering,
54            ResourceType::Boilerplate,
55            ResourceType::Rule,
56        ] {
57            let resource_path = self.resource_path(*resource_type);
58            self.create_dir_if_not_exists(&resource_path)?;
59        }
60
61        // Create cache directory
62        let cache_path = self.base_path.join("cache");
63        self.create_dir_if_not_exists(&cache_path)?;
64
65        Ok(())
66    }
67
68    /// Get the path for a resource type
69    pub fn resource_path(&self, resource_type: ResourceType) -> PathBuf {
70        self.base_path.join(resource_type.dir_name())
71    }
72
73    /// Store a resource file
74    pub fn store_resource(
75        &self,
76        resource_type: ResourceType,
77        name: &str,
78        content: &[u8],
79    ) -> StorageResult<PathBuf> {
80        let resource_dir = self.resource_path(resource_type);
81        let file_path = resource_dir.join(name);
82
83        // Ensure directory exists
84        self.create_dir_if_not_exists(&resource_dir)?;
85
86        // Write file
87        fs::write(&file_path, content)
88            .map_err(|e| StorageError::io_error(file_path.clone(), IoOperation::Write, e))?;
89
90        Ok(file_path)
91    }
92
93    /// Retrieve a resource file
94    pub fn retrieve_resource(
95        &self,
96        resource_type: ResourceType,
97        name: &str,
98    ) -> StorageResult<Vec<u8>> {
99        let resource_dir = self.resource_path(resource_type);
100        let file_path = resource_dir.join(name);
101
102        fs::read(&file_path).map_err(|e| StorageError::io_error(file_path, IoOperation::Read, e))
103    }
104
105    /// List all resources of a type
106    pub fn list_resources(&self, resource_type: ResourceType) -> StorageResult<Vec<String>> {
107        let resource_dir = self.resource_path(resource_type);
108
109        if !resource_dir.exists() {
110            return Ok(Vec::new());
111        }
112
113        let mut resources = Vec::new();
114        let entries = fs::read_dir(&resource_dir)
115            .map_err(|e| StorageError::io_error(resource_dir.clone(), IoOperation::Read, e))?;
116
117        for entry in entries {
118            let entry = entry
119                .map_err(|e| StorageError::io_error(resource_dir.clone(), IoOperation::Read, e))?;
120
121            let path = entry.path();
122            if path.is_file() {
123                if let Some(file_name) = path.file_name() {
124                    if let Some(name_str) = file_name.to_str() {
125                        resources.push(name_str.to_string());
126                    }
127                }
128            }
129        }
130
131        Ok(resources)
132    }
133
134    /// Delete a resource file
135    pub fn delete_resource(&self, resource_type: ResourceType, name: &str) -> StorageResult<()> {
136        let resource_dir = self.resource_path(resource_type);
137        let file_path = resource_dir.join(name);
138
139        if file_path.exists() {
140            fs::remove_file(&file_path)
141                .map_err(|e| StorageError::io_error(file_path, IoOperation::Delete, e))?;
142        }
143
144        Ok(())
145    }
146
147    /// Check if a resource exists
148    pub fn resource_exists(&self, resource_type: ResourceType, name: &str) -> bool {
149        let resource_dir = self.resource_path(resource_type);
150        let file_path = resource_dir.join(name);
151        file_path.exists()
152    }
153
154    /// Create a directory if it doesn't exist
155    fn create_dir_if_not_exists(&self, path: &Path) -> StorageResult<()> {
156        if !path.exists() {
157            fs::create_dir_all(path)
158                .map_err(|e| StorageError::directory_creation_failed(path.to_path_buf(), e))?;
159        }
160        Ok(())
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use tempfile::TempDir;
168
169    #[test]
170    fn test_global_store_initialization() {
171        let temp_dir = TempDir::new().expect("Failed to create temp dir");
172        let store = GlobalStore::new(temp_dir.path().to_path_buf());
173
174        store.initialize().expect("Failed to initialize store");
175
176        // Verify all directories were created
177        assert!(store.resource_path(ResourceType::Template).exists());
178        assert!(store.resource_path(ResourceType::Standard).exists());
179        assert!(store.resource_path(ResourceType::Spec).exists());
180        assert!(store.resource_path(ResourceType::Steering).exists());
181        assert!(store.resource_path(ResourceType::Boilerplate).exists());
182        assert!(store.resource_path(ResourceType::Rule).exists());
183        assert!(temp_dir.path().join("cache").exists());
184    }
185
186    #[test]
187    fn test_store_and_retrieve_resource() {
188        let temp_dir = TempDir::new().expect("Failed to create temp dir");
189        let store = GlobalStore::new(temp_dir.path().to_path_buf());
190        store.initialize().expect("Failed to initialize store");
191
192        let content = b"test content";
193        let name = "test.txt";
194
195        // Store resource
196        let path = store
197            .store_resource(ResourceType::Template, name, content)
198            .expect("Failed to store resource");
199
200        assert!(path.exists());
201
202        // Retrieve resource
203        let retrieved = store
204            .retrieve_resource(ResourceType::Template, name)
205            .expect("Failed to retrieve resource");
206
207        assert_eq!(retrieved, content);
208    }
209
210    #[test]
211    fn test_list_resources() {
212        let temp_dir = TempDir::new().expect("Failed to create temp dir");
213        let store = GlobalStore::new(temp_dir.path().to_path_buf());
214        store.initialize().expect("Failed to initialize store");
215
216        // Store multiple resources
217        store
218            .store_resource(ResourceType::Template, "template1.txt", b"content1")
219            .expect("Failed to store");
220        store
221            .store_resource(ResourceType::Template, "template2.txt", b"content2")
222            .expect("Failed to store");
223
224        // List resources
225        let resources = store
226            .list_resources(ResourceType::Template)
227            .expect("Failed to list resources");
228
229        assert_eq!(resources.len(), 2);
230        assert!(resources.contains(&"template1.txt".to_string()));
231        assert!(resources.contains(&"template2.txt".to_string()));
232    }
233
234    #[test]
235    fn test_delete_resource() {
236        let temp_dir = TempDir::new().expect("Failed to create temp dir");
237        let store = GlobalStore::new(temp_dir.path().to_path_buf());
238        store.initialize().expect("Failed to initialize store");
239
240        let name = "test.txt";
241        store
242            .store_resource(ResourceType::Template, name, b"content")
243            .expect("Failed to store");
244
245        assert!(store.resource_exists(ResourceType::Template, name));
246
247        store
248            .delete_resource(ResourceType::Template, name)
249            .expect("Failed to delete");
250
251        assert!(!store.resource_exists(ResourceType::Template, name));
252    }
253
254    #[test]
255    fn test_resource_exists() {
256        let temp_dir = TempDir::new().expect("Failed to create temp dir");
257        let store = GlobalStore::new(temp_dir.path().to_path_buf());
258        store.initialize().expect("Failed to initialize store");
259
260        let name = "test.txt";
261        assert!(!store.resource_exists(ResourceType::Template, name));
262
263        store
264            .store_resource(ResourceType::Template, name, b"content")
265            .expect("Failed to store");
266
267        assert!(store.resource_exists(ResourceType::Template, name));
268    }
269}