use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::RwLock;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct BackupManager {
backups: Arc<RwLock<HashMap<String, Backup>>>,
config: BackupConfig,
initialized: bool,
}
impl BackupManager {
pub fn new() -> Self {
Self {
backups: Arc::new(RwLock::new(HashMap::new())),
config: BackupConfig::default(),
initialized: false,
}
}
pub fn with_config(config: BackupConfig) -> Self {
Self {
backups: Arc::new(RwLock::new(HashMap::new())),
config,
initialized: false,
}
}
pub async fn initialize(&mut self) -> Result<(), BackupError> {
self.initialized = true;
Ok(())
}
pub async fn shutdown(&mut self) -> Result<(), BackupError> {
self.initialized = false;
Ok(())
}
pub fn is_initialized(&self) -> bool {
self.initialized
}
pub async fn create_backup(&self, data: &[u8], metadata: &BackupMetadata) -> Result<BackupResult, BackupError> {
if !self.initialized {
return Err(BackupError::NotInitialized);
}
let backup_id = format!("backup_{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
let backup = Backup {
id: backup_id.clone(),
data: data.to_vec(),
metadata: metadata.clone(),
created_at: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
size: data.len(),
checksum: self.calculate_checksum(data).await?,
};
let mut backups = self.backups.write().await;
backups.insert(backup_id.clone(), backup);
Ok(BackupResult {
backup_id,
size: data.len(),
created_at: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
})
}
pub async fn restore_backup(&self, backup_id: &str) -> Result<RestoreResult, BackupError> {
if !self.initialized {
return Err(BackupError::NotInitialized);
}
let backups = self.backups.read().await;
let backup = backups.get(backup_id)
.ok_or_else(|| BackupError::BackupNotFound(backup_id.to_string()))?;
let calculated_checksum = self.calculate_checksum(&backup.data).await?;
if calculated_checksum != backup.checksum {
return Err(BackupError::BackupCorrupted(backup_id.to_string()));
}
Ok(RestoreResult {
backup_id: backup_id.to_string(),
data: backup.data.clone(),
metadata: backup.metadata.clone(),
restored_at: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
})
}
pub async fn list_backups(&self) -> Result<Vec<BackupInfo>, BackupError> {
if !self.initialized {
return Err(BackupError::NotInitialized);
}
let backups = self.backups.read().await;
let backup_infos: Vec<BackupInfo> = backups.values()
.map(|backup| BackupInfo {
id: backup.id.clone(),
size: backup.size,
created_at: backup.created_at,
metadata: backup.metadata.clone(),
})
.collect();
Ok(backup_infos)
}
pub async fn delete_backup(&self, backup_id: &str) -> Result<(), BackupError> {
if !self.initialized {
return Err(BackupError::NotInitialized);
}
let mut backups = self.backups.write().await;
backups.remove(backup_id)
.ok_or_else(|| BackupError::BackupNotFound(backup_id.to_string()))?;
Ok(())
}
pub async fn get_backup_info(&self, backup_id: &str) -> Result<BackupInfo, BackupError> {
if !self.initialized {
return Err(BackupError::NotInitialized);
}
let backups = self.backups.read().await;
let backup = backups.get(backup_id)
.ok_or_else(|| BackupError::BackupNotFound(backup_id.to_string()))?;
Ok(BackupInfo {
id: backup.id.clone(),
size: backup.size,
created_at: backup.created_at,
metadata: backup.metadata.clone(),
})
}
pub async fn verify_backup(&self, backup_id: &str) -> Result<bool, BackupError> {
if !self.initialized {
return Err(BackupError::NotInitialized);
}
let backups = self.backups.read().await;
let backup = backups.get(backup_id)
.ok_or_else(|| BackupError::BackupNotFound(backup_id.to_string()))?;
let calculated_checksum = self.calculate_checksum(&backup.data).await?;
Ok(calculated_checksum == backup.checksum)
}
async fn calculate_checksum(&self, data: &[u8]) -> Result<String, BackupError> {
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
hasher.update(data);
let result = hasher.finalize();
Ok(format!("{:x}", result))
}
}
#[derive(Debug, Clone)]
pub struct RestoreManager {
restores: Arc<RwLock<HashMap<String, RestoreOperation>>>,
initialized: bool,
}
impl RestoreManager {
pub fn new() -> Self {
Self {
restores: Arc::new(RwLock::new(HashMap::new())),
initialized: false,
}
}
pub async fn initialize(&mut self) -> Result<(), BackupError> {
self.initialized = true;
Ok(())
}
pub async fn shutdown(&mut self) -> Result<(), BackupError> {
self.initialized = false;
Ok(())
}
pub fn is_initialized(&self) -> bool {
self.initialized
}
pub async fn start_restore(&self, backup_id: &str, strategy: RestoreStrategy) -> Result<String, BackupError> {
if !self.initialized {
return Err(BackupError::NotInitialized);
}
let restore_id = format!("restore_{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
let restore_operation = RestoreOperation {
id: restore_id.clone(),
backup_id: backup_id.to_string(),
strategy,
status: RestoreStatus::InProgress,
started_at: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
completed_at: None,
error: None,
};
let mut restores = self.restores.write().await;
restores.insert(restore_id.clone(), restore_operation);
Ok(restore_id)
}
pub async fn get_restore_status(&self, restore_id: &str) -> Result<RestoreOperation, BackupError> {
if !self.initialized {
return Err(BackupError::NotInitialized);
}
let restores = self.restores.read().await;
let restore = restores.get(restore_id)
.ok_or_else(|| BackupError::RestoreNotFound(restore_id.to_string()))?;
Ok(restore.clone())
}
pub async fn complete_restore(&self, restore_id: &str, success: bool, error: Option<String>) -> Result<(), BackupError> {
if !self.initialized {
return Err(BackupError::NotInitialized);
}
let mut restores = self.restores.write().await;
let restore = restores.get_mut(restore_id)
.ok_or_else(|| BackupError::RestoreNotFound(restore_id.to_string()))?;
restore.status = if success {
RestoreStatus::Completed
} else {
RestoreStatus::Failed
};
restore.completed_at = Some(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
restore.error = error;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Backup {
pub id: String,
pub data: Vec<u8>,
pub metadata: BackupMetadata,
pub created_at: u64,
pub size: usize,
pub checksum: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BackupMetadata {
pub name: String,
pub description: String,
pub backup_type: BackupType,
pub source: String,
pub tags: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BackupType {
Full,
Incremental,
Differential,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BackupStrategy {
Automatic,
Manual,
Scheduled,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RestoreStrategy {
Full,
Partial,
PointInTime,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BackupResult {
pub backup_id: String,
pub size: usize,
pub created_at: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RestoreResult {
pub backup_id: String,
pub data: Vec<u8>,
pub metadata: BackupMetadata,
pub restored_at: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BackupInfo {
pub id: String,
pub size: usize,
pub created_at: u64,
pub metadata: BackupMetadata,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RestoreOperation {
pub id: String,
pub backup_id: String,
pub strategy: RestoreStrategy,
pub status: RestoreStatus,
pub started_at: u64,
pub completed_at: Option<u64>,
pub error: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RestoreStatus {
InProgress,
Completed,
Failed,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BackupConfig {
pub enable_backups: bool,
pub retention_period: Duration,
pub max_backups: usize,
pub strategy: BackupStrategy,
}
impl Default for BackupConfig {
fn default() -> Self {
Self {
enable_backups: true,
retention_period: Duration::from_secs(7 * 24 * 60 * 60), max_backups: 10,
strategy: BackupStrategy::Automatic,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BackupError {
NotInitialized,
BackupNotFound(String),
RestoreNotFound(String),
BackupFailed(String),
RestoreFailed(String),
BackupCorrupted(String),
ConfigurationError(String),
}
impl std::fmt::Display for BackupError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BackupError::NotInitialized => write!(f, "Backup system not initialized"),
BackupError::BackupNotFound(id) => write!(f, "Backup not found: {}", id),
BackupError::RestoreNotFound(id) => write!(f, "Restore not found: {}", id),
BackupError::BackupFailed(msg) => write!(f, "Backup failed: {}", msg),
BackupError::RestoreFailed(msg) => write!(f, "Restore failed: {}", msg),
BackupError::BackupCorrupted(id) => write!(f, "Backup corrupted: {}", id),
BackupError::ConfigurationError(msg) => write!(f, "Configuration error: {}", msg),
}
}
}
impl std::error::Error for BackupError {}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_backup_manager_creation() {
let manager = BackupManager::new();
assert!(!manager.is_initialized());
}
#[tokio::test]
async fn test_backup_manager_initialization() {
let mut manager = BackupManager::new();
let result = manager.initialize().await;
assert!(result.is_ok());
assert!(manager.is_initialized());
}
#[tokio::test]
async fn test_backup_manager_shutdown() {
let mut manager = BackupManager::new();
manager.initialize().await.unwrap();
let result = manager.shutdown().await;
assert!(result.is_ok());
assert!(!manager.is_initialized());
}
#[tokio::test]
async fn test_create_backup() {
let mut manager = BackupManager::new();
manager.initialize().await.unwrap();
let data = b"Hello, World!";
let metadata = BackupMetadata {
name: "test_backup".to_string(),
description: "Test backup".to_string(),
backup_type: BackupType::Full,
source: "test_system".to_string(),
tags: HashMap::new(),
};
let result = manager.create_backup(data, &metadata).await.unwrap();
assert!(!result.backup_id.is_empty());
assert_eq!(result.size, data.len());
assert!(result.created_at > 0);
}
#[tokio::test]
async fn test_restore_backup() {
let mut manager = BackupManager::new();
manager.initialize().await.unwrap();
let data = b"Hello, World!";
let metadata = BackupMetadata {
name: "test_backup".to_string(),
description: "Test backup".to_string(),
backup_type: BackupType::Full,
source: "test_system".to_string(),
tags: HashMap::new(),
};
let backup_result = manager.create_backup(data, &metadata).await.unwrap();
let restore_result = manager.restore_backup(&backup_result.backup_id).await.unwrap();
assert_eq!(restore_result.backup_id, backup_result.backup_id);
assert_eq!(restore_result.data, data);
assert_eq!(restore_result.metadata, metadata);
}
#[tokio::test]
async fn test_list_backups() {
let mut manager = BackupManager::new();
manager.initialize().await.unwrap();
let data = b"Hello, World!";
let metadata = BackupMetadata {
name: "test_backup".to_string(),
description: "Test backup".to_string(),
backup_type: BackupType::Full,
source: "test_system".to_string(),
tags: HashMap::new(),
};
manager.create_backup(data, &metadata).await.unwrap();
let backups = manager.list_backups().await.unwrap();
assert_eq!(backups.len(), 1);
assert_eq!(backups[0].metadata.name, "test_backup");
}
#[tokio::test]
async fn test_delete_backup() {
let mut manager = BackupManager::new();
manager.initialize().await.unwrap();
let data = b"Hello, World!";
let metadata = BackupMetadata {
name: "test_backup".to_string(),
description: "Test backup".to_string(),
backup_type: BackupType::Full,
source: "test_system".to_string(),
tags: HashMap::new(),
};
let backup_result = manager.create_backup(data, &metadata).await.unwrap();
let result = manager.delete_backup(&backup_result.backup_id).await;
assert!(result.is_ok());
let backups = manager.list_backups().await.unwrap();
assert_eq!(backups.len(), 0);
}
#[tokio::test]
async fn test_get_backup_info() {
let mut manager = BackupManager::new();
manager.initialize().await.unwrap();
let data = b"Hello, World!";
let metadata = BackupMetadata {
name: "test_backup".to_string(),
description: "Test backup".to_string(),
backup_type: BackupType::Full,
source: "test_system".to_string(),
tags: HashMap::new(),
};
let backup_result = manager.create_backup(data, &metadata).await.unwrap();
let backup_info = manager.get_backup_info(&backup_result.backup_id).await.unwrap();
assert_eq!(backup_info.id, backup_result.backup_id);
assert_eq!(backup_info.size, data.len());
assert_eq!(backup_info.metadata.name, "test_backup");
}
#[tokio::test]
async fn test_verify_backup() {
let mut manager = BackupManager::new();
manager.initialize().await.unwrap();
let data = b"Hello, World!";
let metadata = BackupMetadata {
name: "test_backup".to_string(),
description: "Test backup".to_string(),
backup_type: BackupType::Full,
source: "test_system".to_string(),
tags: HashMap::new(),
};
let backup_result = manager.create_backup(data, &metadata).await.unwrap();
let is_valid = manager.verify_backup(&backup_result.backup_id).await.unwrap();
assert!(is_valid);
}
#[tokio::test]
async fn test_restore_manager() {
let mut restore_manager = RestoreManager::new();
restore_manager.initialize().await.unwrap();
let restore_id = restore_manager.start_restore("test_backup", RestoreStrategy::Full).await.unwrap();
assert!(!restore_id.is_empty());
let restore_operation = restore_manager.get_restore_status(&restore_id).await.unwrap();
assert_eq!(restore_operation.id, restore_id);
assert_eq!(restore_operation.backup_id, "test_backup");
assert_eq!(restore_operation.status, RestoreStatus::InProgress);
restore_manager.complete_restore(&restore_id, true, None).await.unwrap();
let restore_operation = restore_manager.get_restore_status(&restore_id).await.unwrap();
assert_eq!(restore_operation.status, RestoreStatus::Completed);
assert!(restore_operation.completed_at.is_some());
}
#[tokio::test]
async fn test_backup_not_found() {
let mut manager = BackupManager::new();
manager.initialize().await.unwrap();
let result = manager.restore_backup("nonexistent").await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), BackupError::BackupNotFound(_)));
}
#[tokio::test]
async fn test_restore_not_found() {
let mut restore_manager = RestoreManager::new();
restore_manager.initialize().await.unwrap();
let result = restore_manager.get_restore_status("nonexistent").await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), BackupError::RestoreNotFound(_)));
}
#[test]
fn test_backup_config_default() {
let config = BackupConfig::default();
assert!(config.enable_backups);
assert_eq!(config.retention_period, Duration::from_secs(7 * 24 * 60 * 60));
assert_eq!(config.max_backups, 10);
assert_eq!(config.strategy, BackupStrategy::Automatic);
}
#[test]
fn test_backup_metadata_creation() {
let metadata = BackupMetadata {
name: "test_backup".to_string(),
description: "Test backup".to_string(),
backup_type: BackupType::Full,
source: "test_system".to_string(),
tags: HashMap::new(),
};
assert_eq!(metadata.name, "test_backup");
assert_eq!(metadata.description, "Test backup");
assert_eq!(metadata.backup_type, BackupType::Full);
assert_eq!(metadata.source, "test_system");
}
#[test]
fn test_backup_error_display() {
let error = BackupError::BackupFailed("Test error".to_string());
let error_string = format!("{}", error);
assert!(error_string.contains("Backup failed"));
assert!(error_string.contains("Test error"));
}
}