lzip_parallel/
parallel.rs1use rayon::prelude::*;
10
11use crate::central_dir;
12use crate::entry::{self, ZipEntry, ZipError};
13
14pub fn decompress_parallel(data: &[u8]) -> Result<Vec<ZipEntry>, ZipError> {
20 decompress_parallel_filter(data, "")
21}
22
23pub fn decompress_parallel_filter(data: &[u8], needle: &str) -> Result<Vec<ZipEntry>, ZipError> {
28 let locations = central_dir::read_central_directory(data)?;
29
30 let results: Vec<Result<Option<ZipEntry>, ZipError>> = crate::thread_pool().install(|| {
31 locations
32 .par_iter()
33 .map(|loc| {
34 if loc.is_directory {
35 return Ok(None);
36 }
37 if !needle.is_empty() && !loc.name.contains(needle) {
38 return Ok(None);
39 }
40 let bytes = entry::decompress_entry(data, loc)?;
41 Ok(Some(ZipEntry { name: loc.name.clone(), data: bytes }))
42 })
43 .collect()
44 });
45
46 let mut entries = Vec::with_capacity(locations.len());
47 for r in results {
48 if let Some(e) = r? {
49 entries.push(e);
50 }
51 }
52 Ok(entries)
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use std::io::Write;
59
60 fn make_jar(items: &[(&str, &[u8])]) -> Vec<u8> {
61 use std::io::Cursor;
62 use zip::write::SimpleFileOptions;
63 let mut buf = Vec::new();
64 let mut zw = zip::ZipWriter::new(Cursor::new(&mut buf));
65 let opts = SimpleFileOptions::default()
66 .compression_method(zip::CompressionMethod::Deflated);
67 for (name, data) in items {
68 zw.start_file(*name, opts).unwrap();
69 zw.write_all(data).unwrap();
70 }
71 zw.finish().unwrap();
72 buf
73 }
74
75 #[test]
76 fn parallel_single() {
77 let jar = make_jar(&[("Hello.class", b"cafebabe deadbeef")]);
78 let entries = decompress_parallel(&jar).unwrap();
79 assert_eq!(entries.len(), 1);
80 assert_eq!(entries[0].name, "Hello.class");
81 assert_eq!(entries[0].data, b"cafebabe deadbeef");
82 }
83
84 #[test]
85 fn parallel_multi() {
86 let jar = make_jar(&[
87 ("a.txt", b"aaa"),
88 ("b.txt", b"bbb"),
89 ("c.txt", b"ccc"),
90 ]);
91 let entries = decompress_parallel(&jar).unwrap();
92 assert_eq!(entries.len(), 3);
93 assert_eq!(entries[0].data, b"aaa");
94 assert_eq!(entries[1].data, b"bbb");
95 assert_eq!(entries[2].data, b"ccc");
96 }
97
98 #[test]
99 fn parallel_skips_directories() {
100 let jar = make_jar(&[
101 ("META-INF/", b""),
102 ("META-INF/MANIFEST.MF", b"Manifest-Version: 1.0\n"),
103 ]);
104 let entries = decompress_parallel(&jar).unwrap();
105 assert_eq!(entries.len(), 1);
106 assert_eq!(entries[0].name, "META-INF/MANIFEST.MF");
107 }
108
109 #[test]
110 fn filter_by_needle() {
111 let jar = make_jar(&[
112 ("com/example/App.class", b"bytecode"),
113 ("META-INF/maven/com.example/app/pom.xml", b"<project/>"),
114 ("META-INF/MANIFEST.MF", b"Manifest-Version: 1.0\n"),
115 ]);
116 let entries = decompress_parallel_filter(&jar, "pom.xml").unwrap();
117 assert_eq!(entries.len(), 1);
118 assert_eq!(entries[0].name, "META-INF/maven/com.example/app/pom.xml");
119 assert_eq!(entries[0].data, b"<project/>");
120 }
121
122 #[test]
123 fn filter_empty_matches_all() {
124 let jar = make_jar(&[("a.txt", b"a"), ("b.txt", b"b")]);
125 let entries = decompress_parallel_filter(&jar, "").unwrap();
126 assert_eq!(entries.len(), 2);
127 }
128}