use chrono::{DateTime, Local};
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Operation {
pub source: PathBuf,
pub destination: PathBuf,
pub timestamp: DateTime<Local>,
}
impl Operation {
pub fn new(source: PathBuf, destination: PathBuf) -> Self {
Self {
source,
destination,
timestamp: Local::now(),
}
}
}
#[derive(Debug)]
pub struct HistoryManager {
operations: Vec<Operation>,
max_history_size: usize,
backup_directory: PathBuf,
}
impl HistoryManager {
pub fn new(max_history_size: usize, backup_directory: &Path) -> Self {
Self {
operations: Vec::with_capacity(max_history_size),
max_history_size,
backup_directory: backup_directory.to_path_buf(),
}
}
pub fn record(&mut self, source: PathBuf, destination: PathBuf) -> Result<(), Box<dyn Error>> {
if source.exists() {
self.create_backup(&source)?;
}
let operation = Operation::new(source, destination);
self.operations.push(operation);
if self.operations.len() > self.max_history_size {
self.operations.remove(0);
}
Ok(())
}
pub fn undo(&mut self) -> Result<(), Box<dyn Error>> {
if let Some(operation) = self.operations.pop() {
if operation.destination.exists() {
fs::rename(&operation.destination, &operation.source)?;
println!(
"Undone: Moved '{}' back to '{}'",
operation.destination.display(),
operation.source.display()
);
}
else if !operation.source.exists() {
self.restore_backup(&operation.source)?;
println!(
"Undone: Restored '{}' from backup",
operation.source.display()
);
}
Ok(())
} else {
Err("No operations to undo".into())
}
}
#[allow(dead_code)]
pub fn list_operations(&self) -> &[Operation] {
&self.operations
}
fn create_backup(&self, file_path: &Path) -> Result<(), Box<dyn Error>> {
fs::create_dir_all(&self.backup_directory)?;
let filename = file_path
.file_name()
.ok_or("Invalid file path")?
.to_string_lossy();
let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string();
let backup_name = format!("{filename}_{timestamp}");
let backup_path = self.backup_directory.join(backup_name);
fs::copy(file_path, &backup_path)?;
Ok(())
}
fn restore_backup(&self, file_path: &Path) -> Result<(), Box<dyn Error>> {
let filename = file_path
.file_name()
.ok_or("Invalid file path")?
.to_string_lossy();
let mut backups: Vec<PathBuf> = fs::read_dir(&self.backup_directory)?
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
let name = path.file_name()?.to_string_lossy();
if name.starts_with(&*filename) {
Some(path)
} else {
None
}
})
.collect();
backups.sort_by(|a, b| {
let a_time = fs::metadata(a)
.and_then(|m| m.modified())
.unwrap_or_else(|_| std::time::SystemTime::now());
let b_time = fs::metadata(b)
.and_then(|m| m.modified())
.unwrap_or_else(|_| std::time::SystemTime::now());
b_time.cmp(&a_time)
});
if let Some(backup_path) = backups.first() {
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent)?;
}
fs::copy(backup_path, file_path)?;
Ok(())
} else {
Err("No backup found for this file".into())
}
}
}