1use std::collections::VecDeque;
14use std::io::{Read, Seek, SeekFrom};
15
16use rayon::prelude::*;
17
18use crate::central_dir;
19use crate::entry::{self, ZipEntry, ZipError};
20
21const BATCH_SIZE: usize = 64;
23
24const LOCAL_HEADER_SIG: u32 = 0x04034b50;
25
26pub struct StreamingZipRead<R: Read + Seek> {
41 source: R,
42 locations: Vec<crate::central_dir::EntryLocation>,
44 next_entry: usize,
45 buf: VecDeque<ZipEntry>,
46 error: bool,
47}
48
49impl<R: Read + Seek> StreamingZipRead<R> {
50 pub fn new(mut source: R) -> Result<Self, ZipError> {
55 let all_locs = central_dir::read_central_directory_from(&mut source)?;
56 let mut locations: Vec<_> = all_locs.into_iter().filter(|l| !l.is_directory).collect();
57 locations.sort_by_key(|l| l.local_header_offset);
58 Ok(Self {
59 source,
60 locations,
61 next_entry: 0,
62 buf: VecDeque::new(),
63 error: false,
64 })
65 }
66
67 pub fn with_filter(mut source: R, needle: &str) -> Result<Self, ZipError> {
72 let all_locs = central_dir::read_central_directory_from(&mut source)?;
73 let mut locations: Vec<_> = all_locs.into_iter()
74 .filter(|l| !l.is_directory && l.name.contains(needle))
75 .collect();
76 locations.sort_by_key(|l| l.local_header_offset);
77 Ok(Self {
78 source,
79 locations,
80 next_entry: 0,
81 buf: VecDeque::new(),
82 error: false,
83 })
84 }
85
86 pub fn with_filter_set(mut source: R, names: &[&str]) -> Result<Self, ZipError> {
90 use std::collections::HashSet;
91 let name_set: HashSet<&str> = names.iter().copied().collect();
92 let all_locs = central_dir::read_central_directory_from(&mut source)?;
93 let mut locations: Vec<_> = all_locs.into_iter()
94 .filter(|l| !l.is_directory && name_set.contains(l.name.as_str()))
95 .collect();
96 locations.sort_by_key(|l| l.local_header_offset);
97 Ok(Self {
98 source,
99 locations,
100 next_entry: 0,
101 buf: VecDeque::new(),
102 error: false,
103 })
104 }
105
106 pub fn with_filter_suffixes(mut source: R, suffixes: &[&str]) -> Result<Self, ZipError> {
110 let all_locs = central_dir::read_central_directory_from(&mut source)?;
111 let mut locations: Vec<_> = all_locs.into_iter()
112 .filter(|l| !l.is_directory && suffixes.iter().any(|s| l.name.ends_with(s)))
113 .collect();
114 locations.sort_by_key(|l| l.local_header_offset);
115 Ok(Self {
116 source,
117 locations,
118 next_entry: 0,
119 buf: VecDeque::new(),
120 error: false,
121 })
122 }
123
124 fn fill_batch(&mut self) -> Result<(), ZipError> {
125 let remaining = self.locations.len() - self.next_entry;
126 if remaining == 0 {
127 return Ok(());
128 }
129
130 let batch_end = self.next_entry + remaining.min(BATCH_SIZE);
131 let batch = &self.locations[self.next_entry..batch_end];
132
133 let mut raw: Vec<(String, Vec<u8>, u16, usize)> = Vec::with_capacity(batch.len());
135 for loc in batch {
136 self.source.seek(SeekFrom::Start(loc.local_header_offset))
137 .map_err(|_| ZipError("seek to local header failed"))?;
138
139 let mut hdr = [0u8; 30];
140 self.source.read_exact(&mut hdr)
141 .map_err(|_| ZipError("read local header failed"))?;
142
143 if u32::from_le_bytes(hdr[0..4].try_into().unwrap()) != LOCAL_HEADER_SIG {
144 return Err(ZipError("invalid local file header signature"));
145 }
146
147 let skip = u16::from_le_bytes([hdr[26], hdr[27]]) as i64
149 + u16::from_le_bytes([hdr[28], hdr[29]]) as i64;
150 self.source.seek(SeekFrom::Current(skip))
151 .map_err(|_| ZipError("seek past local header name/extra failed"))?;
152
153 let mut compressed = vec![0u8; loc.compressed_size as usize];
154 self.source.read_exact(&mut compressed)
155 .map_err(|_| ZipError("read compressed data failed"))?;
156
157 raw.push((loc.name.clone(), compressed, loc.compression_method, loc.uncompressed_size as usize));
158 }
159
160 let results: Vec<Result<ZipEntry, ZipError>> = crate::thread_pool().install(|| {
162 raw.par_iter().map(|(name, compressed, method, expected_size)| {
163 let data = entry::decompress_entry_raw(compressed, *method, *expected_size)?;
164 Ok(ZipEntry { name: name.clone(), data })
165 }).collect()
166 });
167
168 for r in results {
170 self.buf.push_back(r?);
171 }
172 self.next_entry = batch_end;
173 Ok(())
174 }
175}
176
177impl<R: Read + Seek> Iterator for StreamingZipRead<R> {
178 type Item = Result<ZipEntry, ZipError>;
179
180 fn next(&mut self) -> Option<Self::Item> {
181 if self.error {
182 return None;
183 }
184 if let Some(entry) = self.buf.pop_front() {
185 return Some(Ok(entry));
186 }
187 if self.next_entry >= self.locations.len() {
188 return None;
189 }
190 match self.fill_batch() {
191 Err(e) => {
192 self.error = true;
193 Some(Err(e))
194 }
195 Ok(()) => self.buf.pop_front().map(Ok),
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use std::io::{Cursor, Write};
204
205 fn make_jar(items: &[(&str, &[u8])]) -> Vec<u8> {
206 use zip::write::SimpleFileOptions;
207 let mut buf = Vec::new();
208 let mut zw = zip::ZipWriter::new(Cursor::new(&mut buf));
209 let opts = SimpleFileOptions::default()
210 .compression_method(zip::CompressionMethod::Deflated);
211 for (name, data) in items {
212 zw.start_file(*name, opts).unwrap();
213 zw.write_all(data).unwrap();
214 }
215 zw.finish().unwrap();
216 buf
217 }
218
219 #[test]
220 fn streaming_single() {
221 let jar = make_jar(&[("Hello.class", b"cafebabe")]);
222 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
223 .unwrap()
224 .collect::<Result<Vec<_>, _>>()
225 .unwrap();
226 assert_eq!(entries.len(), 1);
227 assert_eq!(entries[0].name, "Hello.class");
228 assert_eq!(entries[0].data, b"cafebabe");
229 }
230
231 #[test]
232 fn streaming_multi() {
233 let jar = make_jar(&[("a.txt", b"aaa"), ("b.txt", b"bbb"), ("c.txt", b"ccc")]);
234 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
235 .unwrap()
236 .collect::<Result<Vec<_>, _>>()
237 .unwrap();
238 assert_eq!(entries.len(), 3);
239 }
240
241 #[test]
242 fn streaming_skips_directories() {
243 let jar = make_jar(&[("META-INF/", b""), ("META-INF/MANIFEST.MF", b"Manifest-Version: 1.0\n")]);
244 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
245 .unwrap()
246 .collect::<Result<Vec<_>, _>>()
247 .unwrap();
248 assert_eq!(entries.len(), 1);
249 assert_eq!(entries[0].name, "META-INF/MANIFEST.MF");
250 }
251
252 #[test]
253 fn streaming_many_batches() {
254 let items: Vec<(String, Vec<u8>)> = (0..200)
255 .map(|i| (format!("Class{i}.class"), format!("data{i}").into_bytes()))
256 .collect();
257 let item_refs: Vec<(&str, &[u8])> = items.iter().map(|(n, d)| (n.as_str(), d.as_slice())).collect();
258 let jar = make_jar(&item_refs);
259
260 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
261 .unwrap()
262 .collect::<Result<Vec<_>, _>>()
263 .unwrap();
264 assert_eq!(entries.len(), 200);
265 }
266
267 #[test]
268 fn streaming_matches_parallel() {
269 let items: Vec<(String, Vec<u8>)> = (0..100)
270 .map(|i| (format!("{i}.txt"), format!("content {i}").into_bytes()))
271 .collect();
272 let item_refs: Vec<(&str, &[u8])> = items.iter().map(|(n, d)| (n.as_str(), d.as_slice())).collect();
273 let jar = make_jar(&item_refs);
274
275 let par = crate::parallel::decompress_parallel(&jar).unwrap();
276 let stream: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
277 .unwrap()
278 .collect::<Result<Vec<_>, _>>()
279 .unwrap();
280
281 let mut par_sorted = par;
283 par_sorted.sort_by(|a, b| a.name.cmp(&b.name));
284 let mut stream_sorted = stream;
285 stream_sorted.sort_by(|a, b| a.name.cmp(&b.name));
286
287 assert_eq!(par_sorted.len(), stream_sorted.len());
288 for (p, s) in par_sorted.iter().zip(stream_sorted.iter()) {
289 assert_eq!(p.name, s.name);
290 assert_eq!(p.data, s.data);
291 }
292 }
293}