use crate::error::{IoOperation, StorageError, StorageResult};
use crate::manager::PathResolver;
use crate::types::ResourceType;
use std::fs;
use std::path::{Path, PathBuf};
pub struct GlobalStore {
base_path: PathBuf,
}
impl GlobalStore {
pub fn new(base_path: PathBuf) -> Self {
GlobalStore { base_path }
}
pub fn with_default_path() -> StorageResult<Self> {
let base_path = PathResolver::resolve_global_path()?;
Ok(GlobalStore { base_path })
}
pub fn base_path(&self) -> &PathBuf {
&self.base_path
}
pub fn initialize(&self) -> StorageResult<()> {
self.create_dir_if_not_exists(&self.base_path)?;
for resource_type in &[
ResourceType::Template,
ResourceType::Standard,
ResourceType::Spec,
ResourceType::Steering,
ResourceType::Boilerplate,
ResourceType::Rule,
] {
let resource_path = self.resource_path(*resource_type);
self.create_dir_if_not_exists(&resource_path)?;
}
let cache_path = self.base_path.join("cache");
self.create_dir_if_not_exists(&cache_path)?;
Ok(())
}
pub fn resource_path(&self, resource_type: ResourceType) -> PathBuf {
self.base_path.join(resource_type.dir_name())
}
pub fn store_resource(
&self,
resource_type: ResourceType,
name: &str,
content: &[u8],
) -> StorageResult<PathBuf> {
let resource_dir = self.resource_path(resource_type);
let file_path = resource_dir.join(name);
self.create_dir_if_not_exists(&resource_dir)?;
fs::write(&file_path, content)
.map_err(|e| StorageError::io_error(file_path.clone(), IoOperation::Write, e))?;
Ok(file_path)
}
pub fn retrieve_resource(
&self,
resource_type: ResourceType,
name: &str,
) -> StorageResult<Vec<u8>> {
let resource_dir = self.resource_path(resource_type);
let file_path = resource_dir.join(name);
fs::read(&file_path).map_err(|e| StorageError::io_error(file_path, IoOperation::Read, e))
}
pub fn list_resources(&self, resource_type: ResourceType) -> StorageResult<Vec<String>> {
let resource_dir = self.resource_path(resource_type);
if !resource_dir.exists() {
return Ok(Vec::new());
}
let mut resources = Vec::new();
let entries = fs::read_dir(&resource_dir)
.map_err(|e| StorageError::io_error(resource_dir.clone(), IoOperation::Read, e))?;
for entry in entries {
let entry = entry
.map_err(|e| StorageError::io_error(resource_dir.clone(), IoOperation::Read, e))?;
let path = entry.path();
if path.is_file() {
if let Some(file_name) = path.file_name() {
if let Some(name_str) = file_name.to_str() {
resources.push(name_str.to_string());
}
}
}
}
Ok(resources)
}
pub fn delete_resource(&self, resource_type: ResourceType, name: &str) -> StorageResult<()> {
let resource_dir = self.resource_path(resource_type);
let file_path = resource_dir.join(name);
if file_path.exists() {
fs::remove_file(&file_path)
.map_err(|e| StorageError::io_error(file_path, IoOperation::Delete, e))?;
}
Ok(())
}
pub fn resource_exists(&self, resource_type: ResourceType, name: &str) -> bool {
let resource_dir = self.resource_path(resource_type);
let file_path = resource_dir.join(name);
file_path.exists()
}
fn create_dir_if_not_exists(&self, path: &Path) -> StorageResult<()> {
if !path.exists() {
fs::create_dir_all(path)
.map_err(|e| StorageError::directory_creation_failed(path.to_path_buf(), e))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_global_store_initialization() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let store = GlobalStore::new(temp_dir.path().to_path_buf());
store.initialize().expect("Failed to initialize store");
assert!(store.resource_path(ResourceType::Template).exists());
assert!(store.resource_path(ResourceType::Standard).exists());
assert!(store.resource_path(ResourceType::Spec).exists());
assert!(store.resource_path(ResourceType::Steering).exists());
assert!(store.resource_path(ResourceType::Boilerplate).exists());
assert!(store.resource_path(ResourceType::Rule).exists());
assert!(temp_dir.path().join("cache").exists());
}
#[test]
fn test_store_and_retrieve_resource() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let store = GlobalStore::new(temp_dir.path().to_path_buf());
store.initialize().expect("Failed to initialize store");
let content = b"test content";
let name = "test.txt";
let path = store
.store_resource(ResourceType::Template, name, content)
.expect("Failed to store resource");
assert!(path.exists());
let retrieved = store
.retrieve_resource(ResourceType::Template, name)
.expect("Failed to retrieve resource");
assert_eq!(retrieved, content);
}
#[test]
fn test_list_resources() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let store = GlobalStore::new(temp_dir.path().to_path_buf());
store.initialize().expect("Failed to initialize store");
store
.store_resource(ResourceType::Template, "template1.txt", b"content1")
.expect("Failed to store");
store
.store_resource(ResourceType::Template, "template2.txt", b"content2")
.expect("Failed to store");
let resources = store
.list_resources(ResourceType::Template)
.expect("Failed to list resources");
assert_eq!(resources.len(), 2);
assert!(resources.contains(&"template1.txt".to_string()));
assert!(resources.contains(&"template2.txt".to_string()));
}
#[test]
fn test_delete_resource() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let store = GlobalStore::new(temp_dir.path().to_path_buf());
store.initialize().expect("Failed to initialize store");
let name = "test.txt";
store
.store_resource(ResourceType::Template, name, b"content")
.expect("Failed to store");
assert!(store.resource_exists(ResourceType::Template, name));
store
.delete_resource(ResourceType::Template, name)
.expect("Failed to delete");
assert!(!store.resource_exists(ResourceType::Template, name));
}
#[test]
fn test_resource_exists() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let store = GlobalStore::new(temp_dir.path().to_path_buf());
store.initialize().expect("Failed to initialize store");
let name = "test.txt";
assert!(!store.resource_exists(ResourceType::Template, name));
store
.store_resource(ResourceType::Template, name, b"content")
.expect("Failed to store");
assert!(store.resource_exists(ResourceType::Template, name));
}
}