use crate::maps::{self, MapsCache, HashLookup};
use crate::serve;
use std::fs;
use std::path::Path;
pub fn remove_xvuid(repo_path: &Path, maps_path: &Path, xvuid: &str) {
let dif_path = repo_path.join(format!("{}.dif", xvuid));
if !dif_path.exists() {
eprintln!("Error: No .dif file found for xvuid {}", xvuid);
std::process::exit(1);
}
if let Err(e) = fs::remove_file(&dif_path) {
eprintln!("Error removing {}: {}", dif_path.display(), e);
std::process::exit(1);
}
eprintln!("Removed {}", dif_path.display());
let md_dif_path = repo_path.join("md").join(format!("{}.dif", xvuid));
if md_dif_path.exists() {
if let Err(e) = fs::remove_file(&md_dif_path) {
eprintln!("Warning: could not remove md counterpart {}: {}", md_dif_path.display(), e);
} else {
eprintln!("Removed {}", md_dif_path.display());
}
}
maps::rebuild_memcache_maps(repo_path, maps_path);
}
pub fn remove_by_hash(repo_path: &Path, maps_path: &Path, map_name: &str, hash: &str) {
let cache = MapsCache::load(maps_path);
let (xvuid, index) = match cache.lookup_hash(map_name, hash) {
HashLookup::Found { xvuid, index } => (xvuid, index),
HashLookup::Missing => {
eprintln!("Error: hash {} not found in {}", hash, map_name);
std::process::exit(1);
}
};
let (latest_index, _) = match cache.lookup_latest(&xvuid) {
Some(pair) => pair,
None => {
eprintln!("Error: could not look up latest entry for xvuid {}", xvuid);
std::process::exit(1);
}
};
if index != latest_index {
eprintln!(
"Error: hash {} is at index {} but the latest entry is at index {}. Only the latest entry can be removed.",
hash, index, latest_index
);
std::process::exit(1);
}
if index == 0 {
eprintln!("This is the only entry — removing entire xvuid {}", xvuid);
remove_xvuid(repo_path, maps_path, &xvuid);
return;
}
truncate_last_entry_at(&repo_path.join(format!("{}.dif", xvuid)));
let md_dif_path = repo_path.join("md").join(format!("{}.dif", &xvuid));
if md_dif_path.exists() {
truncate_last_entry_at(&md_dif_path);
}
maps::rebuild_memcache_maps(repo_path, maps_path);
eprintln!("Removed latest entry (index {}) from xvuid {}", index, xvuid);
}
fn truncate_last_entry_at(dif_path: &Path) {
if !dif_path.exists() {
return;
}
let file = match fs::File::open(dif_path) {
Ok(f) => f,
Err(e) => {
eprintln!("Error: could not open {}: {}", dif_path.display(), e);
std::process::exit(1);
}
};
let file_len = file.metadata().unwrap().len();
let prefix = match serve::read_dif_prefix(&file, file_len) {
Some(p) => p,
None => {
eprintln!("Error: could not read dif prefix from {}", dif_path.display());
std::process::exit(1);
}
};
let spans = match serve::scan_entry_spans_from_offset(&file, file_len, prefix.len() as u64, None) {
Some(s) => s,
None => {
eprintln!("Error: could not parse entry spans from {}", dif_path.display());
std::process::exit(1);
}
};
drop(file);
if spans.len() < 2 {
eprintln!("Error: dif file has fewer than 2 entries, use xvuid removal instead");
std::process::exit(1);
}
let last_span = spans.last().unwrap();
let truncate_to = last_span.offset;
let file = fs::OpenOptions::new()
.write(true)
.open(dif_path)
.unwrap_or_else(|e| {
eprintln!("Error opening {} for truncation: {}", dif_path.display(), e);
std::process::exit(1);
});
file.set_len(truncate_to).unwrap_or_else(|e| {
eprintln!("Error truncating {}: {}", dif_path.display(), e);
std::process::exit(1);
});
drop(file);
let mut file = fs::OpenOptions::new()
.append(true)
.open(dif_path)
.unwrap();
use std::io::Write;
file.write_all(&[0xC0, 0x01]).unwrap();
eprintln!("Truncated last entry from {}", dif_path.display());
}