use super::audit_file_data::{AuditEntryRecord, AuditFileConfig, AuditFileData};
use super::audit_file_format::{AuditFileParser, AuditFileWriter};
#[cfg(not(target_arch = "wasm32"))]
mod native_audit {
use super::{AuditEntryRecord, AuditFileConfig, AuditFileData, AuditFileParser, AuditFileWriter};
use crate::Compiler::Utilities::file_permissions;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
pub struct AuditFileManager {
audit_file_path: String,
max_entries: usize,
}
impl AuditFileManager {
pub fn new(audit_file_path: String, max_entries: usize) -> Self {
AuditFileManager { audit_file_path, max_entries }
}
pub fn audit_file_path(&self) -> &str {
&self.audit_file_path
}
pub fn load(&self) -> Option<AuditFileData> {
let path = Path::new(&self.audit_file_path);
if !path.exists() { return None; }
let content = std::fs::read_to_string(path).ok()?;
AuditFileParser::parse(&content).ok()
}
pub fn read_raw(&self) -> Option<String> {
let path = Path::new(&self.audit_file_path);
if !path.exists() { return None; }
std::fs::read_to_string(path).ok()
}
pub fn count_entries(&self) -> usize {
let path = Path::new(&self.audit_file_path);
if !path.exists() { return 0; }
std::fs::read_to_string(path)
.map(|c| AuditFileParser::count_entries(&c))
.unwrap_or(0)
}
pub fn file_exists(&self) -> bool {
Path::new(&self.audit_file_path).exists()
}
pub fn append_entry(
&self,
entry: &AuditEntryRecord,
config: &AuditFileConfig,
) -> Result<(), String> {
self.rotate_if_needed()?;
let path = Path::new(&self.audit_file_path);
let file_exists = path.exists();
if file_exists {
file_permissions::set_writable(path)
.map_err(|e| format!("Cannot unlock audit file for writing: {}", e))?;
}
let result = self.do_append(entry, config, file_exists);
if let Err(e) = file_permissions::set_readonly(path) {
eprintln!("[AuditFileManager] Warning: could not re-lock audit file: {}", e);
}
result
}
fn rotate_if_needed(&self) -> Result<(), String> {
let count = self.count_entries();
if count < self.max_entries { return Ok(()); }
let path = Path::new(&self.audit_file_path);
if path.exists() {
file_permissions::set_writable(path)
.map_err(|e| format!("Cannot unlock audit file for rotation: {}", e))?;
}
let ts = chrono::Utc::now().format("%Y%m%d_%H%M%S");
let archive = format!("{}.archive_{}", self.audit_file_path, ts);
std::fs::rename(&self.audit_file_path, &archive)
.map_err(|e| format!("Failed to rotate audit file: {}", e))?;
Ok(())
}
fn do_append(
&self,
entry: &AuditEntryRecord,
config: &AuditFileConfig,
file_exists: bool,
) -> Result<(), String> {
let existing_count = if file_exists { self.count_entries() } else { 0 };
let mut entry = entry.clone();
entry.index = existing_count + 1;
if !file_exists {
let content = format!(
"{}{}",
AuditFileWriter::write_header(config),
AuditFileWriter::write_entry(&entry),
);
std::fs::write(&self.audit_file_path, content)
.map_err(|e| format!("Failed to create audit file: {}", e))?;
} else {
let mut file = OpenOptions::new()
.append(true)
.open(&self.audit_file_path)
.map_err(|e| format!("Failed to open audit file for append: {}", e))?;
file.write_all(AuditFileWriter::write_entry(&entry).as_bytes())
.map_err(|e| format!("Failed to append to audit file: {}", e))?;
}
Ok(())
}
}
}
#[cfg(target_arch = "wasm32")]
mod wasm_audit {
use super::{AuditEntryRecord, AuditFileConfig, AuditFileData, AuditFileParser, AuditFileWriter};
const KEY_PREFIX: &str = "mdix_audit:";
pub struct AuditFileManager {
audit_key: String,
max_entries: usize,
}
impl AuditFileManager {
pub fn new(audit_file_path: String, max_entries: usize) -> Self {
AuditFileManager {
audit_key: format!("{}{}", KEY_PREFIX, audit_file_path),
max_entries,
}
}
pub fn audit_file_path(&self) -> &str {
&self.audit_key
}
fn storage() -> Option<web_sys::Storage> {
web_sys::window()?.local_storage().ok()?
}
pub fn load(&self) -> Option<AuditFileData> {
let storage = Self::storage()?;
let content = storage.get_item(&self.audit_key).ok()??;
AuditFileParser::parse(&content).ok()
}
pub fn read_raw(&self) -> Option<String> {
let storage = Self::storage()?;
storage.get_item(&self.audit_key).ok()?
}
pub fn count_entries(&self) -> usize {
let Some(storage) = Self::storage() else { return 0; };
match storage.get_item(&self.audit_key) {
Ok(Some(content)) => AuditFileParser::count_entries(&content),
_ => 0,
}
}
pub fn file_exists(&self) -> bool {
match Self::storage() {
Some(s) => matches!(s.get_item(&self.audit_key), Ok(Some(_))),
None => false,
}
}
pub fn append_entry(
&self,
entry: &AuditEntryRecord,
config: &AuditFileConfig,
) -> Result<(), String> {
self.rotate_if_needed()?;
let Some(storage) = Self::storage() else {
return Err(
"localStorage unavailable — cannot append audit entry for this session"
.to_string(),
);
};
let existing = storage.get_item(&self.audit_key).ok().flatten();
let existing_count = existing
.as_deref()
.map(AuditFileParser::count_entries)
.unwrap_or(0);
let mut entry = entry.clone();
entry.index = existing_count + 1;
let new_content = match existing {
Some(content) => format!("{}{}", content, AuditFileWriter::write_entry(&entry)),
None => format!(
"{}{}",
AuditFileWriter::write_header(config),
AuditFileWriter::write_entry(&entry),
),
};
storage
.set_item(&self.audit_key, &new_content)
.map_err(|_| "Failed to write audit entry to localStorage".to_string())
}
fn rotate_if_needed(&self) -> Result<(), String> {
let count = self.count_entries();
if count < self.max_entries { return Ok(()); }
let Some(storage) = Self::storage() else { return Ok(()); };
let Ok(Some(content)) = storage.get_item(&self.audit_key) else { return Ok(()); };
let counter_key = format!("{}.rotation_count", self.audit_key);
let rotation_n: u32 = storage
.get_item(&counter_key)
.ok()
.flatten()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let archive_key = format!("{}.archive_{}", self.audit_key, rotation_n);
let _ = storage.set_item(&archive_key, &content);
let _ = storage.set_item(&counter_key, &(rotation_n + 1).to_string());
let _ = storage.remove_item(&self.audit_key);
Ok(())
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub use native_audit::AuditFileManager;
#[cfg(target_arch = "wasm32")]
pub use wasm_audit::AuditFileManager;