use {
crate::{distribution::Bundle, PkgResult},
serde::{Deserialize, Serialize},
std::io::Read,
};
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct PackageInfo {
pub auth: String,
#[serde(rename = "deleteObsoleteLanguages")]
pub delete_obsolete_languages: Option<bool>,
#[serde(rename = "followSymLinks")]
pub follow_symlinks: Option<bool>,
pub format_version: u8,
pub generator_version: Option<String>,
pub identifier: String,
pub install_location: Option<String>,
#[serde(rename = "minimumSystemVersion")]
pub minimum_system_version: Option<bool>,
pub overwrite_permissions: Option<bool>,
pub postinstall_action: Option<String>,
pub preserve_xattr: Option<bool>,
pub relocatable: Option<bool>,
#[serde(rename = "useHFSPlusCompression")]
pub use_hfs_plus_compression: Option<bool>,
pub version: String,
#[serde(default)]
pub atomic_update_bundle: Vec<BundleRef>,
#[serde(default)]
pub bundle: Vec<Bundle>,
#[serde(default)]
pub bundle_version: Vec<BundleRef>,
#[serde(default)]
pub dont_obsolete: Vec<File>,
#[serde(default)]
pub install_at_startup: Vec<File>,
#[serde(default)]
pub patch: Vec<File>,
pub payload: Option<Payload>,
#[serde(default)]
pub relocate: Vec<BundleRef>,
#[serde(default)]
pub scripts: Scripts,
#[serde(default)]
pub strict_identifiers: Vec<BundleRef>,
#[serde(default)]
pub update_bundle: Vec<BundleRef>,
#[serde(default)]
pub upgrade_bundle: Vec<BundleRef>,
}
impl Default for PackageInfo {
fn default() -> Self {
Self {
auth: "none".into(),
delete_obsolete_languages: None,
follow_symlinks: None,
format_version: 2,
generator_version: Some("rust-apple-flat-package".to_string()),
identifier: "".to_string(),
install_location: None,
minimum_system_version: None,
overwrite_permissions: None,
postinstall_action: None,
preserve_xattr: None,
relocatable: None,
use_hfs_plus_compression: None,
version: "0".to_string(),
atomic_update_bundle: vec![],
bundle: vec![],
bundle_version: vec![],
dont_obsolete: vec![],
install_at_startup: vec![],
patch: vec![],
payload: None,
relocate: vec![],
scripts: Default::default(),
strict_identifiers: vec![],
update_bundle: vec![],
upgrade_bundle: vec![],
}
}
}
impl PackageInfo {
pub fn from_reader(reader: impl Read) -> PkgResult<Self> {
let mut de = serde_xml_rs::Deserializer::new_from_reader(reader);
Ok(Self::deserialize(&mut de)?)
}
pub fn from_xml(s: &str) -> PkgResult<Self> {
let mut de = serde_xml_rs::Deserializer::new_from_reader(s.as_bytes())
.non_contiguous_seq_elements(true);
Ok(Self::deserialize(&mut de)?)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct File {
pub path: String,
pub required_sha1: Option<String>,
pub sha1: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct Payload {
#[serde(rename = "numberOfFiles")]
pub number_of_files: u64,
#[serde(rename = "installKBytes")]
pub install_kbytes: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct BundleRef {
pub id: Option<String>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct Scripts {
#[serde(rename = "$value")]
pub scripts: Vec<Script>,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub enum Script {
#[serde(rename = "preinstall")]
PreInstall(PreInstall),
#[serde(rename = "postinstall")]
PostInstall(PostInstall),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct PreInstall {
pub file: String,
pub component_id: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct PostInstall {
pub file: String,
pub component_id: Option<String>,
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn scripts_decode() {
const INPUT: &str = r#"
<?xml version="1.0" encoding="utf-8"?>
<pkg-info overwrite-permissions="true" relocatable="false" identifier="my-app" postinstall-action="none" version="1" format-version="2" generator-version="InstallCmds-807 (21D62)" install-location="/usr/bin/my-app" auth="root">
<payload numberOfFiles="123" installKBytes="123"/>
<bundle-version/>
<upgrade-bundle/>
<update-bundle/>
<atomic-update-bundle/>
<strict-identifier/>
<relocate/>
<scripts>
<preinstall file="./preinstall"/>
<postinstall file="./postinstall"/>
</scripts>
</pkg-info>
"#;
let info = PackageInfo::from_xml(INPUT.trim()).unwrap();
assert_eq!(
info.scripts.scripts,
vec![
Script::PreInstall(PreInstall {
file: "./preinstall".into(),
component_id: None,
}),
Script::PostInstall(PostInstall {
file: "./postinstall".into(),
component_id: None,
})
]
);
}
}