apt_parser/
packages.rs

1use crate::{
2	case_map::CaseMap,
3	control::Control,
4	errors::{APTError, MissingKeyError},
5};
6use rayon::prelude::*;
7use std::ops::Index;
8
9pub struct Package {
10	pub(crate) map: CaseMap,
11	pub package: String,
12	pub source: Option<String>,
13	pub version: String,
14	pub section: Option<String>,
15	pub priority: Option<String>,
16	pub architecture: String,
17	pub is_essential: Option<bool>,
18	pub depends: Option<Vec<String>>,
19	pub pre_depends: Option<Vec<String>>,
20	pub recommends: Option<Vec<String>>,
21	pub suggests: Option<Vec<String>>,
22	pub replaces: Option<Vec<String>>,
23	pub enhances: Option<Vec<String>>,
24	pub breaks: Option<Vec<String>>,
25	pub conflicts: Option<Vec<String>>,
26	pub installed_size: Option<i64>,
27	pub maintainer: Option<String>,
28	pub description: Option<String>,
29	pub homepage: Option<String>,
30	pub built_using: Option<String>,
31	pub package_type: Option<String>,
32	pub tags: Option<Vec<String>>,
33	pub filename: String,
34	pub size: i64,
35	pub md5sum: Option<String>,
36	pub sha1sum: Option<String>,
37	pub sha256sum: Option<String>,
38	pub sha512sum: Option<String>,
39	pub description_md5sum: Option<String>,
40}
41
42impl Package {
43	pub fn from(data: &str) -> Result<Package, APTError> {
44		let control = match Control::from(data) {
45			Ok(control) => control,
46			Err(err) => return Err(err),
47		};
48
49		let map = control.map;
50
51		let filename = match map.get("Filename") {
52			Some(filename) => filename.to_owned(),
53			None => {
54				return Err(APTError::MissingKeyError(MissingKeyError::new(
55					"Filename", data,
56				)))
57			}
58		};
59
60		let size = match map.get("Size") {
61			Some(size) => size.parse::<i64>().unwrap_or(-1),
62			None => {
63				return Err(APTError::MissingKeyError(MissingKeyError::new(
64					"Size", data,
65				)))
66			}
67		};
68
69		Ok(Package {
70			map: map.clone(),
71			package: control.package,
72			source: control.source,
73			version: control.version,
74			section: control.section,
75			priority: control.priority,
76			architecture: control.architecture,
77			is_essential: control.is_essential,
78			depends: control.depends,
79			pre_depends: control.pre_depends,
80			recommends: control.recommends,
81			suggests: control.suggests,
82			replaces: control.replaces,
83			enhances: control.enhances,
84			breaks: control.breaks,
85			conflicts: control.conflicts,
86			installed_size: control.installed_size,
87			maintainer: control.maintainer,
88			description: control.description,
89			homepage: control.homepage,
90			built_using: control.built_using,
91			package_type: control.package_type,
92			tags: control.tags,
93			filename,
94			size,
95			md5sum: map.get("MD5Sum").cloned(),
96			sha1sum: map.get("SHA1").cloned(),
97			sha256sum: map.get("SHA256").cloned(),
98			sha512sum: map.get("SHA512").cloned(),
99			description_md5sum: map.get("Description-md5").cloned(),
100		})
101	}
102
103	pub fn get(&self, key: &str) -> Option<&str> {
104		self.map.get(key).map(|x| &**x)
105	}
106}
107
108pub struct Packages {
109	pub(crate) packages: Vec<Package>,
110	pub errors: Vec<APTError>,
111}
112
113impl Packages {
114	pub fn from(data: &str) -> Packages {
115		let binding = data.replace("\r\n", "\n").replace('\0', "");
116		let iter = binding.trim().split("\n\n").par_bridge().into_par_iter();
117
118		let values = iter
119			.map(|package| Package::from(&package))
120			.collect::<Vec<Result<Package, APTError>>>();
121
122		let mut packages = Vec::new();
123		let mut errors = Vec::new();
124
125		for value in values {
126			match value {
127				Ok(package) => packages.push(package),
128				Err(err) => errors.push(err),
129			}
130		}
131
132		Packages { packages, errors }
133	}
134
135	pub fn len(&self) -> usize {
136		self.packages.len()
137	}
138}
139
140impl Iterator for Packages {
141	type Item = Package;
142
143	fn next(&mut self) -> Option<Self::Item> {
144		self.packages.pop()
145	}
146}
147
148impl Index<usize> for Packages {
149	type Output = Package;
150
151	fn index(&self, index: usize) -> &Self::Output {
152		&self.packages[index]
153	}
154}
155
156#[cfg(test)]
157mod tests {
158	use super::Packages;
159	use std::fs::read_to_string;
160
161	#[test]
162	fn packages_chariz() {
163		let file = "./test/chariz.packages";
164		let data = match read_to_string(file) {
165			Ok(data) => data,
166			Err(err) => panic!("Failed to read file: {}", err),
167		};
168
169		let packages = Packages::from(&data);
170		if !packages.errors.is_empty() {
171			panic!("Failed to parse packages: {:?}", packages.errors);
172		}
173
174		let control = &packages[0];
175		assert_eq!(packages.len(), 415);
176
177		assert_eq!(control.package, "arpoison");
178		assert_eq!(control.source, None);
179		assert_eq!(control.version, "0.7");
180		assert_eq!(control.section, Some("System".to_owned()));
181		assert_eq!(control.priority, None);
182		assert_eq!(control.architecture, "iphoneos-arm");
183		assert_eq!(control.is_essential, None);
184
185		assert_eq!(control.depends, Some(vec!["libnet9".to_owned()]));
186		assert_eq!(control.pre_depends, None);
187		assert_eq!(control.recommends, None);
188		assert_eq!(control.suggests, None);
189		assert_eq!(control.replaces, None);
190		assert_eq!(control.enhances, None);
191		assert_eq!(control.breaks, None);
192		assert_eq!(control.conflicts, None);
193
194		assert_eq!(control.installed_size, Some(88));
195		assert_eq!(
196			control.maintainer,
197			Some("MidnightChips <midnightchips@gmail.com>".to_owned())
198		);
199		assert_eq!(
200			control.description,
201			Some("Generates user-defined ARP packets".to_owned())
202		);
203		assert_eq!(
204			control.homepage,
205			Some("http://www.arpoison.net/".to_owned())
206		);
207		assert_eq!(control.built_using, None);
208		assert_eq!(control.package_type, None);
209		assert_eq!(
210			control.tags,
211			Some(vec![
212				"role::developer".to_owned(),
213				"compatible_min::ios14.0".to_owned(),
214			])
215		);
216
217		assert_eq!(control.filename, "debs/arpoison_0.7_iphoneos-arm.deb");
218		assert_eq!(control.size, 9618);
219		assert_eq!(
220			control.md5sum,
221			Some("e0be09b9f6d1c17371701d0ed6f625bf".to_owned())
222		);
223		assert_eq!(control.sha1sum, None);
224		assert_eq!(
225			control.sha256sum,
226			Some("9f9f615c50e917e0ce629966899ed28ba78fa637c5de5476aac34f630ab18dd5".to_owned())
227		);
228		assert_eq!(control.sha512sum, None);
229		assert_eq!(control.description_md5sum, None);
230
231		assert_eq!(
232			control.get("Depiction"),
233			Some("https://chariz.com/get/arpoison")
234		);
235
236		assert_eq!(
237			control.get("SileoDepiction"),
238			Some("https://repo.chariz.com/api/sileo/package/arpoison/depiction.json")
239		);
240
241		assert_eq!(
242			control.get("Author"),
243			Some("MidnightChips <midnightchips@gmail.com>")
244		);
245	}
246
247	#[test]
248	fn packages_jammy() {
249		let file = "./test/jammy.packages";
250		let data = match read_to_string(file) {
251			Ok(data) => data,
252			Err(err) => panic!("Failed to read file: {}", err),
253		};
254
255		let packages = Packages::from(&data);
256		if !packages.errors.is_empty() {
257			panic!("Failed to parse packages: {:?}", packages.errors);
258		}
259
260		let control = &packages[0];
261		assert_eq!(packages.len(), 6132);
262
263		assert_eq!(control.package, "accountsservice");
264		assert_eq!(control.source, None);
265		assert_eq!(control.version, "0.6.55-3ubuntu2");
266		assert_eq!(control.section, Some("gnome".to_owned()));
267		assert_eq!(control.priority, Some("optional".to_owned()));
268		assert_eq!(control.architecture, "amd64");
269		assert_eq!(control.is_essential, None);
270
271		assert_eq!(
272			control.depends,
273			Some(vec![
274				"dbus (>= 1.9.18)".to_owned(),
275				"libaccountsservice0 (= 0.6.55-3ubuntu2)".to_owned(),
276				"libc6 (>= 2.34)".to_owned(),
277				"libglib2.0-0 (>= 2.44)".to_owned(),
278				"libpolkit-gobject-1-0 (>= 0.99)".to_owned(),
279			])
280		);
281		assert_eq!(control.pre_depends, None);
282		assert_eq!(
283			control.recommends,
284			Some(vec!["default-logind | logind".to_owned()])
285		);
286		assert_eq!(
287			control.suggests,
288			Some(vec!["gnome-control-center".to_owned()])
289		);
290		assert_eq!(control.replaces, None);
291		assert_eq!(control.enhances, None);
292		assert_eq!(control.breaks, None);
293		assert_eq!(control.conflicts, None);
294
295		assert_eq!(control.installed_size, Some(484));
296		assert_eq!(
297			control.maintainer,
298			Some("Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>".to_owned())
299		);
300		assert_eq!(
301			control.description,
302			Some("query and manipulate user account information".to_owned())
303		);
304		assert_eq!(
305			control.homepage,
306			Some("https://www.freedesktop.org/wiki/Software/AccountsService/".to_owned())
307		);
308		assert_eq!(control.built_using, None);
309		assert_eq!(control.package_type, None);
310		assert_eq!(control.tags, None);
311
312		assert_eq!(
313			control.filename,
314			"pool/main/a/accountsservice/accountsservice_0.6.55-3ubuntu2_amd64.deb"
315		);
316		assert_eq!(control.size, 66304);
317		assert_eq!(
318			control.md5sum,
319			Some("d1dc884f3b039c09d9aaa317d6614582".to_owned())
320		);
321		assert_eq!(
322			control.sha1sum,
323			Some("f0c2c870146d05b8d53cd805527e942ca793ce38".to_owned())
324		);
325		assert_eq!(
326			control.sha256sum,
327			Some("9823e2e330e3ca986440eb5117574c29c1247efc4e8e23cd3b936013dff493b1".to_owned())
328		);
329		assert_eq!(control.sha512sum, Some("9d816378feaa1cb1135212b416321059b86ee622eccfd3e395b863e5b2ea976244c2b2c016b44f5bf6a30f18cd04406c0193f0da13ca296aac0212975f763bd7".to_owned()));
330		assert_eq!(
331			control.description_md5sum,
332			Some("8aeed0a03c7cd494f0c4b8d977483d7e".to_owned())
333		);
334
335		assert_eq!(control.get("Origin"), Some("Ubuntu"));
336
337		assert_eq!(
338			control.get("Original-Maintainer"),
339			Some("Debian freedesktop.org maintainers <pkg-freedesktop-maintainers@lists.alioth.debian.org>")
340		);
341
342		assert_eq!(
343			control.get("Bugs"),
344			Some("https://bugs.launchpad.net/ubuntu/+filebug")
345		);
346
347		assert_eq!(
348			control.get("Task"),
349			Some("ubuntu-desktop-minimal, ubuntu-desktop, ubuntu-desktop-raspi, kubuntu-desktop, xubuntu-core, xubuntu-desktop, lubuntu-desktop, ubuntustudio-desktop-core, ubuntustudio-desktop, ubuntukylin-desktop, ubuntu-mate-core, ubuntu-mate-desktop, ubuntu-budgie-desktop, ubuntu-budgie-desktop-raspi")
350		);
351	}
352}