use std::io::Write;
use std::time::Instant;
use anyhow::{Context, Result};
use crate::checksum;
use crate::cli::{DeleteArgs, GlobalArgs};
use crate::error::AtomwriteError;
use crate::ndjson_types::{DeleteOutput, DryRunPlan, Summary};
use crate::output::NdjsonWriter;
use crate::platform;
#[tracing::instrument(skip_all, fields(command = "delete"))]
pub fn cmd_delete(
args: &DeleteArgs,
global: &GlobalArgs,
writer: &mut NdjsonWriter<impl Write>,
) -> Result<()> {
let start = Instant::now();
let workspace = global.resolve_workspace()?;
let mut deleted = 0u64;
let mut _bytes_freed = 0u64;
for path in &args.paths {
let path = crate::path_safety::validate_path(path, &workspace)?;
if !path.exists() {
return Err(AtomwriteError::NotFound { path }.into());
}
if path.is_dir() && !args.recursive {
return Err(AtomwriteError::InvalidInput {
reason: format!("{} is a directory, use --recursive", path.display()),
}
.into());
}
if path.is_file() {
let path_str = path.display().to_string();
let meta =
std::fs::metadata(&path).with_context(|| format!("cannot stat {path_str}"))?;
let hash = checksum::hash_file(&path, global.effective_max_filesize())?;
let size = meta.len();
if args.dry_run {
writer.write_event(&DryRunPlan {
r#type: "plan",
operation: "delete".into(),
path: path_str,
would_modify: true,
details: Some(format!("{size} bytes")),
})?;
continue;
}
if args.backup {
crate::atomic::create_backup(&path, args.retention)?;
}
std::fs::remove_file(&path)
.with_context(|| format!("cannot delete {}", path.display()))?;
if let Some(parent) = path.parent() {
if let Err(e) = platform::fsync_dir(parent) {
tracing::warn!(
path = %parent.display(),
error = %e,
"fsync_dir after delete failed"
);
}
}
deleted += 1;
_bytes_freed += size;
writer.write_event(&DeleteOutput {
r#type: "deleted",
path: path_str,
bytes: size,
checksum_before: hash,
elapsed_ms: start.elapsed().as_millis() as u64,
})?;
}
}
writer.write_event(&Summary {
r#type: "summary",
files_visited: args.paths.len() as u64,
files_matched: deleted,
files_modified: Some(deleted),
files_skipped: Some(args.paths.len() as u64 - deleted),
total_matches: None,
total_replacements: None,
elapsed_ms: start.elapsed().as_millis() as u64,
})?;
Ok(())
}