use std::{error::Error, fmt::Display, fs::File, io::{self, Read, Seek, Write}, path::{Path, PathBuf}};
use zip::{read::root_dir_common_filter, write::SimpleFileOptions};
use crate::{category::{self}, game, item, location, meta, options, region::Regions, APWorld};
#[derive(Debug)]
pub enum APIOError{
FileError(io::Error),
ZipError(zip::result::ZipError),
SerdeError(serde_json::Error),
}
impl Display for APIOError{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self{
Self::FileError(v)=>v.fmt(f),
Self::ZipError(v)=>v.fmt(f),
Self::SerdeError(v)=>v.fmt(f)
}
}
}
impl Error for APIOError{
}
impl From<io::Error> for APIOError{
fn from(value: io::Error) -> Self {
Self::FileError(value)
}
}
impl From<zip::result::ZipError> for APIOError{
fn from(value: zip::result::ZipError) -> Self {
Self::ZipError(value)
}
}
impl From<serde_json::Error> for APIOError{
fn from(value: serde_json::Error) -> Self {
Self::SerdeError(value)
}
}
#[must_use]
pub fn read_apworld<R: Read+Seek>(reader: R)->Result<APWorld,APIOError>{
let mut archive = zip::ZipArchive::new(reader)?;
let r=archive.root_dir(root_dir_common_filter)?.ok_or(APIOError::ZipError(zip::result::ZipError::FileNotFound))?;
let r=r.join("data");
let categories = archive.by_path(r.join("categories.json"))?;
let categories: category::Categories=serde_json::from_reader(categories)?;
let game = archive.by_path(r.join("game.json"))?;
let game: game::Game=serde_json::from_reader(game)?;
let items: Vec<item::Item> = serde_json::from_reader(archive.by_path(r.join("items.json"))?)?;
let locations: Vec<location::Location> = serde_json::from_reader(archive.by_path(r.join("locations.json"))?)?;
let meta: meta::Meta = serde_json::from_reader(archive.by_path(r.join("meta.json"))?)?;
let options: options::Options = serde_json::from_reader(archive.by_path(r.join("options.json"))?)?;
let regions: Regions = serde_json::from_reader(archive.by_path(r.join("regions.json"))?)?;
Ok(crate::APWorld{
category: categories,
game, items, locations, meta, options, regions
})
}
#[must_use]
pub fn read_apworld_from_file(path: &Path)->Result<APWorld,APIOError>{
let f= File::open(path)?;
read_apworld(f)
}
#[must_use]
pub fn write_apworld<W: Write+Seek>(writer: W, stem: &str,world:&APWorld,method: zip::CompressionMethod)->Result<(),APIOError>{
let mut zip = zip::ZipWriter::new(writer);
let opts = SimpleFileOptions::default().compression_method(method).unix_permissions(0o755);
let tl =Path::new(stem);
zip.add_directory_from_path(tl, opts)?;
let r: PathBuf=tl.join(Path::new("data"));
zip.add_directory_from_path(&r, opts)?;
zip.start_file_from_path(r.join("categories.json"),opts )?;
serde_json::to_writer(&mut zip,&world.category)?;
zip.start_file_from_path(r.join("game.json"), opts)?;
serde_json::to_writer(&mut zip, &world.game)?;
zip.start_file_from_path(r.join("items.json"), opts)?;
serde_json::to_writer(&mut zip, &world.items)?;
zip.start_file_from_path(r.join("locations.json"), opts)?;
serde_json::to_writer(&mut zip, &world.locations)?;
zip.start_file_from_path(r.join("meta.json"), opts)?;
serde_json::to_writer(&mut zip, &world.meta)?;
zip.start_file_from_path(r.join("options.json"),opts)?;
serde_json::to_writer(&mut zip, &world.options)?;
zip.start_file_from_path(r.join("regions.json"), opts)?;
serde_json::to_writer(&mut zip, &world.regions)?;
let r: PathBuf=tl.join(Path::new("hooks"));
zip.add_directory_from_path(&r, opts)?;
zip.start_file_from_path(r.join("__init__.py"),opts)?;
zip.write_all(include_bytes!("static_files/hooks/__init__.py"))?; zip.start_file_from_path(r.join("Data.py"), opts)?;
zip.write_all(include_bytes!("static_files/hooks/Data.py"))?;
zip.start_file_from_path(r.join("Helpers.py"), opts)?;
zip.write_all(include_bytes!("static_files/hooks/Helpers.py"))?;
zip.start_file_from_path(r.join("Options.py"), opts)?;
zip.write_all(include_bytes!("static_files/hooks/Options.py"))?;
zip.start_file_from_path(r.join("Rules.py"), opts)?;
zip.write_all(include_bytes!("static_files/hooks/Rules.py"))?;
zip.start_file_from_path(r.join("World.py"), opts)?;
zip.write_all(include_bytes!("static_files/hooks/World.py"))?;
zip.start_file_from_path(tl.join("__init__.py"),opts)?;
zip.write_all(include_bytes!("static_files/__init__.py"))?;
zip.start_file_from_path(tl.join("Data.py"),opts)?;
zip.write_all(include_bytes!("static_files/Data.py"))?;
zip.start_file_from_path(tl.join("DataValidation.py"), opts)?;
zip.write_all(include_bytes!("static_files/DataValidation.py"))?;
zip.start_file_from_path(tl.join("Game.py"), opts)?;
zip.write_all(include_bytes!("static_files/Game.py"))?;
zip.start_file_from_path(tl.join("Helpers.py"), opts)?;
zip.write_all(include_bytes!("static_files/Helpers.py"))?;
zip.start_file_from_path(tl.join("Items.py"), opts)?;
zip.write_all(include_bytes!("static_files/Items.py"))?;
zip.start_file_from_path(tl.join("Locations.py"), opts)?;
zip.write_all(include_bytes!("static_files/Locations.py"))?;
zip.start_file_from_path(tl.join("manual_test.py"), opts)?;
zip.write_all(include_bytes!("static_files/manual_test.py"))?;
zip.start_file_from_path(tl.join("ManualClient.py"), opts)?;
zip.write_all(include_bytes!("static_files/ManualClient.py"))?;
zip.start_file_from_path(tl.join("Meta.py"), opts)?;
zip.write_all(include_bytes!("static_files/Meta.py"))?;
zip.start_file_from_path(tl.join("Options.py"), opts)?;
zip.write_all(include_bytes!("static_files/Options.py"))?;
zip.start_file_from_path(tl.join("Regions.py"), opts)?;
zip.write_all(include_bytes!("static_files/Regions.py"))?;
zip.start_file_from_path(tl.join("Rules.py"), opts)?;
zip.write_all(include_bytes!("static_files/Rules.py"))?;
zip.finish()?;
Ok(())
}
#[must_use]
pub fn write_apworld_to_file(path: &Path,world:&APWorld,method: zip::CompressionMethod)->Result<(),APIOError>{
let f=File::open(path)?;
let stem = path.file_stem()
.ok_or(APIOError::FileError(io::Error::new(io::ErrorKind::InvalidFilename, "File has no stem")))?;
let stem = stem.to_str().ok_or(io::Error::new(io::ErrorKind::InvalidFilename,"Non unicode filename"))?;
write_apworld(f, stem, world, method)
}