1use {
8 crate::{distribution::Bundle, PkgResult},
9 serde::{Deserialize, Serialize},
10 std::io::Read,
11};
12
13#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
18#[serde(rename_all = "kebab-case")]
19pub struct PackageInfo {
20 pub auth: String,
24
25 #[serde(rename = "deleteObsoleteLanguages")]
26 pub delete_obsolete_languages: Option<bool>,
27
28 #[serde(rename = "followSymLinks")]
31 pub follow_symlinks: Option<bool>,
32
33 pub format_version: u8,
37
38 pub generator_version: Option<String>,
40
41 pub identifier: String,
45
46 pub install_location: Option<String>,
48
49 #[serde(rename = "minimumSystemVersion")]
51 pub minimum_system_version: Option<bool>,
52
53 pub overwrite_permissions: Option<bool>,
55
56 pub postinstall_action: Option<String>,
60
61 pub preserve_xattr: Option<bool>,
63
64 pub relocatable: Option<bool>,
69
70 #[serde(rename = "useHFSPlusCompression")]
72 pub use_hfs_plus_compression: Option<bool>,
73
74 pub version: String,
79
80 #[serde(default)]
82 pub atomic_update_bundle: Vec<BundleRef>,
83
84 #[serde(default)]
86 pub bundle: Vec<Bundle>,
87
88 #[serde(default)]
89 pub bundle_version: Vec<BundleRef>,
90
91 #[serde(default)]
93 pub dont_obsolete: Vec<File>,
94
95 #[serde(default)]
97 pub install_at_startup: Vec<File>,
98
99 #[serde(default)]
101 pub patch: Vec<File>,
102
103 pub payload: Option<Payload>,
105
106 #[serde(default)]
107 pub relocate: Vec<BundleRef>,
108
109 #[serde(default)]
111 pub scripts: Scripts,
112
113 #[serde(default)]
114 pub strict_identifiers: Vec<BundleRef>,
115
116 #[serde(default)]
117 pub update_bundle: Vec<BundleRef>,
118
119 #[serde(default)]
120 pub upgrade_bundle: Vec<BundleRef>,
121}
122
123impl Default for PackageInfo {
124 fn default() -> Self {
125 Self {
126 auth: "none".into(),
127 delete_obsolete_languages: None,
128 follow_symlinks: None,
129 format_version: 2,
130 generator_version: Some("rust-apple-flat-package".to_string()),
131 identifier: "".to_string(),
132 install_location: None,
133 minimum_system_version: None,
134 overwrite_permissions: None,
135 postinstall_action: None,
136 preserve_xattr: None,
137 relocatable: None,
138 use_hfs_plus_compression: None,
139 version: "0".to_string(),
140 atomic_update_bundle: vec![],
141 bundle: vec![],
142 bundle_version: vec![],
143 dont_obsolete: vec![],
144 install_at_startup: vec![],
145 patch: vec![],
146 payload: None,
147 relocate: vec![],
148 scripts: Default::default(),
149 strict_identifiers: vec![],
150 update_bundle: vec![],
151 upgrade_bundle: vec![],
152 }
153 }
154}
155
156impl PackageInfo {
157 pub fn from_reader(reader: impl Read) -> PkgResult<Self> {
159 let mut de = serde_xml_rs::Deserializer::new_from_reader(reader);
160
161 Ok(Self::deserialize(&mut de)?)
162 }
163
164 pub fn from_xml(s: &str) -> PkgResult<Self> {
166 let mut de = serde_xml_rs::Deserializer::new_from_reader(s.as_bytes())
167 .non_contiguous_seq_elements(true);
168
169 Ok(Self::deserialize(&mut de)?)
170 }
171}
172
173#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
175#[serde(rename_all = "kebab-case")]
176pub struct File {
177 pub path: String,
179
180 pub required_sha1: Option<String>,
182
183 pub sha1: Option<String>,
185}
186
187#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
188pub struct Payload {
189 #[serde(rename = "numberOfFiles")]
190 pub number_of_files: u64,
191 #[serde(rename = "installKBytes")]
192 pub install_kbytes: u64,
193}
194
195#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
196pub struct BundleRef {
197 pub id: Option<String>,
198}
199
200#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
202pub struct Scripts {
203 #[serde(rename = "$value")]
204 pub scripts: Vec<Script>,
205}
206
207#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
209pub enum Script {
210 #[serde(rename = "preinstall")]
211 PreInstall(PreInstall),
212
213 #[serde(rename = "postinstall")]
214 PostInstall(PostInstall),
215}
216
217#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
219pub struct PreInstall {
220 pub file: String,
222
223 pub component_id: Option<String>,
225}
226
227#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
229pub struct PostInstall {
230 pub file: String,
232
233 pub component_id: Option<String>,
235}
236
237#[cfg(test)]
238mod test {
239 use super::*;
240
241 #[test]
242 fn scripts_decode() {
243 const INPUT: &str = r#"
244 <?xml version="1.0" encoding="utf-8"?>
245 <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">
246 <payload numberOfFiles="123" installKBytes="123"/>
247 <bundle-version/>
248 <upgrade-bundle/>
249 <update-bundle/>
250 <atomic-update-bundle/>
251 <strict-identifier/>
252 <relocate/>
253 <scripts>
254 <preinstall file="./preinstall"/>
255 <postinstall file="./postinstall"/>
256 </scripts>
257 </pkg-info>
258 "#;
259
260 let info = PackageInfo::from_xml(INPUT.trim()).unwrap();
261
262 assert_eq!(
263 info.scripts.scripts,
264 vec![
265 Script::PreInstall(PreInstall {
266 file: "./preinstall".into(),
267 component_id: None,
268 }),
269 Script::PostInstall(PostInstall {
270 file: "./postinstall".into(),
271 component_id: None,
272 })
273 ]
274 );
275 }
276}