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 len = loc.compressed_size as usize;
156 let mut compressed = Vec::with_capacity(len);
157 unsafe { compressed.set_len(len); }
160 self.source.read_exact(&mut compressed)
161 .map_err(|_| ZipError("read compressed data failed"))?;
162
163 raw.push((loc.name.clone(), compressed, loc.compression_method, loc.uncompressed_size as usize));
164 }
165
166 let results: Vec<Result<ZipEntry, ZipError>> = crate::thread_pool().install(|| {
170 raw.into_par_iter().map(|(name, compressed, method, expected_size)| {
171 let data = entry::decompress_entry_raw(&compressed, method, expected_size)?;
172 Ok(ZipEntry { name, data })
173 }).collect()
174 });
175
176 for r in results {
178 self.buf.push_back(r?);
179 }
180 self.next_entry = batch_end;
181 Ok(())
182 }
183}
184
185impl<R: Read + Seek> Iterator for StreamingZipRead<R> {
186 type Item = Result<ZipEntry, ZipError>;
187
188 fn next(&mut self) -> Option<Self::Item> {
189 if self.error {
190 return None;
191 }
192 if let Some(entry) = self.buf.pop_front() {
193 return Some(Ok(entry));
194 }
195 if self.next_entry >= self.locations.len() {
196 return None;
197 }
198 match self.fill_batch() {
199 Err(e) => {
200 self.error = true;
201 Some(Err(e))
202 }
203 Ok(()) => self.buf.pop_front().map(Ok),
204 }
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use std::io::{Cursor, Write};
212
213 fn make_jar(items: &[(&str, &[u8])]) -> Vec<u8> {
214 use zip::write::SimpleFileOptions;
215 let mut buf = Vec::new();
216 let mut zw = zip::ZipWriter::new(Cursor::new(&mut buf));
217 let opts = SimpleFileOptions::default()
218 .compression_method(zip::CompressionMethod::Deflated);
219 for (name, data) in items {
220 zw.start_file(*name, opts).unwrap();
221 zw.write_all(data).unwrap();
222 }
223 zw.finish().unwrap();
224 buf
225 }
226
227 #[test]
228 fn streaming_single() {
229 let jar = make_jar(&[("Hello.class", b"cafebabe")]);
230 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
231 .unwrap()
232 .collect::<Result<Vec<_>, _>>()
233 .unwrap();
234 assert_eq!(entries.len(), 1);
235 assert_eq!(entries[0].name, "Hello.class");
236 assert_eq!(entries[0].data, b"cafebabe");
237 }
238
239 #[test]
240 fn streaming_multi() {
241 let jar = make_jar(&[("a.txt", b"aaa"), ("b.txt", b"bbb"), ("c.txt", b"ccc")]);
242 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
243 .unwrap()
244 .collect::<Result<Vec<_>, _>>()
245 .unwrap();
246 assert_eq!(entries.len(), 3);
247 }
248
249 #[test]
250 fn streaming_skips_directories() {
251 let jar = make_jar(&[("META-INF/", b""), ("META-INF/MANIFEST.MF", b"Manifest-Version: 1.0\n")]);
252 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
253 .unwrap()
254 .collect::<Result<Vec<_>, _>>()
255 .unwrap();
256 assert_eq!(entries.len(), 1);
257 assert_eq!(entries[0].name, "META-INF/MANIFEST.MF");
258 }
259
260 #[test]
261 fn streaming_many_batches() {
262 let items: Vec<(String, Vec<u8>)> = (0..200)
263 .map(|i| (format!("Class{i}.class"), format!("data{i}").into_bytes()))
264 .collect();
265 let item_refs: Vec<(&str, &[u8])> = items.iter().map(|(n, d)| (n.as_str(), d.as_slice())).collect();
266 let jar = make_jar(&item_refs);
267
268 let entries: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
269 .unwrap()
270 .collect::<Result<Vec<_>, _>>()
271 .unwrap();
272 assert_eq!(entries.len(), 200);
273 }
274
275 #[test]
276 fn streaming_matches_parallel() {
277 let items: Vec<(String, Vec<u8>)> = (0..100)
278 .map(|i| (format!("{i}.txt"), format!("content {i}").into_bytes()))
279 .collect();
280 let item_refs: Vec<(&str, &[u8])> = items.iter().map(|(n, d)| (n.as_str(), d.as_slice())).collect();
281 let jar = make_jar(&item_refs);
282
283 let par = crate::parallel::decompress_parallel(&jar).unwrap();
284 let stream: Vec<_> = StreamingZipRead::new(Cursor::new(&jar))
285 .unwrap()
286 .collect::<Result<Vec<_>, _>>()
287 .unwrap();
288
289 let mut par_sorted = par;
291 par_sorted.sort_by(|a, b| a.name.cmp(&b.name));
292 let mut stream_sorted = stream;
293 stream_sorted.sort_by(|a, b| a.name.cmp(&b.name));
294
295 assert_eq!(par_sorted.len(), stream_sorted.len());
296 for (p, s) in par_sorted.iter().zip(stream_sorted.iter()) {
297 assert_eq!(p.name, s.name);
298 assert_eq!(p.data, s.data);
299 }
300 }
301}