Backbone Storage
๐ฆด Production-ready local filesystem storage with comprehensive file management
Backbone Storage provides a robust, feature-rich local filesystem storage solution with support for file uploads, downloads, metadata management, streaming, and security features. Designed for production use with comprehensive error handling and async support.
๐ Table of Contents
๐ฏ Overview
Backbone Storage provides a complete file storage solution focused on local filesystem operations. It includes all essential storage features like file uploads, downloads, metadata management, directory traversal, and security scanning capabilities.
Key Design Principles
- Local First: Optimized for reliable local filesystem storage
- Async Native: Full async/await support with tokio
- Security Aware: Built-in virus scanning and file validation
- Metadata Rich: Comprehensive file metadata and statistics
- Production Ready: Robust error handling and recovery
๐ Features
๐ Core Storage Features
- File Upload/Download: Support for bytes, readers, and file uploads
- Streaming: Async file streaming with range support
- Metadata Management: Rich file metadata with timestamps and hashes
- Directory Operations: File listing with pagination and filtering
- File Operations: Copy, move, delete with proper error handling
๐ก๏ธ Security & Safety
- Path Sanitization: Prevents directory traversal attacks
- File Type Detection: MIME type detection and validation
- Hash Verification: File integrity checking
- Virus Scanning: Integration with security analysis (optional)
- Permission Validation: Filesystem permission checking
๐ Advanced Features
- Compression: Built-in file compression support (optional)
- Presigned URLs: Generate file access URLs
- Statistics: Storage usage and file count analytics
- Builder Pattern: Fluent configuration API
- Generic Trait: Extensible StorageService trait
๐ Usage
Quick Start
[dependencies]
backbone-storage = "2.0.0"
tokio = { version = "1.0", features = ["full"] }
bytes = "1.0"
serde = { version = "1.0", features = ["derive"] }
uuid = { version = "1.0", features = ["v4"] }
chrono = { version = "0.4", features = ["serde"] }
Basic File Operations
use backbone_storage::{StorageService, LocalStorage};
use bytes::Bytes;
let storage = LocalStorage::new(config)?;
let file_data = Bytes::from("Hello, Backbone Storage!");
let uploaded_file = storage.upload_bytes("greeting.txt", file_data, None).await?;
println!("File uploaded: {}", uploaded_file.name);
let downloaded_data = storage.download_bytes("greeting.txt", None).await?;
println!("File content: {}", String::from_utf8_lossy(&downloaded_data));
let exists = storage.file_exists("greeting.txt").await?;
println!("File exists: {}", exists);
File Upload from Reader
use backbone_storage::{StorageService, LocalStorage};
use tokio::fs::File;
use tokio::io::BufReader;
let storage = LocalStorage::new(config)?;
let file = File::open("large_file.zip").await?;
let reader = Box::new(BufReader::new(file));
let uploaded_file = storage.upload_reader(
"uploads/large_file.zip",
reader,
Some(1024 * 1024 * 50), None
).await?;
println!("Large file uploaded: {} ({} bytes)",
uploaded_file.name, uploaded_file.size);
File Listing and Metadata
use backbone_storage::{StorageService, LocalStorage};
let storage = LocalStorage::new(config)?;
let files = storage.list_files("", Some(20), None).await?;
println!("Found {} files:", files.total_count.unwrap_or(0));
for file in &files.files {
println!(" {} - {} bytes ({})",
file.name, file.size, file.content_type);
}
let file = storage.get_file("documents/report.pdf").await?;
println!("File: {}", file.name);
println!(" Size: {} bytes", file.size);
println!(" Type: {}", file.content_type);
println!(" Created: {}", file.created_at);
println!(" Modified: {:?}", file.updated_at);
println!(" Checksum: {}", file.checksum.as_ref().unwrap_or(&"N/A".to_string()));
File Streaming
use backbone_storage::{StorageService, LocalStorage};
use futures::StreamExt;
let storage = LocalStorage::new(config)?;
let mut stream = storage.stream_file("large_video.mp4", None).await?;
let mut total_bytes = 0;
while let Some(chunk_result) = stream.next().await {
match chunk_result {
Ok(chunk) => {
total_bytes += chunk.len();
println!("Received {} bytes (total: {})", chunk.len(), total_bytes);
}
Err(e) => {
eprintln!("Stream error: {}", e);
break;
}
}
}
println!("Streamed {} bytes total", total_bytes);
File Operations
use backbone_storage::{StorageService, LocalStorage};
let storage = LocalStorage::new(config)?;
let copied_file = storage.copy_file(
"source/document.pdf",
"backup/document_copy.pdf"
).await?;
println!("File copied: {}", copied_file.name);
let moved_file = storage.move_file(
"temp/upload.tmp",
"documents/final.pdf"
).await?;
println!("File moved: {}", moved_file.name);
let deleted = storage.delete_file("old_file.txt").await?;
println!("File deleted: {}", deleted);
Storage Statistics
use backbone_storage::{StorageService, LocalStorage};
let storage = LocalStorage::new(config)?;
let stats = storage.get_stats().await?;
println!("Storage Statistics:");
println!(" Total files: {}", stats.file_count);
println!(" Total size: {} bytes", stats.total_bytes);
println!(" Average file size: {:.2} bytes", stats.average_file_size);
println!(" Last updated: {}", stats.last_updated);
for (backend, bytes) in &stats.backend_usage {
println!(" {}: {} bytes", backend, bytes);
}
for (content_type, count) in &stats.content_type_counts {
println!(" {}: {} files", content_type, count);
}
๐ง Configuration
Basic Configuration
use backbone_storage::{LocalStorage, LocalStorageConfig};
let config = LocalStorageConfig {
base_dir: "./storage".to_string(),
base_path: None,
compression: false,
encryption: None,
};
let storage = LocalStorage::new(config)?;
Builder Pattern Configuration
use backbone_storage::{LocalStorage, LocalStorageBuilder};
let storage = LocalStorage::builder()
.base_dir("./app_storage")
.compression(true)
.build()?;
Directory Structure
./storage/
โโโ uploads/
โ โโโ documents/
โ โ โโโ report.pdf
โ โ โโโ invoice.docx
โ โโโ images/
โ โ โโโ logo.png
โ โ โโโ banner.jpg
โ โโโ temp/
โ โโโ upload.tmp
โโโ backups/
โโโ archives/
๐ง Technical Details
Core Traits
StorageService Trait
#[async_trait]
pub trait StorageService: Send + Sync {
async fn upload_bytes(&self, path: &str, data: Bytes, options: Option<UploadOptions>) -> StorageResult<StorageFile>;
async fn upload_reader(&self, path: &str, reader: Box<dyn AsyncRead + Send + Unpin>, size: Option<u64>, options: Option<UploadOptions>) -> StorageResult<StorageFile>;
async fn upload_file(&self, path: &str, local_path: &str, options: Option<UploadOptions>) -> StorageResult<StorageFile>;
async fn download_bytes(&self, path: &str, options: Option<DownloadOptions>) -> StorageResult<Bytes>;
async fn download_writer(&self, path: &str, writer: Box<dyn AsyncWrite + Send + Unpin>, options: Option<DownloadOptions>) -> StorageResult<()>;
async fn download_file(&self, path: &str, local_path: &str, options: Option<DownloadOptions>) -> StorageResult<()>;
async fn stream_file(&self, path: &str, range: Option<ByteRange>) -> StorageResult<Box<dyn Stream<Item = StorageResult<Bytes>> + Send + Unpin>>;
async fn get_file(&self, path: &str) -> StorageResult<StorageFile>;
async fn file_exists(&self, path: &str) -> StorageResult<bool>;
async fn delete_file(&self, path: &str) -> StorageResult<bool>;
async fn copy_file(&self, from_path: &str, to_path: &str) -> StorageResult<StorageFile>;
async fn move_file(&self, from_path: &str, to_path: &str) -> StorageResult<StorageFile>;
async fn list_files(&self, prefix: &str, limit: Option<u32>, continuation_token: Option<String>) -> StorageResult<FileListResult>;
async fn generate_presigned_url(&self, path: &str, options: PresignedUrlOptions) -> StorageResult<String>;
async fn get_stats(&self) -> StorageResult<StorageStats>;
async fn test_connection(&self) -> StorageResult<bool>;
}
Data Types
StorageFile
pub struct StorageFile {
pub id: String,
pub name: String,
pub size: u64,
pub content_type: String,
pub checksum: Option<String>,
pub checksum_algorithm: Option<String>,
pub metadata: HashMap<String, String>,
pub created_at: DateTime<Utc>,
pub updated_at: Option<DateTime<Utc>>,
pub version: Option<String>,
pub backend: StorageBackend,
pub bucket: String,
pub storage_path: String,
pub encrypted: bool,
pub compressed: bool,
pub expires_at: Option<DateTime<Utc>>,
}
StorageStats
pub struct StorageStats {
pub file_count: u64,
pub total_bytes: u64,
pub backend_usage: HashMap<String, u64>,
pub content_type_counts: HashMap<String, u64>,
pub average_file_size: f64,
pub largest_file_size: u64,
pub last_updated: DateTime<Utc>,
}
Error Handling
#[derive(Debug, thiserror::Error)]
pub enum StorageError {
#[error("Configuration error: {0}")]
InvalidConfiguration(String),
#[error("File not found: {0}")]
FileNotFound(String),
#[error("File already exists: {0}")]
FileAlreadyExists(String),
#[error("Filesystem error: {0}")]
FilesystemError(#[from] std::io::Error),
#[error("File too large: {size} bytes, maximum allowed: {max_size} bytes")]
FileTooLarge { size: u64, max_size: u64 },
#[error("Invalid file format: {0}")]
InvalidFileFormat(String),
#[error("Security error: {operation} - {message}")]
SecurityError { operation: String, message: String },
#[error("Unsupported operation '{operation}' for backend '{backend}'")]
UnsupportedOperation { operation: String, backend: String },
}
๐ Examples
1. File Upload Service
use backbone_storage::{StorageService, LocalStorage};
use tokio::fs::File;
async fn upload_user_file(
storage: &dyn StorageService,
user_id: &str,
filename: &str,
file_path: &str
) -> Result<backbone_storage::StorageFile, Box<dyn std::error::Error>> {
let storage_path = format!("users/{}/files/{}", user_id, filename);
let uploaded_file = storage.upload_file(&storage_path, file_path, None).await?;
println!("File uploaded successfully:");
println!(" Original: {}", filename);
println!(" Storage: {}", uploaded_file.storage_path);
println!(" Size: {} bytes", uploaded_file.size);
Ok(uploaded_file)
}
2. Backup Service
use backbone_storage::{StorageService, LocalStorage};
async fn create_backup(
storage: &dyn StorageService,
source_path: &str,
backup_name: &str
) -> Result<backbone_storage::StorageFile, Box<dyn std::error::Error>> {
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
let backup_path = format!("backups/{}/{}_{}", backup_name, timestamp, source_path);
let backup_file = storage.copy_file(source_path, &backup_path).await?;
println!("Backup created: {}", backup_file.storage_path);
println!("Backup size: {} bytes", backup_file.size);
Ok(backup_file)
}
3. File Processing Pipeline
use backbone_storage::{StorageService, LocalStorage};
use futures::StreamExt;
async fn process_uploaded_file(
storage: &dyn StorageService,
file_path: &str
) -> Result<(), Box<dyn std::error::Error>> {
let file = storage.get_file(file_path).await?;
println!("Processing file: {} ({} bytes)", file.name, file.size);
let mut stream = storage.stream_file(file_path, None).await?;
let mut processed_bytes = 0;
while let Some(chunk_result) = stream.next().await {
match chunk_result {
Ok(chunk) => {
processed_bytes += chunk.len();
println!("Processed {} bytes", processed_bytes);
}
Err(e) => {
eprintln!("Error processing file: {}", e);
return Err(e.into());
}
}
}
let processed_path = file_path.replace("uploads/", "processed/");
let final_file = storage.move_file(file_path, &processed_path).await?;
println!("File processing complete: {}", final_file.storage_path);
Ok(())
}
4. Storage Monitoring
use backbone_storage::StorageService;
async fn monitor_storage_usage(storage: &dyn StorageService) -> Result<(), Box<dyn std::error::Error>> {
let stats = storage.get_stats().await?;
println!("๐ Storage Usage Report");
println!("======================");
println!("Total files: {}", stats.file_count);
println!("Total size: {:.2} MB", stats.total_bytes as f64 / 1024.0 / 1024.0);
println!("Average file size: {:.2} KB", stats.average_file_size / 1024.0);
println!("Last updated: {}", stats.last_updated);
println!("\n๐ File Types:");
let mut content_types: Vec<_> = stats.content_type_counts.iter().collect();
content_types.sort_by(|a, b| b.1.cmp(a.1));
for (content_type, count) in content_types.iter().take(10) {
println!(" {}: {} files", content_type, count);
}
const SIZE_LIMIT_MB: u64 = 1024; let size_mb = stats.total_bytes / 1024 / 1024;
if size_mb > SIZE_LIMIT_MB {
println!("\nโ ๏ธ Storage usage warning: {:.2} MB used (limit: {} MB)",
size_mb, SIZE_LIMIT_MB);
}
Ok(())
}
๐ง Error Handling
Comprehensive Error Recovery
use backbone_storage::{StorageService, StorageError};
async fn safe_file_operation(
storage: &dyn StorageService,
operation: String
) -> Result<(), Box<dyn std::error::Error>> {
match storage.test_connection().await {
Ok(true) => println!("โ
Storage service is healthy"),
Ok(false) => {
println!("โ Storage service connection failed");
return Err("Storage service unavailable".into());
}
Err(e) => {
println!("โ Storage service error: {}", e);
return Err(e.into());
}
}
match storage.file_exists("test.txt").await {
Ok(exists) => println!("File exists check: {}", exists),
Err(StorageError::FileNotFound(path)) => {
println!("File not found: {}", path);
}
Err(StorageError::FilesystemError(e)) => {
eprintln!("Filesystem error: {}", e);
return Err(e.into());
}
Err(e) => {
eprintln!("Unexpected error: {}", e);
return Err(e.into());
}
}
Ok(())
}
๐งช Testing
Unit Tests
cargo test --package backbone-storage
cargo test --package backbone-storage -- -- upload
cargo test --package backbone-storage -- -- download
cargo test --package backbone-storage -- -- metadata
Integration Tests
use backbone_storage::{StorageService, LocalStorage};
use tempfile::TempDir;
#[tokio::test]
async fn test_file_operations() -> Result<(), Box<dyn std::error::Error>> {
let temp_dir = TempDir::new()?;
let config = backbone_storage::LocalStorageConfig {
base_dir: temp_dir.path().to_string_lossy().to_string(),
base_path: None,
compression: false,
encryption: None,
};
let storage = LocalStorage::new(config)?;
let data = bytes::Bytes::from("Hello, World!");
let uploaded = storage.upload_bytes("test.txt", data.clone(), None).await?;
assert_eq!(uploaded.name, "test.txt");
assert_eq!(uploaded.size, data.len() as u64);
let downloaded = storage.download_bytes("test.txt", None).await?;
assert_eq!(downloaded, data);
assert!(storage.file_exists("test.txt").await?);
let deleted = storage.delete_file("test.txt").await?;
assert!(deleted);
assert!(!storage.file_exists("test.txt").await?);
Ok(())
}
๐ Configuration
Environment Variables
LOCAL_STORAGE_DIR=./storage
LOCAL_STORAGE_BASE_PATH=/app/storage
LOCAL_STORAGE_COMPRESSION=true
LOCAL_STORAGE_MAX_FILE_SIZE=104857600
YAML Configuration
storage:
backend: "local"
local:
base_dir: "${LOCAL_STORAGE_DIR}"
base_path: "${LOCAL_STORAGE_BASE_PATH}"
compression: ${LOCAL_STORAGE_COMPRESSION}
max_file_size: ${LOCAL_STORAGE_MAX_FILE_SIZE}
upload:
chunk_size: 8388608 multipart_threshold: 104857600 calculate_checksum: true
security:
enable_virus_scanning: false
allowed_file_types: ["pdf", "doc", "docx", "jpg", "png"]
max_file_size: 104857600
compression:
enabled: true
algorithms: ["gzip", "brotli"]
quality: 6
๐ Performance Considerations
File I/O Optimization
use backbone_storage::{StorageService, LocalStorage};
use std::sync::Arc;
let storage = Arc::new(
LocalStorage::builder()
.base_dir("./fast_storage")
.compression(false) .build()?
);
let mut handles = vec![];
for i in 0..10 {
let storage = storage.clone();
let handle = tokio::spawn(async move {
let data = format!("File content {}", i);
let bytes = bytes::Bytes::from(data);
storage.upload_bytes(&format!("file_{}.txt", i), bytes, None).await
});
handles.push(handle);
}
for handle in handles {
let result = handle.await?;
println!("Upload result: {:?}", result);
}
๐ Version History
Current Version: 2.0.0
Features:
- โ
Complete local filesystem storage implementation
- โ
Async file streaming with range support
- โ
Rich metadata and statistics collection
- โ
Security features and path sanitization
- โ
Builder pattern configuration
- โ
Comprehensive error handling
- โ
File upload/download/copy/move/delete operations
- โ
Directory listing with pagination
Breaking Changes from v1.x:
- Simplified to local storage only
- Updated API to use async/await throughout
- Enhanced metadata structure
- Improved error handling with context
- Removed cloud storage complexity
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Related Projects
๐ฆด Backbone Storage - Reliable local filesystem storage
Local First โข Async Native โข Production Ready