Skip to main content

zesven/streaming/
iterator.rs

1//! Entry iterator for streaming decompression.
2//!
3//! This module provides [`EntryIterator`] for iterating over archive entries
4//! with streaming decompression, and [`StreamingEntry`] for accessing entry
5//! data.
6
7use std::io::{self, Read, Seek, SeekFrom};
8
9use crate::format::parser::ArchiveHeader;
10use crate::format::streams::Folder;
11use crate::read::Entry;
12use crate::{Error, READ_BUFFER_SIZE, Result};
13
14#[cfg(feature = "aes")]
15use crate::Password;
16
17use super::config::StreamingConfig;
18
19/// Iterator that yields archive entries one at a time with streaming decompression.
20///
21/// This iterator processes entries sequentially, allowing for memory-efficient
22/// extraction of archives. For solid archives, entries must be processed in
23/// order due to compression dependencies.
24///
25/// # Example
26///
27/// ```rust,ignore
28/// use zesven::streaming::{StreamingArchive, StreamingConfig};
29///
30/// let mut archive = StreamingArchive::open(file)?;
31/// for entry_result in archive.entries() {
32///     let mut entry = entry_result?;
33///     if should_extract(entry.entry()) {
34///         entry.extract_to(&mut output_file)?;
35///     } else {
36///         entry.skip()?;
37///     }
38/// }
39/// ```
40pub struct EntryIterator<'a, R: Read + Seek> {
41    /// Reference to the archive header
42    header: &'a ArchiveHeader,
43    /// List of entries
44    entries: &'a [Entry],
45    /// Source reader
46    source: &'a mut R,
47    /// Password for encrypted archives
48    #[cfg(feature = "aes")]
49    #[allow(dead_code)] // Reserved for encrypted streaming support
50    password: &'a Password,
51    /// Streaming configuration
52    config: StreamingConfig,
53    /// Current entry index
54    current_index: usize,
55    /// Current folder index being processed
56    current_folder: Option<usize>,
57    /// Active folder decoder (for solid archives)
58    folder_decoder: Option<Box<dyn Read + Send + 'static>>,
59    /// Position within current folder's stream
60    stream_position_in_folder: usize,
61    /// Bytes remaining in current entry
62    bytes_remaining: u64,
63    /// Pack data start position in the archive
64    pack_start: u64,
65    /// Whether the iterator is exhausted
66    finished: bool,
67}
68
69impl<'a, R: Read + Seek + Send> EntryIterator<'a, R> {
70    /// Creates a new entry iterator.
71    #[cfg(feature = "aes")]
72    pub(crate) fn new(
73        header: &'a ArchiveHeader,
74        entries: &'a [Entry],
75        source: &'a mut R,
76        password: &'a Password,
77        config: StreamingConfig,
78    ) -> Result<Self> {
79        let pack_start = super::calculate_pack_start(header);
80
81        Ok(Self {
82            header,
83            entries,
84            source,
85            password,
86            config,
87            current_index: 0,
88            current_folder: None,
89            folder_decoder: None,
90            stream_position_in_folder: 0,
91            bytes_remaining: 0,
92            pack_start,
93            finished: false,
94        })
95    }
96
97    /// Creates a new entry iterator (without AES support).
98    #[cfg(not(feature = "aes"))]
99    pub(crate) fn new(
100        header: &'a ArchiveHeader,
101        entries: &'a [Entry],
102        source: &'a mut R,
103        config: StreamingConfig,
104    ) -> Result<Self> {
105        let pack_start = super::calculate_pack_start(header);
106
107        Ok(Self {
108            header,
109            entries,
110            source,
111            config,
112            current_index: 0,
113            current_folder: None,
114            folder_decoder: None,
115            stream_position_in_folder: 0,
116            bytes_remaining: 0,
117            pack_start,
118            finished: false,
119        })
120    }
121
122    /// Returns the total number of entries.
123    pub fn len(&self) -> usize {
124        self.entries.len()
125    }
126
127    /// Returns true if there are no entries.
128    pub fn is_empty(&self) -> bool {
129        self.entries.is_empty()
130    }
131
132    /// Returns the number of remaining entries.
133    pub fn remaining(&self) -> usize {
134        self.entries.len().saturating_sub(self.current_index)
135    }
136
137    /// Returns the streaming configuration.
138    pub fn config(&self) -> &StreamingConfig {
139        &self.config
140    }
141
142    fn next_internal(&mut self) -> Result<Option<StreamingEntry<'a>>> {
143        if self.finished || self.current_index >= self.entries.len() {
144            return Ok(None);
145        }
146
147        // Whatever the caller did not read from the previous entry still has to
148        // come out of the decoder, or every following entry in the same folder
149        // starts at the wrong place. The documentation promised this; only the
150        // call was missing.
151        if self.bytes_remaining > 0 {
152            self.skip_remaining()?;
153        }
154
155        let entry = &self.entries[self.current_index];
156        self.current_index += 1;
157
158        // Handle directories (no data to extract)
159        if entry.is_directory {
160            return Ok(Some(StreamingEntry::directory(entry)));
161        }
162
163        // Get folder and stream indices
164        let folder_index = match entry.folder_index {
165            Some(idx) => idx,
166            None => {
167                // Entry without folder - empty file
168                return Ok(Some(StreamingEntry::empty(entry)));
169            }
170        };
171
172        let stream_index = entry.stream_index.unwrap_or(0);
173
174        // Check if we need to switch folders
175        if self.current_folder != Some(folder_index) {
176            self.init_folder_decoder(folder_index)?;
177            self.stream_position_in_folder = 0;
178        }
179
180        // For solid archives, we may need to skip previous streams
181        while self.stream_position_in_folder < stream_index {
182            let skip_size = self.get_stream_size(folder_index, self.stream_position_in_folder);
183            self.skip_bytes(skip_size)?;
184            self.stream_position_in_folder += 1;
185        }
186
187        // Get the stream size
188        let size = self.get_stream_size(folder_index, stream_index);
189        self.bytes_remaining = size;
190        self.stream_position_in_folder = stream_index + 1;
191
192        // Create streaming entry
193        Ok(Some(StreamingEntry::with_size(entry, size)))
194    }
195
196    fn skip_bytes(&mut self, bytes: u64) -> Result<()> {
197        if let Some(decoder) = &mut self.folder_decoder {
198            io::copy(&mut decoder.take(bytes), &mut io::sink()).map_err(Error::Io)?;
199        }
200        Ok(())
201    }
202
203    fn init_folder_decoder(&mut self, folder_index: usize) -> Result<()> {
204        let folders = match &self.header.unpack_info {
205            Some(ui) => &ui.folders,
206            None => return Err(Error::InvalidFormat("missing unpack info".into())),
207        };
208
209        if folder_index >= folders.len() {
210            return Err(Error::InvalidFormat(format!(
211                "folder index {} out of range",
212                folder_index
213            )));
214        }
215
216        let folder = &folders[folder_index];
217
218        // Calculate folder position in pack data
219        let pack_offset = self.calculate_folder_offset(folder_index)?;
220        self.source
221            .seek(SeekFrom::Start(pack_offset))
222            .map_err(Error::Io)?;
223
224        // Build decoder chain
225        let decoder = self.build_folder_decoder(folder, folder_index)?;
226
227        self.folder_decoder = Some(decoder);
228        self.current_folder = Some(folder_index);
229
230        Ok(())
231    }
232
233    fn get_stream_size(&self, folder_index: usize, stream_index: usize) -> u64 {
234        // Calculate the linear index into unpack_sizes
235        let ss = match &self.header.substreams_info {
236            Some(ss) => ss,
237            None => {
238                // No substreams info - use folder unpack size
239                return self
240                    .header
241                    .unpack_info
242                    .as_ref()
243                    .and_then(|ui| ui.folders.get(folder_index))
244                    .and_then(|f| f.final_unpack_size())
245                    .unwrap_or(0);
246            }
247        };
248
249        // Calculate offset into unpack_sizes
250        let mut offset = 0usize;
251        for (i, &count) in ss.num_unpack_streams_in_folders.iter().enumerate() {
252            if i == folder_index {
253                return ss
254                    .unpack_sizes
255                    .get(offset + stream_index)
256                    .copied()
257                    .unwrap_or(0);
258            }
259            offset += count as usize;
260        }
261
262        0
263    }
264
265    fn calculate_folder_offset(&self, folder_index: usize) -> Result<u64> {
266        let pack_info = self
267            .header
268            .pack_info
269            .as_ref()
270            .ok_or_else(|| Error::InvalidFormat("missing pack info".into()))?;
271
272        let mut offset = self.pack_start;
273
274        // Sum up pack sizes for previous folders
275        for i in 0..folder_index {
276            if i < pack_info.pack_sizes.len() {
277                offset += pack_info.pack_sizes[i];
278            }
279        }
280
281        Ok(offset)
282    }
283
284    fn build_folder_decoder(
285        &mut self,
286        folder: &Folder,
287        folder_index: usize,
288    ) -> Result<Box<dyn Read + Send + 'static>> {
289        if folder.coders.is_empty() {
290            return Err(Error::InvalidFormat("folder has no coders".into()));
291        }
292
293        let uncompressed_size = folder.final_unpack_size().unwrap_or(0);
294
295        // The folder being switched to, not the one still recorded as current:
296        // reading the previous folder's pack size truncated or overran every
297        // folder after the first.
298        let pack_size = self
299            .header
300            .pack_info
301            .as_ref()
302            .and_then(|pi| pi.pack_sizes.get(folder_index).copied())
303            .unwrap_or(0);
304
305        // Read packed data into buffer to get 'static lifetime
306        let mut packed_data = vec![0u8; pack_size as usize];
307        self.source
308            .read_exact(&mut packed_data)
309            .map_err(Error::Io)?;
310
311        // Build the whole chain: a folder may filter, compress and encrypt, and
312        // decoding with its first coder alone yields plausible-looking rubbish.
313        let cursor = std::io::Cursor::new(packed_data);
314        #[cfg(feature = "aes")]
315        let decoder = crate::codec::build_folder_decoder_for(
316            cursor,
317            folder,
318            uncompressed_size,
319            Some(self.password),
320        )?;
321        #[cfg(not(feature = "aes"))]
322        let decoder = crate::codec::build_folder_decoder_for(cursor, folder, uncompressed_size)?;
323
324        Ok(decoder)
325    }
326
327    /// Reads data from the current entry.
328    ///
329    /// This is a low-level method for reading raw bytes from the current entry.
330    /// For most use cases, prefer [`Self::extract_current_to`] or [`Self::extract_current_to_vec`].
331    ///
332    /// # Arguments
333    ///
334    /// * `buf` - Buffer to read into
335    ///
336    /// # Returns
337    ///
338    /// The number of bytes read, or 0 if the entry has been fully read.
339    pub fn read_entry_data(&mut self, buf: &mut [u8]) -> io::Result<usize> {
340        if self.bytes_remaining == 0 {
341            return Ok(0);
342        }
343
344        let decoder = match &mut self.folder_decoder {
345            Some(d) => d,
346            None => return Ok(0),
347        };
348
349        let to_read = buf.len().min(self.bytes_remaining as usize);
350        let n = decoder.read(&mut buf[..to_read])?;
351        self.bytes_remaining -= n as u64;
352
353        Ok(n)
354    }
355
356    /// Skips the remaining bytes in the current entry.
357    pub(crate) fn skip_remaining(&mut self) -> Result<()> {
358        self.skip_bytes(self.bytes_remaining)?;
359        self.bytes_remaining = 0;
360        Ok(())
361    }
362
363    /// Extracts the current entry's data to a Write sink.
364    ///
365    /// This should be called after retrieving an entry via `next()` and before
366    /// calling `next()` again. For entries that should be skipped, simply call
367    /// `next()` without extracting - the iterator will automatically skip the
368    /// remaining bytes.
369    ///
370    /// # Arguments
371    ///
372    /// * `sink` - Any type implementing `Write` to receive the entry data
373    ///
374    /// # Returns
375    ///
376    /// The number of bytes written.
377    ///
378    /// # Example
379    ///
380    /// ```rust,ignore
381    /// let mut iter = archive.entries()?;
382    /// while let Some(entry_result) = iter.next() {
383    ///     let entry = entry_result?;
384    ///     if should_extract(&entry) {
385    ///         let mut file = File::create(entry.name())?;
386    ///         iter.extract_current_to(&mut file)?;
387    ///     }
388    ///     // If not extracted, the iterator will skip it automatically
389    /// }
390    /// ```
391    pub fn extract_current_to<W: io::Write>(&mut self, sink: &mut W) -> Result<u64> {
392        let mut total_written = 0u64;
393        let mut buf = [0u8; READ_BUFFER_SIZE];
394
395        loop {
396            let n = self.read_entry_data(&mut buf)?;
397            if n == 0 {
398                break;
399            }
400            sink.write_all(&buf[..n]).map_err(Error::Io)?;
401            total_written += n as u64;
402        }
403
404        Ok(total_written)
405    }
406
407    /// Extracts the current entry's data with a progress callback.
408    ///
409    /// # Arguments
410    ///
411    /// * `sink` - Any type implementing `Write` to receive the entry data
412    /// * `on_progress` - Callback called with (bytes_written, total_bytes)
413    ///
414    /// # Returns
415    ///
416    /// The number of bytes written.
417    pub fn extract_current_to_with_progress<W, F>(
418        &mut self,
419        sink: &mut W,
420        mut on_progress: F,
421    ) -> Result<u64>
422    where
423        W: io::Write,
424        F: FnMut(u64, u64),
425    {
426        let total = self.bytes_remaining;
427        let mut total_written = 0u64;
428        let mut buf = [0u8; READ_BUFFER_SIZE];
429
430        loop {
431            let n = self.read_entry_data(&mut buf)?;
432            if n == 0 {
433                break;
434            }
435            sink.write_all(&buf[..n]).map_err(Error::Io)?;
436            total_written += n as u64;
437            on_progress(total_written, total);
438        }
439
440        Ok(total_written)
441    }
442
443    /// Reads the current entry into a Vec.
444    ///
445    /// # Returns
446    ///
447    /// The decompressed entry data as a `Vec<u8>`.
448    pub fn extract_current_to_vec(&mut self) -> Result<Vec<u8>> {
449        let mut data = Vec::with_capacity(self.bytes_remaining as usize);
450        self.extract_current_to(&mut data)?;
451        Ok(data)
452    }
453
454    /// Returns the bytes remaining in the current entry.
455    pub fn current_entry_remaining(&self) -> u64 {
456        self.bytes_remaining
457    }
458}
459
460impl<'a, R: Read + Seek + Send> Iterator for EntryIterator<'a, R> {
461    type Item = Result<StreamingEntry<'a>>;
462
463    fn next(&mut self) -> Option<Self::Item> {
464        match self.next_internal() {
465            Ok(Some(entry)) => Some(Ok(entry)),
466            Ok(None) => None,
467            Err(e) => {
468                self.finished = true;
469                Some(Err(e))
470            }
471        }
472    }
473
474    fn size_hint(&self) -> (usize, Option<usize>) {
475        let remaining = self.remaining();
476        (remaining, Some(remaining))
477    }
478}
479
480impl<R: Read + Seek + Send> ExactSizeIterator for EntryIterator<'_, R> {}
481
482/// Represents a single entry during streaming iteration.
483///
484/// This provides access to entry metadata and methods for extracting
485/// or skipping the entry's data.
486#[allow(dead_code)] // buffer reserved for streaming implementation
487pub struct StreamingEntry<'a> {
488    /// Entry metadata
489    entry: &'a Entry,
490    /// Entry size
491    size: u64,
492    /// Whether this is a directory
493    is_directory: bool,
494    /// Bytes read so far
495    bytes_read: u64,
496    /// Internal buffer for reading
497    buffer: Vec<u8>,
498}
499
500impl<'a> StreamingEntry<'a> {
501    /// Creates a directory entry (no data).
502    fn directory(entry: &'a Entry) -> Self {
503        Self {
504            entry,
505            size: 0,
506            is_directory: true,
507            bytes_read: 0,
508            buffer: Vec::new(),
509        }
510    }
511
512    /// Creates an empty entry (no data).
513    fn empty(entry: &'a Entry) -> Self {
514        Self {
515            entry,
516            size: 0,
517            is_directory: false,
518            bytes_read: 0,
519            buffer: Vec::new(),
520        }
521    }
522
523    /// Creates an entry with a known size.
524    fn with_size(entry: &'a Entry, size: u64) -> Self {
525        Self {
526            entry,
527            size,
528            is_directory: false,
529            bytes_read: 0,
530            buffer: Vec::new(),
531        }
532    }
533
534    /// Returns the entry metadata.
535    pub fn entry(&self) -> &Entry {
536        self.entry
537    }
538
539    /// Returns true if this is a directory.
540    pub fn is_directory(&self) -> bool {
541        self.is_directory
542    }
543
544    /// Returns the uncompressed size of the entry.
545    pub fn size(&self) -> u64 {
546        self.size
547    }
548
549    /// Returns the entry name/path.
550    pub fn name(&self) -> &str {
551        self.entry.path.as_str()
552    }
553
554    /// Returns the bytes read so far.
555    pub fn bytes_read(&self) -> u64 {
556        self.bytes_read
557    }
558
559    /// Returns the remaining bytes to read.
560    pub fn remaining(&self) -> u64 {
561        self.size.saturating_sub(self.bytes_read)
562    }
563
564    /// Skips this entry without reading data.
565    ///
566    /// For solid archives, this still decompresses the data but discards it.
567    /// This method is a no-op - the actual skipping is handled by the iterator.
568    pub fn skip(self) -> Result<()> {
569        // Skipping is handled by the iterator when it advances
570        Ok(())
571    }
572}
573
574#[cfg(test)]
575mod tests {
576    use super::*;
577
578    #[test]
579    fn test_streaming_config_defaults() {
580        let config = StreamingConfig::default();
581        assert!(config.max_memory_buffer > 0);
582        assert!(config.read_buffer_size > 0);
583    }
584
585    #[test]
586    fn test_streaming_entry_directory() {
587        use crate::ArchivePath;
588
589        let entry = Entry {
590            path: ArchivePath::new("test").unwrap(),
591            is_directory: true,
592            size: 0,
593            crc32: None,
594            crc64: None,
595            modification_time: None,
596            creation_time: None,
597            access_time: None,
598            attributes: None,
599            is_encrypted: false,
600            is_symlink: false,
601            is_anti: false,
602            ownership: None,
603            index: 0,
604            folder_index: None,
605            stream_index: None,
606        };
607
608        let streaming = StreamingEntry::directory(&entry);
609        assert!(streaming.is_directory());
610        assert_eq!(streaming.size(), 0);
611    }
612}