Skip to main content

sevenz_turbo/reader/
positional_block.rs

1use super::*;
2
3/// An owned block decode that can move to a caller's worker pool.
4///
5/// Every decode owns an independent positional cursor. The archive and password
6/// are shared without cloning their contents; sharing the same `Arc<Password>`
7/// also shares its lifetime KDF budget and derived-key cache. No worker is
8/// spawned until a codec's explicitly requested thread count requires one.
9///
10/// The source must provide the same archive image that was parsed. A streaming
11/// consumer is responsible for admitting only covered packed ranges and for
12/// invalidating or cancelling work when those bytes are replaced by repair.
13/// Callback errors stop the decode and pass through unchanged, as they do for
14/// [`BlockDecoder`]. Codec workers are joined when their decode stack drops.
15pub struct PositionalBlockDecoder {
16    archive: Arc<Archive>,
17    password: Arc<Password>,
18    source: Arc<dyn ReadAt>,
19    block_index: usize,
20    limits: ArchiveLimits,
21    thread_count: u32,
22    adaptive_lzma2: bool,
23    verify_checksums: bool,
24    lzma2: Arc<Lzma2Control>,
25    on_sub_stream_complete: Option<Box<dyn FnMut(SubStreamCompletion) + Send>>,
26}
27
28impl PositionalBlockDecoder {
29    /// Prepares one block over a parsed archive and an independent cursor.
30    ///
31    /// Defaults to one codec thread and checksum verification. This allocates
32    /// no decoder dictionary and reads no packed bytes until the entry walk.
33    ///
34    /// # Errors
35    /// Returns an error if `block_index` is outside the parsed archive or its
36    /// data members are interspersed with no-stream entries. The latter cannot
37    /// be represented by the underlying block walk and requires another path.
38    pub fn new(
39        archive: Arc<Archive>,
40        password: Arc<Password>,
41        source: Arc<dyn ReadAt>,
42        block_index: usize,
43    ) -> Result<Self, Error> {
44        Self::with_limits(
45            archive,
46            password,
47            source,
48            block_index,
49            ArchiveLimits::default(),
50        )
51    }
52
53    /// As [`Self::new`], enforcing the same decoder and KDF limits as
54    /// [`BlockDecoder::with_limits`]. Concurrent decodes each obey these limits;
55    /// the caller must also bound their combined memory and worker count.
56    ///
57    /// # Errors
58    /// As [`Self::new`].
59    pub fn with_limits(
60        archive: Arc<Archive>,
61        password: Arc<Password>,
62        source: Arc<dyn ReadAt>,
63        block_index: usize,
64        limits: ArchiveLimits,
65    ) -> Result<Self, Error> {
66        if block_index >= archive.blocks.len() {
67            return Err(Error::invalid_argument("block index out of range"));
68        }
69        let start = *archive
70            .stream_map
71            .block_first_file_index
72            .get(block_index)
73            .ok_or_else(|| Error::corrupted("missing block file mapping"))?;
74        let count = archive.blocks[block_index].num_unpack_sub_streams;
75        let entries = start
76            .checked_add(count)
77            .and_then(|end| archive.files.get(start..end))
78            .ok_or_else(|| Error::corrupted("invalid block file mapping"))?;
79        if entries.iter().enumerate().any(|(offset, entry)| {
80            !entry.has_stream
81                || archive.stream_map.file_block_index.get(start + offset)
82                    != Some(&Some(block_index))
83        }) {
84            return Err(Error::unsupported(
85                "positional block walk requires contiguous data members",
86            ));
87        }
88        Ok(Self {
89            archive,
90            password,
91            source,
92            block_index,
93            limits,
94            thread_count: 1,
95            adaptive_lzma2: false,
96            verify_checksums: true,
97            lzma2: Arc::new(Lzma2Control::new(1)),
98            on_sub_stream_complete: None,
99        })
100    }
101
102    /// Sets this block's codec thread count, clamped to `1..=256`.
103    pub fn set_threads(&mut self, threads: u32) {
104        self.thread_count = threads.clamp(1, 256);
105        self.lzma2.set_threads(self.thread_count);
106    }
107
108    /// [`Self::set_threads`] for a builder chain.
109    #[must_use]
110    pub fn with_threads(mut self, threads: u32) -> Self {
111        self.set_threads(threads);
112        self
113    }
114
115    /// Enables later LZMA2 widening through [`Self::lzma2_handle`].
116    #[must_use]
117    pub fn with_adaptive_lzma2(mut self) -> Self {
118        self.adaptive_lzma2 = true;
119        self
120    }
121
122    /// Controls checksum verification, as on [`BlockDecoder`].
123    #[must_use]
124    pub fn with_verify_checksums(mut self, verify: bool) -> Self {
125        self.verify_checksums = verify;
126        self
127    }
128
129    /// Returns the control handle that remains usable while this decode runs.
130    pub fn lzma2_handle(&self) -> Lzma2Handle {
131        Lzma2Handle {
132            control: Arc::clone(&self.lzma2),
133        }
134    }
135
136    /// Installs the verified sub-stream completion hook for this block.
137    /// Hooks run on the thread executing the entry walk, in file order.
138    pub fn set_sub_stream_complete_hook(
139        &mut self,
140        hook: impl FnMut(SubStreamCompletion) + Send + 'static,
141    ) {
142        self.on_sub_stream_complete = Some(Box::new(hook));
143    }
144
145    /// Entries in this block, in archive order.
146    pub fn entries(&self) -> &[ArchiveEntry] {
147        let start = self.archive.stream_map.block_first_file_index[self.block_index];
148        let count = self.archive.blocks[self.block_index].num_unpack_sub_streams;
149        &self.archive.files[start..start + count]
150    }
151
152    /// Decodes on the calling thread, with ordinary [`Read`] callbacks.
153    /// Returns `false` when a callback stops the walk early.
154    ///
155    /// # Errors
156    /// The same located codec/checksum errors and unchanged callback errors
157    /// as [`BlockDecoder::for_each_entries`].
158    pub fn for_each_entries<F>(self, each: &mut F) -> Result<bool, Error>
159    where
160        F: FnMut(&ArchiveEntry, &mut dyn Read) -> Result<bool, Error>,
161    {
162        self.for_each_entries_direct(&mut |entry, reader| each(entry, reader))
163    }
164
165    /// Decodes on the calling thread with direct output delivery.
166    ///
167    /// # Errors
168    /// As [`Self::for_each_entries`].
169    pub fn for_each_entries_direct<F>(mut self, each: &mut F) -> Result<bool, Error>
170    where
171        F: FnMut(&ArchiveEntry, &mut dyn EntryRead) -> Result<bool, Error>,
172    {
173        let mut cursor = ReadAtCursor::new(Arc::clone(&self.source));
174        let decoder = BlockDecoder {
175            thread_count: self.thread_count,
176            adaptive_lzma2: self.adaptive_lzma2,
177            // Positional ownership alone does not certify download coverage.
178            input_complete: false,
179            positional: None,
180            verify_checksums: self.verify_checksums,
181            lzma2: self.lzma2,
182            block_index: self.block_index,
183            archive: &self.archive,
184            password: &self.password,
185            source: &mut cursor,
186            limits: self.limits,
187            on_sub_stream_complete: self
188                .on_sub_stream_complete
189                .as_mut()
190                .map(|hook| &mut **hook as &mut (dyn FnMut(SubStreamCompletion) + Send)),
191        };
192        decoder.for_each_entries_direct(each)
193    }
194}