#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ZipExportConfig {
pub comment: String,
pub compress: bool,
pub compression_level: u8,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ZipEntry {
pub name: String,
pub size_bytes: u64,
pub data: Vec<u8>,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ZipBundle {
pub config: ZipExportConfig,
pub entries: Vec<ZipEntry>,
}
#[allow(dead_code)]
pub fn default_zip_config() -> ZipExportConfig {
ZipExportConfig {
comment: String::new(),
compress: false,
compression_level: 0,
}
}
#[allow(dead_code)]
pub fn new_zip_bundle(cfg: &ZipExportConfig) -> ZipBundle {
ZipBundle {
config: cfg.clone(),
entries: Vec::new(),
}
}
#[allow(dead_code)]
pub fn zip_add_entry(bundle: &mut ZipBundle, name: &str, size_bytes: u64, data: Vec<u8>) {
if let Some(entry) = bundle.entries.iter_mut().find(|e| e.name == name) {
entry.size_bytes = size_bytes;
entry.data = data;
return;
}
bundle.entries.push(ZipEntry {
name: name.to_string(),
size_bytes,
data,
});
}
#[allow(dead_code)]
pub fn zip_remove_entry(bundle: &mut ZipBundle, name: &str) -> bool {
let before = bundle.entries.len();
bundle.entries.retain(|e| e.name != name);
bundle.entries.len() < before
}
#[allow(dead_code)]
pub fn zip_bundle_clear(bundle: &mut ZipBundle) {
bundle.entries.clear();
}
#[allow(dead_code)]
pub fn zip_entry_count(bundle: &ZipBundle) -> usize {
bundle.entries.len()
}
#[allow(dead_code)]
pub fn zip_total_size(bundle: &ZipBundle) -> u64 {
bundle.entries.iter().map(|e| e.size_bytes).sum()
}
#[allow(dead_code)]
pub fn zip_find_entry<'a>(bundle: &'a ZipBundle, name: &str) -> Option<&'a ZipEntry> {
bundle.entries.iter().find(|e| e.name == name)
}
#[allow(dead_code)]
pub fn zip_to_bytes_stub(bundle: &ZipBundle) -> Vec<u8> {
let mut out: Vec<u8> = Vec::new();
out.extend_from_slice(b"OXZP");
out.extend_from_slice(&(bundle.entries.len() as u32).to_le_bytes());
for entry in &bundle.entries {
let name_bytes = entry.name.as_bytes();
out.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
out.extend_from_slice(name_bytes);
out.extend_from_slice(&entry.size_bytes.to_le_bytes());
out.extend_from_slice(&(entry.data.len() as u32).to_le_bytes());
out.extend_from_slice(&entry.data);
}
out
}
#[allow(dead_code)]
pub fn zip_write_to_file(bundle: &ZipBundle, path: &str) -> Result<(), String> {
use std::io::Write;
let bytes = zip_to_bytes_stub(bundle);
let mut file =
std::fs::File::create(path).map_err(|e| format!("zip_write_to_file: {}", e))?;
file.write_all(&bytes)
.map_err(|e| format!("zip_write_to_file write: {}", e))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let cfg = default_zip_config();
assert!(!cfg.compress);
assert_eq!(cfg.compression_level, 0);
assert!(cfg.comment.is_empty());
}
#[test]
fn test_new_bundle_empty() {
let cfg = default_zip_config();
let bundle = new_zip_bundle(&cfg);
assert_eq!(zip_entry_count(&bundle), 0);
assert_eq!(zip_total_size(&bundle), 0);
}
#[test]
fn test_add_entry_and_count() {
let cfg = default_zip_config();
let mut bundle = new_zip_bundle(&cfg);
zip_add_entry(&mut bundle, "mesh.glb", 1024, vec![0u8; 16]);
assert_eq!(zip_entry_count(&bundle), 1);
assert_eq!(zip_total_size(&bundle), 1024);
}
#[test]
fn test_add_entry_replaces_existing() {
let cfg = default_zip_config();
let mut bundle = new_zip_bundle(&cfg);
zip_add_entry(&mut bundle, "file.txt", 100, vec![1u8; 4]);
zip_add_entry(&mut bundle, "file.txt", 200, vec![2u8; 8]);
assert_eq!(zip_entry_count(&bundle), 1);
assert_eq!(zip_total_size(&bundle), 200);
}
#[test]
fn test_find_entry() {
let cfg = default_zip_config();
let mut bundle = new_zip_bundle(&cfg);
zip_add_entry(&mut bundle, "model.obj", 512, vec![42u8; 10]);
let found = zip_find_entry(&bundle, "model.obj");
assert!(found.is_some());
assert_eq!(found.expect("should succeed").size_bytes, 512);
}
#[test]
fn test_find_missing_entry() {
let cfg = default_zip_config();
let bundle = new_zip_bundle(&cfg);
assert!(zip_find_entry(&bundle, "nope.glb").is_none());
}
#[test]
fn test_remove_entry() {
let cfg = default_zip_config();
let mut bundle = new_zip_bundle(&cfg);
zip_add_entry(&mut bundle, "a.txt", 10, vec![0u8; 2]);
zip_add_entry(&mut bundle, "b.txt", 20, vec![0u8; 4]);
let removed = zip_remove_entry(&mut bundle, "a.txt");
assert!(removed);
assert_eq!(zip_entry_count(&bundle), 1);
}
#[test]
fn test_remove_missing_returns_false() {
let cfg = default_zip_config();
let mut bundle = new_zip_bundle(&cfg);
assert!(!zip_remove_entry(&mut bundle, "ghost.txt"));
}
#[test]
fn test_bundle_clear() {
let cfg = default_zip_config();
let mut bundle = new_zip_bundle(&cfg);
zip_add_entry(&mut bundle, "x.txt", 5, vec![0u8]);
zip_add_entry(&mut bundle, "y.txt", 7, vec![0u8]);
zip_bundle_clear(&mut bundle);
assert_eq!(zip_entry_count(&bundle), 0);
}
#[test]
fn test_to_bytes_stub_magic() {
let cfg = default_zip_config();
let bundle = new_zip_bundle(&cfg);
let bytes = zip_to_bytes_stub(&bundle);
assert_eq!(&bytes[0..4], b"OXZP");
}
}