use std::io::{Seek, Write};
use anyhow::Result;
use bitflags::bitflags;
use super::{AbstractData, LayerData};
use crate::{ar, tf};
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileFormatCaps: u8 {
const READ = 1 << 0;
const WRITE = 1 << 1;
const EDIT = 1 << 2;
}
}
impl FileFormatCaps {
pub fn can_read(self) -> bool {
self.contains(Self::READ)
}
pub fn can_write(self) -> bool {
self.contains(Self::WRITE)
}
pub fn can_edit(self) -> bool {
self.contains(Self::EDIT)
}
}
pub trait WriteSeek: Write + Seek {}
impl<T: Write + Seek + ?Sized> WriteSeek for T {}
pub trait FileFormat: Sync {
fn format_id(&self) -> tf::Token;
fn extensions(&self) -> &[&str];
fn caps(&self) -> FileFormatCaps {
FileFormatCaps::all()
}
fn read(&self, resolver: &dyn ar::Resolver, resolved: &ar::ResolvedPath) -> Result<LayerData>;
fn resolve_layer(&self, _resolver: &dyn ar::Resolver, resolved: &ar::ResolvedPath) -> Option<ar::ResolvedPath> {
Some(resolved.clone())
}
fn matches_content(&self, _prefix: &[u8]) -> bool {
false
}
fn write(&self, data: &dyn AbstractData, sink: &mut dyn WriteSeek) -> Result<()>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ar::{DefaultResolver, Resolver};
use crate::sdf::{self, SpecType};
use crate::usda::UsdaFileFormat;
use crate::usdc::UsdcFileFormat;
use crate::usdz::UsdzFileFormat;
fn sample_data() -> sdf::Data {
let mut data = sdf::Data::new();
let ps = data.create_spec(sdf::Path::abs_root(), SpecType::PseudoRoot);
ps.add("primChildren", sdf::Value::TokenVec(vec!["Foo".into()]));
let foo = sdf::path("/Foo").unwrap();
let sp = data.create_spec(foo, SpecType::Prim);
sp.add("specifier", sdf::Value::Specifier(sdf::Specifier::Def));
sp.add("typeName", sdf::Value::Token("Xform".into()));
data
}
fn roundtrip(format: &dyn FileFormat, ext: &str) {
let data = sample_data();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join(format!("rt.{ext}"));
let mut file = std::fs::File::create(&path).unwrap();
format.write(&data, &mut file).unwrap();
drop(file);
let resolver = DefaultResolver::new();
let resolved = resolver.resolve(path.to_str().unwrap()).unwrap();
let read = format.read(&resolver, &resolved).unwrap();
let foo = sdf::path("/Foo").unwrap();
assert_eq!(read.spec_type(&foo), Some(SpecType::Prim));
assert_eq!(
read.get_field(&foo, "typeName").unwrap().into_owned(),
sdf::Value::Token("Xform".into())
);
}
#[test]
fn roundtrip_usda() {
roundtrip(&UsdaFileFormat, "usda");
}
#[test]
fn roundtrip_usdc() {
roundtrip(&UsdcFileFormat, "usdc");
}
#[test]
fn roundtrip_usdz() {
roundtrip(&UsdzFileFormat, "usdz");
}
}