use super::traits::HealthChecker;
use crate::error::{ProviderError, Result, SyncError};
use async_trait::async_trait;
use std::collections::HashMap;
use std::time::{Duration, Instant};
pub struct HealthCheckerImpl {
providers: HashMap<String, Box<dyn crate::providers::StorageProvider>>,
}
impl HealthCheckerImpl {
pub fn new(providers: HashMap<String, Box<dyn crate::providers::StorageProvider>>) -> Self {
Self { providers }
}
}
#[derive(Debug, Clone, Default)]
struct TempStorageHealth {}
#[async_trait]
impl HealthChecker for HealthCheckerImpl {
async fn check_provider_health(&self, provider_id: &str) -> Result<HealthStatus> {
let provider = self
.providers
.get(provider_id)
.ok_or_else(|| SyncError::Provider(ProviderError::NotFound(provider_id.into())))?;
let start_time = Instant::now();
match provider.list("/").await {
Ok(_) => {
let response_time = start_time.elapsed();
Ok(HealthStatus::Healthy {
provider_id: provider_id.into(),
response_time,
last_check: chrono::Utc::now(),
})
}
Err(e) => Ok(HealthStatus::Unhealthy {
provider_id: provider_id.into(),
error: e.to_string(),
last_check: chrono::Utc::now(),
}),
}
}
async fn check_storage_health(&self) -> Result<StorageHealth> {
let mut health = StorageHealth {
local_storage: LocalStorageHealth::default(),
temp_storage: TempStorageHealth::default(),
database_health: DatabaseHealth::default(),
overall: HealthStatus::Healthy {
provider_id: "system".into(),
response_time: Duration::from_secs(0),
last_check: chrono::Utc::now(),
},
};
if let Some(data_dir) = dirs::data_dir() {
let avail = fs2::available_space(&data_dir).unwrap_or(0);
let total = fs2::total_space(&data_dir).unwrap_or(0);
health.local_storage.available_space = avail;
health.local_storage.total_space = total;
let used = total.saturating_sub(avail);
health.local_storage.usage_percentage = if total > 0 {
(used as f64 / total as f64) * 100.0
} else {
0.0
};
if health.local_storage.usage_percentage > 90.0 {
health.overall = HealthStatus::Degraded {
provider_id: "local_storage".into(),
warning: "Local storage usage is high".into(),
last_check: chrono::Utc::now(),
};
}
}
Ok(health)
}
async fn check_connectivity(&self) -> Result<ConnectivityStatus> {
let mut status = ConnectivityStatus {
internet: false,
dns_resolution: false,
proxy_configured: false,
connectivity_tests: Vec::new(),
};
let internet_test = ConnectivityTest {
name: "Internet".into(),
target: "8.8.8.8:53".into(),
protocol: Protocol::UDP,
timeout: Duration::from_secs(3),
success: false,
latency: None,
};
Ok(status)
}
}
#[derive(Debug, Clone)]
pub enum HealthStatus {
Healthy {
provider_id: String,
response_time: Duration,
last_check: chrono::DateTime<chrono::Utc>,
},
Degraded {
provider_id: String,
warning: String,
last_check: chrono::DateTime<chrono::Utc>,
},
Unhealthy {
provider_id: String,
error: String,
last_check: chrono::DateTime<chrono::Utc>,
},
}
#[derive(Debug, Clone)]
pub struct StorageHealth {
pub local_storage: LocalStorageHealth,
pub temp_storage: TempStorageHealth,
pub database_health: DatabaseHealth,
pub overall: HealthStatus,
}
#[derive(Debug, Clone, Default)]
pub struct LocalStorageHealth {
pub available_space: u64,
pub total_space: u64,
pub usage_percentage: f64,
pub is_readable: bool,
pub is_writable: bool,
}
#[derive(Debug, Clone, Default)]
pub struct DatabaseHealth {}
#[derive(Debug, Clone)]
pub struct ConnectivityStatus {
pub internet: bool,
pub dns_resolution: bool,
pub proxy_configured: bool,
pub connectivity_tests: Vec<ConnectivityTest>,
}
#[derive(Debug, Clone)]
pub struct ConnectivityTest {
pub name: String,
pub target: String,
pub protocol: Protocol,
pub timeout: Duration,
pub success: bool,
pub latency: Option<Duration>,
}
#[derive(Debug, Clone)]
pub enum Protocol {
TCP,
UDP,
HTTP,
HTTPS,
}