use std::{
fs::File,
io::{self, Cursor, Read, Seek},
path::Path,
};
use zip::ZipArchive;
use super::ArchiveError;
use crate::{ar, sdf, usda, usdc};
pub struct Archive<R: Read + Seek = File> {
archive: ZipArchive<R>,
}
impl Archive<File> {
pub fn open(path: impl AsRef<Path>) -> Result<Self, ArchiveError> {
let path = path.as_ref();
let file = File::open(path)
.map_err(|error| io::Error::new(error.kind(), format!("unable to open {}: {error}", path.display())))?;
let archive = ZipArchive::new(file)?;
Ok(Self { archive })
}
}
impl Archive<Cursor<Vec<u8>>> {
pub fn from_asset(resolver: &dyn ar::Resolver, resolved: &ar::ResolvedPath) -> Result<Self, ArchiveError> {
let bytes = resolver.open_asset(resolved)?.read_all()?;
Archive::from_reader(Cursor::new(bytes))
}
}
impl<R: Read + Seek> Archive<R> {
pub fn from_reader(reader: R) -> Result<Self, ArchiveError> {
let archive = ZipArchive::new(reader)?;
Ok(Self { archive })
}
pub fn first_layer_name(&self) -> Option<String> {
self.archive
.file_names()
.find(|name| name.ends_with(".usdc") || name.ends_with(".usda") || name.ends_with(".usd"))
.map(String::from)
}
pub fn read_first_layer(&mut self) -> Result<Box<dyn sdf::AbstractData>, ArchiveError> {
let name = self.first_layer_name().ok_or(ArchiveError::NoDefaultLayer)?;
self.read(&name)
}
pub fn read(&mut self, file_path: &str) -> Result<Box<dyn sdf::AbstractData>, ArchiveError> {
let mut file = self
.archive
.by_name(file_path)
.map_err(|e| ArchiveError::entry(file_path, e))?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)
.map_err(|e| ArchiveError::entry(file_path, e))?;
if file_path.ends_with(".usdz") {
return Err(ArchiveError::NestedPackage {
path: file_path.to_owned(),
});
}
let is_crate = if file_path.ends_with(".usdc") {
true
} else if file_path.ends_with(".usda") {
false
} else {
buffer.starts_with(usdc::MAGIC)
};
if is_crate {
let data =
usdc::CrateData::open(Cursor::new(buffer), true).map_err(|e| ArchiveError::entry(file_path, e))?;
Ok(Box::new(data))
} else {
let content = String::from_utf8(buffer).map_err(|e| ArchiveError::Utf8 {
name: file_path.to_owned(),
source: e.utf8_error(),
})?;
let data = usda::parse(&content).map_err(|error| error.with_source_name(file_path))?;
Ok(Box::new(data))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Result;
#[test]
fn test_open_usdz() -> Result<()> {
let mut archive = Archive::open("fixtures/test.usdz")?;
let data = archive.read("file_1.usdc")?;
let root = sdf::Path::abs_root();
assert!(data.has_spec(&root));
assert_eq!(data.spec_type(&root), Some(sdf::SpecType::PseudoRoot));
Ok(())
}
}