use crate::backend::{LocalStorageBackend, StorageBackend};
use crate::config::FileConfig;
use crate::Result;
use std::path::{Path, PathBuf};
pub struct FileStorage {
backend: Box<dyn StorageBackend>,
#[allow(dead_code)]
config: FileConfig,
}
#[derive(Debug, Clone, Default)]
pub struct StorageStats {
pub total_files: usize,
pub total_size: u64,
pub total_refs: usize,
}
impl FileStorage {
pub fn new(config: FileConfig) -> Self {
let data_dir = config.data_dir();
let backend = Box::new(LocalStorageBackend::new(data_dir));
Self { backend, config }
}
pub fn with_backend(config: FileConfig, backend: Box<dyn StorageBackend>) -> Self {
Self { backend, config }
}
pub async fn initialize(&self) -> Result<()> {
self.backend.initialize().await
}
pub async fn store_data(&self, hash: &str, data: &[u8]) -> Result<PathBuf> {
self.backend.write(hash, data).await
}
pub async fn read_data(&self, relative_path: &Path) -> Result<Vec<u8>> {
self.backend.read(relative_path).await
}
pub async fn delete_data(&self, relative_path: &Path) -> Result<()> {
self.backend.delete(relative_path).await
}
pub async fn exists(&self, hash: &str) -> bool {
self.backend.exists(hash).await
}
pub fn full_path(&self, relative_path: &Path) -> PathBuf {
self.backend.full_path(relative_path)
}
pub async fn stats(&self) -> Result<StorageStats> {
let backend_stats = self.backend.stats().await?;
Ok(StorageStats {
total_files: backend_stats.total_objects,
total_size: backend_stats.total_size,
total_refs: 0, })
}
pub fn backend(&self) -> &dyn StorageBackend {
self.backend.as_ref()
}
}
pub fn hash_to_path(hash: &str) -> PathBuf {
if hash.len() < 4 {
return PathBuf::from(hash);
}
let prefix = &hash[0..2];
let rest = &hash[2..];
PathBuf::from(prefix).join(rest)
}
pub fn compute_hash(data: &[u8]) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(data);
hex::encode(hasher.finalize())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_hash_to_path() {
assert_eq!(hash_to_path("abcdef123456"), PathBuf::from("ab/cdef123456"));
assert_eq!(hash_to_path("ab"), PathBuf::from("ab"));
assert_eq!(hash_to_path("a"), PathBuf::from("a"));
}
#[test]
fn test_compute_hash() {
let data = b"hello world";
let hash = compute_hash(data);
assert_eq!(hash.len(), 64); assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
}
#[tokio::test]
async fn test_file_storage_with_backend() {
let temp_dir = TempDir::new().unwrap();
let config = FileConfig::with_path(temp_dir.path().to_path_buf());
let storage = FileStorage::new(config);
storage.initialize().await.unwrap();
let data = b"test content";
let hash = compute_hash(data);
let path = storage.store_data(&hash, data).await.unwrap();
assert!(storage.exists(&hash).await);
let read_data = storage.read_data(&path).await.unwrap();
assert_eq!(read_data, data);
}
#[tokio::test]
async fn test_file_storage_with_custom_backend() {
let temp_dir = TempDir::new().unwrap();
let config = FileConfig::with_path(temp_dir.path().to_path_buf());
let custom_backend = LocalStorageBackend::new(temp_dir.path().join("custom"));
let storage = FileStorage::with_backend(config, Box::new(custom_backend));
storage.initialize().await.unwrap();
let data = b"custom backend test";
let hash = compute_hash(data);
let path = storage.store_data(&hash, data).await.unwrap();
assert!(storage.exists(&hash).await);
let read_data = storage.read_data(&path).await.unwrap();
assert_eq!(read_data, data);
}
}