use crate::upgrade::CdnzUpgradeError;
use super::*;
use std::{
collections::BTreeMap,
io::{self, Read},
};
use serde::{Deserializer, Serializer};
use tar::{Archive, Builder, Header};
#[derive(Debug, Serialize, Deserialize)]
pub struct VersionInfo<'a> {
pub cdnz_version: &'a str,
pub cadenza_version: &'a str,
}
const CURRENT_VERSION: VersionInfo = VersionInfo {
cdnz_version: "0.1.0",
cadenza_version: env!("CARGO_PKG_VERSION"),
};
#[derive(Debug, thiserror::Error)]
pub enum CdnzSerError {
#[error("IO error: {0}")]
IoError(#[from] io::Error),
#[error("Serde error: {0}")]
SerdeError(#[from] serde_json::Error),
}
#[derive(Debug, thiserror::Error)]
pub enum CdnzDeError {
#[error("IO error: {0}")]
IoError(#[from] io::Error),
#[error("Serde error: {0}")]
SerdeError(#[from] serde_json::Error),
#[error("Error upgrading file: {0}")]
UpgradeError(#[from] CdnzUpgradeError),
#[error("Missing or empty `data.json.zst` for CDNZ file")]
MissingOrEmptyDataJsonZst,
#[error("Missing or empty `data.json` for CDNX file")]
MissingOrEmptyDataJson,
#[error("Missing or incorrect `mimetype`")]
MissingOrIncorrectMimetype,
}
impl Cdnz {
pub fn serialize(&self) -> Result<Vec<u8>, CdnzSerError> {
let mut buffer = Vec::new();
{
let mut tar = Builder::new(&mut buffer);
let data_json = serde_json::to_string_pretty(self)?;
let data_json_zstd = zstd::encode_all(data_json.as_bytes(), 0)?;
let mut header = Header::new_gnu();
header.set_path("data.json.zst")?;
header.set_size(data_json_zstd.len() as u64);
header.set_cksum();
tar.append_data(&mut header, "data.json.zst", &data_json_zstd[..])?;
let version_json = serde_json::to_string_pretty(&CURRENT_VERSION)?;
let mut header = Header::new_gnu();
header.set_path("version.json")?;
header.set_size(version_json.len() as u64);
header.set_cksum();
tar.append_data(&mut header, "version.json", version_json.as_bytes())?;
let mimetype = "application/vnd.cadenza.cdnz";
let mut header = Header::new_gnu();
header.set_path("mimetype")?;
header.set_size(mimetype.len() as u64);
header.set_cksum();
tar.append_data(&mut header, "mimetype", mimetype.as_bytes())?;
tar.finish()?;
}
Ok(buffer)
}
pub fn serialize_no_compress(&self) -> Result<Vec<u8>, CdnzSerError> {
let mut buffer = Vec::new();
{
let mut tar = Builder::new(&mut buffer);
let data_json = serde_json::to_string_pretty(self)?;
let mut header = Header::new_gnu();
header.set_path("data.json")?;
header.set_size(data_json.len() as u64);
header.set_cksum();
tar.append_data(&mut header, "data.json", data_json.as_bytes())?;
let version_json = serde_json::to_string_pretty(&CURRENT_VERSION)?;
let mut header = Header::new_gnu();
header.set_path("version.json")?;
header.set_size(version_json.len() as u64);
header.set_cksum();
tar.append_data(&mut header, "version.json", version_json.as_bytes())?;
let mimetype = "application/vnd.cadenza.cdnz";
let mut header = Header::new_gnu();
header.set_path("mimetype")?;
header.set_size(mimetype.len() as u64);
header.set_cksum();
tar.append_data(&mut header, "mimetype", mimetype.as_bytes())?;
tar.finish()?;
}
Ok(buffer)
}
pub fn deserialize<R: Read>(reader: R) -> Result<Self, CdnzDeError> {
let data_json = Cdnz::deserialize_json(reader)?;
Ok(serde_json::from_str(&data_json)?)
}
pub fn deserialize_json<R: Read>(reader: R) -> Result<String, CdnzDeError> {
let mut archive = Archive::new(reader);
let mut data_json = String::new();
let mut data_json_zst = String::new();
let mut version_json = String::new();
let mut mimetype = String::new();
for entry in archive.entries()? {
let mut entry = entry?;
let target = match entry.path()?.to_str() {
Some("mimetype") => &mut mimetype,
Some("version.json") => &mut version_json,
Some("data.json") => &mut data_json,
Some("data.json.zst") => &mut data_json_zst,
_ => continue,
};
entry.read_to_string(target)?;
}
if mimetype.trim() == "application/vnd.cadenza.cdnz" {
if data_json_zst == "" {
return Err(CdnzDeError::MissingOrEmptyDataJsonZst);
}
let decompressed = zstd::decode_all(data_json_zst.as_bytes())?;
data_json = String::from_utf8_lossy(&decompressed).to_string();
} else if mimetype.trim() == "application/vnd.cadenza.cdnx" {
if data_json == "" {
return Err(CdnzDeError::MissingOrEmptyDataJson);
}
} else {
return Err(CdnzDeError::MissingOrIncorrectMimetype);
}
let version_info: VersionInfo = serde_json::from_str(&version_json)?;
upgrade::upgrade_json(&data_json, version_info, CURRENT_VERSION)?;
Ok(data_json)
}
pub fn validate<R: Read>(reader: R) -> Result<(), CdnzDeError> {
let mut archive = Archive::new(reader);
let mut data_json = String::new();
let mut data_json_zst = String::new();
let mut version_json = String::new();
let mut mimetype = String::new();
for entry in archive.entries()? {
let mut entry = entry?;
let target = match entry.path()?.to_str() {
Some("mimetype") => &mut mimetype,
Some("version.json") => &mut version_json,
Some("data.json") => &mut data_json,
Some("data.json.zst") => &mut data_json_zst,
_ => continue,
};
entry.read_to_string(target)?;
}
if mimetype.trim() == "application/vnd.cadenza.cdnz" {
if data_json_zst == "" {
return Err(CdnzDeError::MissingOrEmptyDataJsonZst);
}
let decompressed = zstd::decode_all(data_json_zst.as_bytes())?;
data_json = String::from_utf8_lossy(&decompressed).to_string();
} else if mimetype.trim() == "application/vnd.cadenza.cdnx" {
if data_json == "" {
return Err(CdnzDeError::MissingOrEmptyDataJson);
}
} else {
return Err(CdnzDeError::MissingOrIncorrectMimetype);
}
let version_info: VersionInfo = serde_json::from_str(&version_json)?;
upgrade::upgrade_json(&data_json, version_info, CURRENT_VERSION)?;
Ok(())
}
}
pub(super) fn serialize_position_map<S>(
map: &BTreeMap<Position, Vec<GlobalModEvent>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
map.iter().collect::<Vec<_>>().serialize(serializer)
}
pub(super) fn deserialize_position_map<'de, D>(
deserializer: D,
) -> Result<BTreeMap<Position, Vec<GlobalModEvent>>, D::Error>
where
D: Deserializer<'de>,
{
Vec::<(Position, Vec<GlobalModEvent>)>::deserialize(deserializer)
.map(|v| v.into_iter().collect())
}