use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct BackupResult {
pub snapshot_id: u64,
pub uploaded: bool,
pub duration_ms: u64,
pub timestamp: u64,
}
#[derive(Debug, Clone)]
pub struct BackupStatus {
pub running: bool,
pub interval_secs: u64,
pub last_backup: Option<BackupResult>,
pub total_backups: u64,
pub total_failures: u64,
pub history: Vec<BackupResult>,
}
pub struct BackupScheduler {
running: Arc<AtomicBool>,
interval_secs: Arc<RwLock<u64>>,
last_backup: Arc<RwLock<Option<BackupResult>>>,
total_backups: Arc<RwLock<u64>>,
total_failures: Arc<RwLock<u64>>,
history: Arc<RwLock<Vec<BackupResult>>>,
max_history: usize,
}
impl BackupScheduler {
pub fn new(interval_secs: u64) -> Self {
Self {
running: Arc::new(AtomicBool::new(false)),
interval_secs: Arc::new(RwLock::new(interval_secs)),
last_backup: Arc::new(RwLock::new(None)),
total_backups: Arc::new(RwLock::new(0)),
total_failures: Arc::new(RwLock::new(0)),
history: Arc::new(RwLock::new(Vec::new())),
max_history: 50,
}
}
pub fn start<F>(&self, backup_fn: F)
where
F: Fn() -> Result<BackupResult, String> + Send + 'static,
{
if self.running.load(Ordering::SeqCst) {
return; }
self.running.store(true, Ordering::SeqCst);
let running = Arc::clone(&self.running);
let interval = Arc::clone(&self.interval_secs);
let last_backup = Arc::clone(&self.last_backup);
let total_backups = Arc::clone(&self.total_backups);
let total_failures = Arc::clone(&self.total_failures);
let history = Arc::clone(&self.history);
let max_history = self.max_history;
std::thread::Builder::new()
.name("reddb-backup-scheduler".into())
.spawn(move || {
while running.load(Ordering::SeqCst) {
let secs = *interval.read().unwrap_or_else(|e| e.into_inner());
std::thread::sleep(Duration::from_secs(secs));
if !running.load(Ordering::SeqCst) {
break;
}
match backup_fn() {
Ok(result) => {
*last_backup.write().unwrap_or_else(|e| e.into_inner()) =
Some(result.clone());
*total_backups.write().unwrap_or_else(|e| e.into_inner()) += 1;
let mut hist = history.write().unwrap_or_else(|e| e.into_inner());
hist.push(result);
if hist.len() > max_history {
hist.remove(0);
}
}
Err(_) => {
*total_failures.write().unwrap_or_else(|e| e.into_inner()) += 1;
}
}
}
})
.ok();
}
pub fn stop(&self) {
self.running.store(false, Ordering::SeqCst);
}
pub fn set_interval(&self, secs: u64) {
*self
.interval_secs
.write()
.unwrap_or_else(|e| e.into_inner()) = secs;
}
pub fn record_backup(&self, result: BackupResult) {
*self.last_backup.write().unwrap_or_else(|e| e.into_inner()) = Some(result.clone());
*self
.total_backups
.write()
.unwrap_or_else(|e| e.into_inner()) += 1;
let mut hist = self.history.write().unwrap_or_else(|e| e.into_inner());
hist.push(result);
if hist.len() > self.max_history {
hist.remove(0);
}
}
pub fn status(&self) -> BackupStatus {
BackupStatus {
running: self.running.load(Ordering::SeqCst),
interval_secs: *self.interval_secs.read().unwrap_or_else(|e| e.into_inner()),
last_backup: self
.last_backup
.read()
.unwrap_or_else(|e| e.into_inner())
.clone(),
total_backups: *self.total_backups.read().unwrap_or_else(|e| e.into_inner()),
total_failures: *self
.total_failures
.read()
.unwrap_or_else(|e| e.into_inner()),
history: self
.history
.read()
.unwrap_or_else(|e| e.into_inner())
.clone(),
}
}
pub fn is_running(&self) -> bool {
self.running.load(Ordering::SeqCst)
}
}
impl Default for BackupScheduler {
fn default() -> Self {
Self::new(3600)
}
}