use crate::options::SyncOptions;
use anyhow::Result;
use chrono::Local;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
pub struct ErrorReporter {
report_file: Option<PathBuf>,
errors: Arc<Mutex<Vec<ErrorEntry>>>,
warnings: Arc<Mutex<Vec<ErrorEntry>>>,
error_count: Arc<Mutex<usize>>,
warning_count: Arc<Mutex<usize>>,
verbose: u8,
show_progress: bool,
}
#[derive(Clone)]
struct ErrorEntry {
timestamp: String,
path: String,
message: String,
}
impl ErrorReporter {
pub fn new(_source: &Path, _destination: &Path, options: &SyncOptions) -> Self {
let report_path = if let Some(ref log_file) = options.log_file {
let log_path = PathBuf::from(log_file);
let stem = log_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("log");
let extension = log_path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("log");
PathBuf::from(format!("{stem}_errors.{extension}"))
} else {
let timestamp = Local::now().format("%Y%m%d_%H%M%S");
PathBuf::from(format!("{timestamp}_robosync_errors.log"))
};
let report_file = if options.no_report_errors {
None
} else {
Some(report_path)
};
Self {
report_file,
errors: Arc::new(Mutex::new(Vec::new())),
warnings: Arc::new(Mutex::new(Vec::new())),
error_count: Arc::new(Mutex::new(0)),
warning_count: Arc::new(Mutex::new(0)),
verbose: options.verbose,
show_progress: options.show_progress,
}
}
pub fn get_handle(&self) -> ErrorReportHandle {
ErrorReportHandle {
errors: Arc::clone(&self.errors),
warnings: Arc::clone(&self.warnings),
error_count: Arc::clone(&self.error_count),
warning_count: Arc::clone(&self.warning_count),
verbose: self.verbose,
show_progress: self.show_progress,
}
}
pub fn add_error(&self, path: &Path, message: &str) {
let entry = ErrorEntry {
timestamp: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
path: path.display().to_string(),
message: message.to_string(),
};
if let Ok(mut errors) = self.errors.lock() {
errors.push(entry);
}
if let Ok(mut count) = self.error_count.lock() {
*count += 1;
}
}
pub fn add_warning(&self, path: &Path, message: &str) {
let entry = ErrorEntry {
timestamp: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
path: path.display().to_string(),
message: message.to_string(),
};
if let Ok(mut warnings) = self.warnings.lock() {
warnings.push(entry);
}
if let Ok(mut count) = self.warning_count.lock() {
*count += 1;
}
}
pub fn error_count(&self) -> usize {
*self.error_count.lock().unwrap_or_else(|e| {
eprintln!("Warning: Error report lock poisoned: {}", e);
std::process::exit(1);
})
}
pub fn warning_count(&self) -> usize {
*self.warning_count.lock().unwrap_or_else(|e| {
eprintln!("Warning: Error report lock poisoned: {}", e);
std::process::exit(1);
})
}
pub fn write_report(&self) -> Result<Option<PathBuf>> {
let error_count = self.error_count();
let warning_count = self.warning_count();
if error_count == 0 && warning_count == 0 {
return Ok(None);
}
let report_path = match &self.report_file {
Some(path) => path,
None => return Ok(None),
};
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(report_path)?;
writeln!(file, "RoboSync Error Report")?;
writeln!(
file,
"Generated: {}",
Local::now().format("%Y-%m-%d %H:%M:%S")
)?;
writeln!(file, "{}=", "=".repeat(78))?;
writeln!(file)?;
writeln!(file, "Summary:")?;
writeln!(file, " Errors: {error_count}")?;
writeln!(file, " Warnings: {warning_count}")?;
writeln!(file)?;
if error_count > 0 {
writeln!(file, "ERRORS:")?;
writeln!(file, "-------")?;
if let Ok(errors) = self.errors.lock() {
for (i, error) in errors.iter().enumerate() {
writeln!(file, "\n[{}] Error #{}", error.timestamp, i + 1)?;
writeln!(file, " File: {}", error.path)?;
writeln!(file, " Details: {}", error.message)?;
}
}
writeln!(file)?;
}
if warning_count > 0 {
writeln!(file, "WARNINGS:")?;
writeln!(file, "---------")?;
if let Ok(warnings) = self.warnings.lock() {
for (i, warning) in warnings.iter().enumerate() {
writeln!(file, "\n[{}] Warning #{}", warning.timestamp, i + 1)?;
writeln!(file, " File: {}", warning.path)?;
writeln!(file, " Details: {}", warning.message)?;
}
}
}
file.flush()?;
Ok(Some(report_path.clone()))
}
}
#[derive(Clone)]
pub struct ErrorReportHandle {
errors: Arc<Mutex<Vec<ErrorEntry>>>,
warnings: Arc<Mutex<Vec<ErrorEntry>>>,
error_count: Arc<Mutex<usize>>,
warning_count: Arc<Mutex<usize>>,
verbose: u8,
show_progress: bool,
}
impl ErrorReportHandle {
pub fn add_error(&self, path: &Path, message: &str) {
self.add_error_with_operation(path, message, "sync");
}
pub fn add_error_with_operation(&self, path: &Path, message: &str, operation: &str) {
let entry = ErrorEntry {
timestamp: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
path: path.display().to_string(),
message: format!("{operation}: {message}"),
};
if let Ok(mut errors) = self.errors.lock() {
errors.push(entry);
}
if let Ok(mut count) = self.error_count.lock() {
*count += 1;
if self.verbose >= 1 {
eprintln!(
"[{}] Error {}: {} - {}",
Local::now().format("%H:%M:%S"),
operation,
path.display(),
message
);
} else if self.show_progress && *count % 10 == 0 {
eprintln!(" [{count}] errors encountered (details will be saved to report)");
}
}
}
pub fn add_warning(&self, path: &Path, message: &str) {
let entry = ErrorEntry {
timestamp: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
path: path.display().to_string(),
message: message.to_string(),
};
if let Ok(mut warnings) = self.warnings.lock() {
warnings.push(entry);
}
if let Ok(mut count) = self.warning_count.lock() {
*count += 1;
if *count % 10 == 0 {
eprintln!(" [{count}] warnings encountered (details will be saved to report)");
}
}
}
pub fn log_success(&self, path: &Path, operation: &str) {
if self.verbose >= 2 {
let timestamp = Local::now().format("%H:%M:%S");
println!("[{}] {} {}", timestamp, operation, path.display());
}
}
pub fn should_show_progress(&self) -> bool {
self.verbose < 2 && self.show_progress
}
}