use regex::Regex;
use tracing::{error, info};
use walkdir::WalkDir;
use crate::util::{calculate_file_hash, should_exclude};
use super::Storage;
use std::{
fs,
path::{Path, PathBuf},
};
pub struct LocalStorage {
backup_directory: PathBuf,
}
impl LocalStorage {
pub fn new(backup_directory: &str) -> Self {
LocalStorage {
backup_directory: PathBuf::from(backup_directory),
}
}
}
impl Storage for LocalStorage {
fn store_file(
&self,
source_path: &str,
destination_path: &str,
exclude: &[String],
) -> Result<(), String> {
let exclude_patterns: Vec<Regex> = exclude
.iter()
.map(|p| {
let clean_pattern = p.trim_end_matches('/').to_string();
let escaped_pattern = regex::escape(&clean_pattern).replace("\\*", ".*");
Regex::new(&escaped_pattern).map_err(|err| format!("Invalid regex pattern {}: {}", p, err))
})
.collect::<Result<_, _>>()?;
for entry in WalkDir::new(source_path) {
let entry = entry.unwrap();
let entry_path = entry.path();
if should_exclude(entry_path, &exclude_patterns) {
println!("Excluded: {}", entry_path.display());
continue;
}
if entry.path().is_file() {
let source_hash = calculate_file_hash(entry.path().to_str().unwrap())
.map_err(|err| format!("Error calculating source file hash: {}", err))?;
let relative_path = entry
.path()
.strip_prefix(source_path)
.map_err(|err| format!("Error getting relative path: {}", err))?;
let relative_dir = relative_path
.parent()
.ok_or("Error getting relative directory")?;
let destination_dir = Path::new(destination_path).join(relative_dir);
if !destination_dir.exists() {
fs::create_dir_all(&destination_dir).map_err(|err| {
format!(
"Error creating directory {}: {}",
destination_dir.display(),
err
)
})?;
}
let destination_file_path = destination_dir.join(entry.file_name());
let destination_hash =
calculate_file_hash(&destination_file_path.to_str().unwrap())
.unwrap_or_default();
if source_hash != destination_hash {
match fs::copy(entry.path(), &destination_file_path) {
Ok(_) => info!(
"File {} backed up successfully",
destination_file_path.display()
),
Err(err) => error!(
"Error storing file {}: {}",
destination_file_path.display(),
err
),
}
} else {
info!(
"File {} already exists and has the same hash. No need to backup.",
destination_file_path.to_str().unwrap()
);
}
}
}
Ok(())
}
fn backup_destination(&self) -> &str {
self.backup_directory
.to_str()
.expect("Invalid backup directory path")
}
}