use std::path::Path;
use crate::workspace::Workspace;
include!("io.rs");
pub const MAX_AGENT_FILE_SIZE: u64 = 10 * 1024 * 1024;
pub fn write_file_atomic_with_workspace(
workspace: &dyn Workspace,
path: &Path,
content: &str,
) -> std::io::Result<()> {
workspace.write_atomic(path, content)
}
pub fn verify_file_not_corrupted_with_workspace(
workspace: &dyn Workspace,
path: &Path,
) -> std::io::Result<bool> {
let content = workspace.read_bytes(path)?;
if content.is_empty() || content.len() as u64 > MAX_AGENT_FILE_SIZE {
return Ok(false);
}
let Ok(text) = String::from_utf8(content) else {
return Ok(false);
};
Ok(!text.contains('\0'))
}
pub fn check_filesystem_ready_with_workspace(
workspace: &dyn Workspace,
path: &Path,
) -> std::io::Result<()> {
if !workspace.is_dir(path) {
workspace.create_dir_all(path)?;
}
let test_file = path.join(".write_test");
workspace.write(&test_file, "test")?;
workspace.remove(&test_file)?;
if let Some(lock_file) = workspace
.read_dir(path)
.ok()
.and_then(|entries| find_stale_lock(&entries))
{
return Err(std::io::Error::other(format!(
"Stale lock file found: {lock_file}"
)));
}
Ok(())
}
pub fn check_xml_file_writable_with_workspace(
workspace: &dyn Workspace,
xml_path: &Path,
force_cleanup: bool,
) -> std::io::Result<bool> {
if !workspace.exists(xml_path) {
return Ok(false);
}
if force_cleanup {
workspace.remove(xml_path)?;
return Ok(false);
}
let content = workspace.read(xml_path)?;
workspace.write(xml_path, &content)?;
Ok(true)
}
pub fn check_and_cleanup_xml_before_retry_with_workspace(
workspace: &dyn Workspace,
xml_path: &Path,
logger: &crate::logger::Logger,
) -> std::io::Result<()> {
match check_xml_file_writable_with_workspace(workspace, xml_path, false) {
Ok(true | false) => Ok(()),
Err(e) => {
logger.warn(&format!(
"XML file {} error: {}. Attempting cleanup...",
xml_path.display(),
e
));
match check_xml_file_writable_with_workspace(workspace, xml_path, true) {
Ok(_) => {
logger.info(&format!(
"Successfully cleaned up file: {}",
xml_path.display()
));
Ok(())
}
Err(cleanup_err) => {
logger.error(&format!(
"Failed to cleanup file {}: {}",
xml_path.display(),
cleanup_err
));
Err(cleanup_err)
}
}
}
}
}
pub fn cleanup_stale_xml_files_with_workspace(
workspace: &dyn Workspace,
tmp_dir: &Path,
force_cleanup: bool,
) -> std::io::Result<String> {
if !workspace.is_dir(tmp_dir) {
return Ok("Directory doesn't exist yet - nothing to clean".to_string());
}
let entries = workspace.read_dir(tmp_dir)?;
let results: Vec<_> = entries
.iter()
.filter_map(|entry| {
let path = entry.path();
let extension = path.extension().and_then(|s| s.to_str())?;
if extension != "xml" {
return None;
}
Some((path, extension == "xml"))
})
.collect();
let (writable, cleaned, report): (usize, usize, Vec<String>) = if force_cleanup {
let cleaned: Vec<_> = results
.iter()
.filter(|(path, _)| workspace.exists(path))
.filter_map(|(path, _)| {
workspace.remove(path).ok()?;
Some(format!(" 🗑 Removed file: {}", path.display()))
})
.collect();
(0, cleaned.len(), cleaned)
} else {
let report: Vec<_> = results
.iter()
.map(|(path, _)| format!(" ✓ {} is writable", path.display()))
.collect();
(results.len(), 0, report)
};
let summary = format!(
"XML file check complete: {} writable, {} locked, {} cleaned",
writable, 0, cleaned
);
if report.is_empty() {
Ok(summary)
} else {
Ok(format!("{}\n{}", summary, report.join("\n")))
}
}
#[cfg(test)]
mod tests;