use crate::utils::error_messages::ERR_CREATE_AUDIT_DIR;
use crate::utils::file_utils::ensure_dir_exists_sync;
use anyhow::{Context, Result};
use chrono::{DateTime, Local};
use serde::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::BufWriter;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileConflictAuditEvent {
pub timestamp: DateTime<Local>,
pub path: PathBuf,
pub reason: String,
pub file_exists: bool,
pub size_bytes: Option<u64>,
pub sha256: Option<String>,
}
pub struct FileConflictAuditLog {
writer: Option<BufWriter<std::fs::File>>,
log_path: PathBuf,
}
impl FileConflictAuditLog {
pub fn new(audit_dir: PathBuf) -> Result<Self> {
ensure_dir_exists_sync(&audit_dir).context(ERR_CREATE_AUDIT_DIR)?;
let date = Local::now().format("%Y-%m-%d");
let log_path = audit_dir.join(format!("file-conflicts-{}.log", date));
Ok(Self {
writer: None,
log_path,
})
}
pub fn record(&mut self, event: &FileConflictAuditEvent) -> Result<()> {
use std::io::Write;
let json = serde_json::to_string(event)
.context("Failed to serialize file conflict audit event")?;
let writer = self.writer_mut()?;
writeln!(writer, "{json}").context("Failed to write file conflict audit event")?;
writer
.flush()
.context("Failed to flush file conflict audit log")?;
Ok(())
}
pub fn log_path(&self) -> &Path {
&self.log_path
}
fn writer_mut(&mut self) -> Result<&mut BufWriter<std::fs::File>> {
if self.writer.is_none() {
let file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.log_path)
.with_context(|| {
format!(
"Failed to open file conflict audit log at {:?}",
self.log_path
)
})?;
self.writer = Some(BufWriter::new(file));
}
self.writer
.as_mut()
.context("file conflict audit log writer was not initialized")
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn defers_file_conflict_audit_log_creation_until_first_record() -> Result<()> {
let dir = TempDir::new()?;
let log = FileConflictAuditLog::new(dir.path().to_path_buf())?;
assert!(!log.log_path().exists());
Ok(())
}
}