use std::io::{Read, Seek, Write};
use std::sync::Arc;
use crate::error::{LaurusError, Result};
pub mod column;
pub mod file;
pub mod memory;
pub mod prefixed;
pub mod structured;
#[derive(Debug, Clone)]
pub struct FileMetadata {
pub size: u64,
pub modified: u64,
pub created: u64,
pub readonly: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoadingMode {
Eager,
Lazy,
}
pub trait Storage: Send + Sync + std::fmt::Debug {
fn loading_mode(&self) -> LoadingMode {
LoadingMode::Eager
}
fn open_input(&self, name: &str) -> Result<Box<dyn StorageInput>>;
fn create_output(&self, name: &str) -> Result<Box<dyn StorageOutput>>;
fn create_output_append(&self, name: &str) -> Result<Box<dyn StorageOutput>>;
fn file_exists(&self, name: &str) -> bool;
fn delete_file(&self, name: &str) -> Result<()>;
fn list_files(&self) -> Result<Vec<String>>;
fn file_size(&self, name: &str) -> Result<u64>;
fn metadata(&self, name: &str) -> Result<FileMetadata>;
fn rename_file(&self, old_name: &str, new_name: &str) -> Result<()>;
fn create_temp_output(&self, prefix: &str) -> Result<(String, Box<dyn StorageOutput>)>;
fn sync(&self) -> Result<()>;
fn close(&mut self) -> Result<()>;
}
pub trait StorageInput: Read + Seek + Send + Sync + std::fmt::Debug {
fn size(&self) -> Result<u64>;
fn clone_input(&self) -> Result<Box<dyn StorageInput>>;
fn close(&mut self) -> Result<()>;
}
pub trait StorageOutput: Write + Seek + Send + std::fmt::Debug {
fn flush_and_sync(&mut self) -> Result<()>;
fn position(&self) -> Result<u64>;
fn close(&mut self) -> Result<()>;
}
impl StorageOutput for Box<dyn StorageOutput> {
fn flush_and_sync(&mut self) -> Result<()> {
self.as_mut().flush_and_sync()
}
fn position(&self) -> Result<u64> {
self.as_ref().position()
}
fn close(&mut self) -> Result<()> {
self.as_mut().close()
}
}
impl StorageInput for Box<dyn StorageInput> {
fn size(&self) -> Result<u64> {
self.as_ref().size()
}
fn clone_input(&self) -> Result<Box<dyn StorageInput>> {
self.as_ref().clone_input()
}
fn close(&mut self) -> Result<()> {
self.as_mut().close()
}
}
pub trait LockManager: Send + Sync + std::fmt::Debug {
fn acquire_lock(&self, name: &str) -> Result<Box<dyn StorageLock>>;
fn try_acquire_lock(&self, name: &str) -> Result<Option<Box<dyn StorageLock>>>;
fn lock_exists(&self, name: &str) -> bool;
fn release_all(&self) -> Result<()>;
}
pub trait StorageLock: Send + std::fmt::Debug {
fn name(&self) -> &str;
fn release(&mut self) -> Result<()>;
fn is_valid(&self) -> bool;
}
#[derive(Debug, Clone)]
pub enum StorageConfig {
File(file::FileStorageConfig),
Memory(memory::MemoryStorageConfig),
}
impl Default for StorageConfig {
fn default() -> Self {
StorageConfig::Memory(memory::MemoryStorageConfig::default())
}
}
pub struct StorageFactory;
impl StorageFactory {
pub fn create(config: StorageConfig) -> Result<Arc<dyn Storage>> {
match config {
StorageConfig::Memory(mem_config) => {
let storage = memory::MemoryStorage::new(mem_config);
Ok(Arc::new(storage))
}
StorageConfig::File(file_config) => {
let path = file_config.path.clone();
let storage = file::FileStorage::new(&path, file_config)?;
Ok(Arc::new(storage))
}
}
}
pub fn open(config: StorageConfig) -> Result<Arc<dyn Storage>> {
Self::create(config)
}
}
#[derive(Debug, Clone)]
pub enum StorageError {
FileNotFound(String),
FileExists(String),
PermissionDenied(String),
IoError(String),
LockFailed(String),
StorageClosed,
InvalidOperation(String),
}
impl std::fmt::Display for StorageError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StorageError::FileNotFound(name) => write!(f, "File not found: {name}"),
StorageError::FileExists(name) => write!(f, "File already exists: {name}"),
StorageError::PermissionDenied(name) => write!(f, "Permission denied: {name}"),
StorageError::IoError(msg) => write!(f, "I/O error: {msg}"),
StorageError::LockFailed(name) => write!(f, "Failed to acquire lock: {name}"),
StorageError::StorageClosed => write!(f, "Storage is closed"),
StorageError::InvalidOperation(msg) => write!(f, "Invalid operation: {msg}"),
}
}
}
impl std::error::Error for StorageError {}
impl From<StorageError> for LaurusError {
fn from(err: StorageError) -> Self {
LaurusError::storage(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::file::FileStorageConfig;
use crate::storage::memory::MemoryStorageConfig;
#[test]
fn test_storage_config_default() {
let config = StorageConfig::default();
match config {
StorageConfig::Memory(mem_config) => {
assert_eq!(mem_config.initial_capacity, 16);
}
_ => panic!("Expected Memory config"),
}
}
#[test]
fn test_file_storage_config() {
let config = FileStorageConfig::new("/tmp/test");
assert_eq!(config.path, std::path::PathBuf::from("/tmp/test"));
assert!(!config.use_mmap);
assert_eq!(config.buffer_size, 65536);
assert!(!config.sync_writes);
assert!(config.use_locking);
assert!(config.temp_dir.is_none());
}
#[test]
fn test_storage_error_display() {
let err = StorageError::FileNotFound("test.txt".to_string());
assert_eq!(err.to_string(), "File not found: test.txt");
let err = StorageError::FileExists("test.txt".to_string());
assert_eq!(err.to_string(), "File already exists: test.txt");
let err = StorageError::PermissionDenied("test.txt".to_string());
assert_eq!(err.to_string(), "Permission denied: test.txt");
let err = StorageError::IoError("connection failed".to_string());
assert_eq!(err.to_string(), "I/O error: connection failed");
let err = StorageError::LockFailed("write.lock".to_string());
assert_eq!(err.to_string(), "Failed to acquire lock: write.lock");
let err = StorageError::StorageClosed;
assert_eq!(err.to_string(), "Storage is closed");
let err = StorageError::InvalidOperation("cannot write to read-only storage".to_string());
assert_eq!(
err.to_string(),
"Invalid operation: cannot write to read-only storage"
);
}
#[test]
fn test_file_metadata() {
let metadata = FileMetadata {
size: 1024,
modified: 1234567890,
created: 1234567890,
readonly: false,
};
assert_eq!(metadata.size, 1024);
assert_eq!(metadata.modified, 1234567890);
assert_eq!(metadata.created, 1234567890);
assert!(!metadata.readonly);
}
#[test]
fn test_storage_factory_memory() {
let config = StorageConfig::Memory(MemoryStorageConfig::default());
let storage = StorageFactory::create(config).unwrap();
assert!(!storage.file_exists("test.txt"));
}
#[test]
fn test_storage_factory_file() {
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let file_config = FileStorageConfig::new(temp_dir.path());
let config = StorageConfig::File(file_config);
let storage = StorageFactory::create(config).unwrap();
assert!(!storage.file_exists("test.txt"));
}
#[test]
fn test_storage_factory_with_mmap() {
use std::io::Write;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let mut file_config = FileStorageConfig::new(temp_dir.path());
file_config.use_mmap = true;
let config = StorageConfig::File(file_config);
let storage = StorageFactory::create(config).unwrap();
let mut output = storage.create_output("test.txt").unwrap();
output.write_all(b"Hello, Factory!").unwrap();
output.close().unwrap();
let mut input = storage.open_input("test.txt").unwrap();
let mut buffer = Vec::new();
input.read_to_end(&mut buffer).unwrap();
assert_eq!(buffer, b"Hello, Factory!");
}
}