use crate::Error;
use std::path::Path;
#[tracing::instrument]
pub fn unzip(zip_path: &Path, destination: &Path) -> Result<(), Error> {
let file = std::fs::File::open(zip_path)?;
let mut archive = zip::ZipArchive::new(file)?;
archive.extract(destination)?;
Ok(())
}
#[tracing::instrument]
pub fn clean_except_maps(sde_dir: &Path) -> Result<(), Error> {
if !sde_dir.exists() {
return Ok(());
}
let maps_dir = sde_dir.join("maps");
for entry in std::fs::read_dir(sde_dir)? {
let path = entry?.path();
if path == maps_dir {
continue;
}
if path.is_dir() {
std::fs::remove_dir_all(&path)?;
} else {
std::fs::remove_file(&path)?;
}
}
Ok(())
}
#[tracing::instrument]
pub fn prepare_sde_directory(zip_path: &Path, sde_dir: &Path) -> Result<(), Error> {
clean_except_maps(sde_dir)?;
unzip(zip_path, sde_dir)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn temp_dir(name: &str) -> std::path::PathBuf {
let dir =
std::env::temp_dir().join(format!("sde-extract-test-{name}-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
dir
}
fn build_test_zip(path: &Path, entries: &[(&str, &str)]) {
let file = std::fs::File::create(path).unwrap();
let mut writer = zip::ZipWriter::new(file);
let options = zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Stored);
for (name, content) in entries {
writer.start_file(*name, options).unwrap();
writer.write_all(content.as_bytes()).unwrap();
}
writer.finish().unwrap();
}
#[test]
fn unzip_extracts_files_and_subdirectories() {
let dir = temp_dir("basic");
let zip_path = dir.join("test.zip");
build_test_zip(
&zip_path,
&[
("types.jsonl", "{\"_key\": 1}\n"),
("universe/regions/The_Forge.jsonl", "{\"_key\": 10000002}\n"),
],
);
let destination = dir.join("out");
unzip(&zip_path, &destination).unwrap();
let types_content = std::fs::read_to_string(destination.join("types.jsonl")).unwrap();
assert_eq!(types_content, "{\"_key\": 1}\n");
let region_content =
std::fs::read_to_string(destination.join("universe/regions/The_Forge.jsonl")).unwrap();
assert_eq!(region_content, "{\"_key\": 10000002}\n");
}
#[test]
fn unzip_errors_on_invalid_zip() {
let dir = temp_dir("invalid");
let bad_zip = dir.join("not_a_zip.zip");
std::fs::write(&bad_zip, b"this is not a zip").unwrap();
let destination = dir.join("out");
let result = unzip(&bad_zip, &destination);
assert!(matches!(
result,
Err(err) if matches!(err.kind(), crate::error::ErrorKind::Zip(_))
));
}
#[test]
fn clean_except_maps_does_nothing_when_directory_missing() {
let dir = temp_dir("missing");
let sde_dir = dir.join("does_not_exist_yet");
clean_except_maps(&sde_dir).unwrap();
}
#[test]
fn clean_except_maps_preserves_maps_removes_the_rest() {
let dir = temp_dir("preserve");
let sde_dir = dir.join("sde");
std::fs::create_dir_all(sde_dir.join("maps")).unwrap();
std::fs::write(sde_dir.join("maps").join("The_Forge.svg"), "old svg").unwrap();
std::fs::write(sde_dir.join("types.jsonl"), "old data").unwrap();
std::fs::create_dir_all(sde_dir.join("universe")).unwrap();
std::fs::write(
sde_dir.join("universe").join("region.jsonl"),
"more old data",
)
.unwrap();
clean_except_maps(&sde_dir).unwrap();
assert!(sde_dir.join("maps").exists(), "maps/ must survive");
assert!(
sde_dir.join("maps").join("The_Forge.svg").exists(),
"maps/'s content must not be touched either"
);
assert!(
!sde_dir.join("types.jsonl").exists(),
"types.jsonl must be removed"
);
assert!(
!sde_dir.join("universe").exists(),
"universe/ must be removed entirely"
);
}
#[test]
fn prepare_sde_directory_cleans_then_extracts() {
let dir = temp_dir("prepare");
let sde_dir = dir.join("sde");
std::fs::create_dir_all(sde_dir.join("maps")).unwrap();
std::fs::write(sde_dir.join("maps").join("Domain.svg"), "preserved svg").unwrap();
std::fs::write(sde_dir.join("old_data.jsonl"), "data from a previous build").unwrap();
let zip_path = dir.join("new_sde.zip");
build_test_zip(&zip_path, &[("types.jsonl", "{\"_key\": 1}\n")]);
prepare_sde_directory(&zip_path, &sde_dir).unwrap();
assert!(!sde_dir.join("old_data.jsonl").exists());
assert!(sde_dir.join("maps").join("Domain.svg").exists());
let types_content = std::fs::read_to_string(sde_dir.join("types.jsonl")).unwrap();
assert_eq!(types_content, "{\"_key\": 1}\n");
}
}