vtcode_core/audit/
file_conflict_log.rs1use crate::utils::error_messages::ERR_CREATE_AUDIT_DIR;
4use anyhow::{Context, Result};
5use chrono::{DateTime, Local};
6use serde::{Deserialize, Serialize};
7use std::io::BufWriter;
8use std::path::{Path, PathBuf};
9use vtcode_commons::VtCodePaths;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct FileConflictAuditEvent {
14 pub timestamp: DateTime<Local>,
15 pub path: PathBuf,
16 pub reason: String,
17 pub file_exists: bool,
18 pub size_bytes: Option<u64>,
19 pub sha256: Option<String>,
20}
21
22pub struct FileConflictAuditLog {
24 writer: Option<BufWriter<std::fs::File>>,
25 log_path: PathBuf,
26}
27
28impl FileConflictAuditLog {
29 pub fn new(audit_dir: PathBuf) -> Result<Self> {
31 VtCodePaths::ensure_user_dir(&audit_dir).context(ERR_CREATE_AUDIT_DIR)?;
32
33 let date = Local::now().format("%Y-%m-%d");
34 let log_path = audit_dir.join(format!("file-conflicts-{date}.log"));
35 Ok(Self { writer: None, log_path })
36 }
37
38 pub fn record(&mut self, event: &FileConflictAuditEvent) -> Result<()> {
39 use std::io::Write;
40
41 let json = serde_json::to_string(event).context("Failed to serialize file conflict audit event")?;
42 let writer = self.writer_mut()?;
43 writeln!(writer, "{json}").context("Failed to write file conflict audit event")?;
44 writer.flush().context("Failed to flush file conflict audit log")?;
45 Ok(())
46 }
47
48 pub fn log_path(&self) -> &Path {
49 &self.log_path
50 }
51
52 fn writer_mut(&mut self) -> Result<&mut BufWriter<std::fs::File>> {
53 if self.writer.is_none() {
54 let file = VtCodePaths::open_private_append_file(&self.log_path)
55 .with_context(|| format!("Failed to open file conflict audit log at {:?}", self.log_path))?;
56 self.writer = Some(BufWriter::new(file));
57 }
58
59 self.writer
60 .as_mut()
61 .context("file conflict audit log writer was not initialized")
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use tempfile::TempDir;
69
70 #[test]
71 fn defers_file_conflict_audit_log_creation_until_first_record() -> Result<()> {
72 let dir = TempDir::new()?;
73 let log = FileConflictAuditLog::new(dir.path().to_path_buf())?;
74 assert!(!log.log_path().exists());
75 Ok(())
76 }
77}