chapter_tgz/lib.rs
1//! [![github]](https://github.com/dtolnay/chapter-tgz) [![crates-io]](https://crates.io/crates/chapter-tgz) [![docs-rs]](https://docs.rs/chapter-tgz)
2//!
3//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
6//!
7//! <br>
8//!
9//! This is a library for creating and consuming specially crafted .tar.gz files
10//! with the following properties:
11//!
12//! 1. **Efficient access to specific predefined points in the tar** ("chapter
13//! boundaries"). A chapter consists of zero or more consecutive tar entries.
14//! This can be used to skip over groups of tar entries in _O(1)_ time
15//! without performing the work of gzip decompression on the intervening
16//! entries.
17//!
18//! 2. **Parallel decompression:** different chapters of the same tgz can be
19//! read simultaneously by different threads. Extracting a later entry is not
20//! stalled on processing all previous entries as in a conventional tgz file.
21//!
22//! 3. **Perfectly compatible with existing readers** that do not know about
23//! chapter information. All existing software will be able to read these
24//! files as ordinary tgz files. Chapter information is encoded in the form
25//! of valid empty gzip blocks with peculiar Huffman code alphabets.
26//!
27//! 4. **Perfectly compatible with existing writers** that do not embed chapter
28//! information. Tgz files without chapter information are handled as if
29//! there was a single chapter encompassing all of their entries.
30//!
31//! Refer to example code on [`TgzReader`] that demonstrates leveraging chapter
32//! information to skip over entries or process chapters on a thread pool.
33
34#![allow(
35 clippy::cast_possible_truncation,
36 clippy::cast_possible_wrap,
37 clippy::doc_markdown,
38 clippy::elidable_lifetime_names,
39 clippy::missing_errors_doc,
40 clippy::missing_panics_doc,
41 clippy::must_use_candidate,
42 clippy::unreadable_literal
43)]
44
45mod count;
46mod decode;
47mod encode;
48mod independent;
49pub mod io;
50mod refmut;
51
52use crate::count::Count;
53use crate::decode::{decode_from_end, decode_from_start};
54use crate::encode::encode;
55use crate::io::{ChapterReader, ChapterReaderImpl, ChapterWriter};
56use crate::refmut::RefMut;
57use flate2::Crc;
58use flate2::read::{DeflateDecoder, GzDecoder};
59use flate2::write::DeflateEncoder;
60use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom, Write};
61use std::mem;
62
63pub extern crate tar;
64
65pub use crate::independent::IndependentRead;
66#[doc(no_inline)]
67pub use flate2::Compression;
68
69/// Compressor for producing .tar.gz files with embedded chapter information.
70///
71/// Most of the API surface area is in the [`tar`] crate.
72///
73/// # Example
74///
75/// This example creates a tgz with several large files containing random data,
76/// each in its own chapter.
77///
78/// ```no_run
79/// use chapter_tgz::{Compression, TgzWriter};
80/// use rand::RngReader;
81/// use rand::rngs::SmallRng;
82/// use std::fs;
83/// use std::io::{self, Read as _};
84///
85/// fn main() -> io::Result<()> {
86/// let mut tgz = TgzWriter::new(Vec::new(), Compression::fast());
87/// let mut rng: SmallRng = rand::make_rng();
88/// for i in 0..20 {
89/// let mut chapter = tgz.create_chapter();
90/// let mut header = tar::Header::new_gnu();
91/// header.set_size(100_000_000);
92/// let path = format!("random/{i}");
93/// let data = RngReader(&mut rng).take(100_000_000);
94/// chapter.append_data(&mut header, path, data)?;
95/// }
96/// let compressed = tgz.into_inner()?;
97/// fs::write("example.tar.gz", compressed)?;
98/// Ok(())
99/// }
100/// ```
101pub struct TgzWriter<W: Write> {
102 state: TgzWriterState<W>,
103 crc: Crc,
104 level: Compression,
105 chapters: u32,
106 boundaries_written: u32,
107 last_boundary: u64,
108}
109
110enum TgzWriterState<W: Write> {
111 Empty(W),
112 Chapter(DeflateEncoder<Count<W>>),
113 Finished(W),
114 Failed(ErrorKind),
115}
116
117/// Decompressor for reading .tar.gz files containing embedded chapter
118/// information.
119///
120/// Tgz files without chapter information will simply be observed to have a
121/// single chapter encompassing their entire contents.
122pub struct TgzReader<R> {
123 data: R,
124 boundaries: Vec<u64>,
125 chapter: u32,
126}
127
128static GZIP_HEADER: [u8; 10] = [
129 0x1F, // ID1 (magic number)
130 0x8B, // ID2
131 8, // CM (compression method = Deflate)
132 0, // FLG (flags)
133 0, // MTIME 1/4
134 0, // MTIME 2/4
135 0, // MTIME 3/4
136 0, // MTIME 4/4
137 0, // XFL (extra flags = none)
138 255, // OS (filesystem = unknown)
139];
140
141static TAR_TERMINATION_SECTIONS: [u8; 1024] = [0; 1024];
142
143impl<W> TgzWriter<W>
144where
145 W: Write,
146{
147 pub fn new(out: W, level: Compression) -> Self {
148 TgzWriter {
149 state: TgzWriterState::Empty(out),
150 crc: Crc::new(),
151 level,
152 chapters: 0,
153 boundaries_written: 0,
154 last_boundary: 0,
155 }
156 }
157
158 /// Begin writing the next chapters.
159 ///
160 /// A chapter may contain zero or more tar entries.
161 ///
162 /// The total number of chapters is limited to u32::MAX.
163 ///
164 /// # Panics
165 ///
166 /// Panics if 4,294,967,295 previous chapters have already been created in
167 /// this tgz.
168 #[track_caller]
169 pub fn create_chapter(&mut self) -> tar::Builder<ChapterWriter<'_, W>> {
170 if let TgzWriterState::Finished(_) = self.state {
171 panic!("called create_chapter on a TgzWriter that is already finished");
172 }
173
174 let index = self.chapters;
175 self.chapters = self.chapters.strict_add(1);
176
177 tar::Builder::new(ChapterWriter {
178 tgz: self,
179 index,
180 deferred_termination_sections: false,
181 })
182 }
183
184 /// Number of chapters created so far.
185 pub fn chapters(&self) -> u32 {
186 self.chapters
187 }
188
189 /// Flush the last chapter information to the underlying writer.
190 ///
191 /// This is done automatically by `Drop`, but calling it explicitly allows
192 /// any error to be handled. `Drop` would ignore the error.
193 ///
194 /// # Panics
195 ///
196 /// Panics if `finish` has already been called on this tgz.
197 #[track_caller]
198 pub fn finish(&mut self) -> Result<()> {
199 if let TgzWriterState::Finished(_) = self.state {
200 panic!("TgzWriter got finished twice");
201 }
202
203 self.do_finish()?;
204 Ok(())
205 }
206
207 /// Flush the last chapter information to the underlying writer and return
208 /// the writer object.
209 ///
210 /// It is not necessary to call `finish` before `into_inner`.
211 pub fn into_inner(mut self) -> Result<W> {
212 self.do_finish()?;
213 let TgzWriterState::Finished(writer) =
214 mem::replace(&mut self.state, TgzWriterState::Failed(ErrorKind::Other))
215 else {
216 unreachable!()
217 };
218 Ok(writer)
219 }
220
221 fn write_boundary(&mut self) -> Result<()> {
222 match mem::replace(&mut self.state, TgzWriterState::Failed(ErrorKind::Other)) {
223 TgzWriterState::Empty(mut writer) => {
224 writer.write_all(&GZIP_HEADER)?;
225 self.boundaries_written += 1;
226 self.state =
227 TgzWriterState::Chapter(DeflateEncoder::new(Count::new(writer), self.level));
228 Ok(())
229 }
230 TgzWriterState::Chapter(out) => {
231 let mut writer = out.flush_finish()?;
232 let bfinal = false;
233 let payload = writer.position().strict_sub(self.last_boundary);
234 self.last_boundary = writer.position();
235 let boundary = encode(bfinal, payload);
236 writer.write_all(boundary.as_slice())?;
237 self.boundaries_written += 1;
238 self.state = TgzWriterState::Chapter(DeflateEncoder::new(writer, self.level));
239 Ok(())
240 }
241 TgzWriterState::Finished(_) => unreachable!(),
242 TgzWriterState::Failed(kind) => Err(Error::new(kind, "TgzWriter error")),
243 }
244 }
245
246 fn do_finish(&mut self) -> Result<()> {
247 while self.boundaries_written == 0 || self.boundaries_written < self.chapters {
248 self.write_boundary()?;
249 }
250
251 match mem::replace(&mut self.state, TgzWriterState::Failed(ErrorKind::Other)) {
252 TgzWriterState::Empty(_) => unreachable!(),
253 TgzWriterState::Chapter(mut out) => {
254 out.write_all(&TAR_TERMINATION_SECTIONS)?;
255 self.crc.update(&TAR_TERMINATION_SECTIONS);
256 let writer = out.flush_finish()?;
257 let bfinal = true;
258 let mut payload = writer.position().strict_sub(self.last_boundary);
259 if self.chapters == 0 {
260 payload += GZIP_HEADER.len() as u64;
261 }
262 let boundary = encode(bfinal, payload);
263 let mut writer = writer.into_inner();
264 writer.write_all(boundary.as_slice())?;
265 writer.write_all(&self.crc.sum().to_le_bytes())?;
266 writer.write_all(&self.crc.amount().to_le_bytes())?;
267 writer.flush()?;
268 self.state = TgzWriterState::Finished(writer);
269 Ok(())
270 }
271 TgzWriterState::Finished(writer) => {
272 self.state = TgzWriterState::Finished(writer);
273 Ok(())
274 }
275 TgzWriterState::Failed(kind) => {
276 self.state = TgzWriterState::Failed(kind);
277 Err(Error::new(kind, "TgzWriter error"))
278 }
279 }
280 }
281}
282
283impl<W> Drop for TgzWriter<W>
284where
285 W: Write,
286{
287 fn drop(&mut self) {
288 let _ = self.do_finish();
289 }
290}
291
292impl<'a, W> Write for ChapterWriter<'a, W>
293where
294 W: Write,
295{
296 fn write(&mut self, buf: &[u8]) -> Result<usize> {
297 if buf.is_empty() {
298 return Ok(0);
299 }
300
301 if self.deferred_termination_sections {
302 if self.tgz.boundaries_written > self.index {
303 match &mut self.tgz.state {
304 TgzWriterState::Chapter(out) => {
305 if let Err(err) = out.write_all(&TAR_TERMINATION_SECTIONS) {
306 self.tgz.state = TgzWriterState::Failed(err.kind());
307 return Err(err);
308 }
309 }
310 TgzWriterState::Empty(_) | TgzWriterState::Finished(_) => unreachable!(),
311 TgzWriterState::Failed(kind) => {
312 return Err(Error::new(*kind, "TgzWriter error"));
313 }
314 }
315 }
316 self.deferred_termination_sections = false;
317 }
318
319 if buf == TAR_TERMINATION_SECTIONS {
320 self.deferred_termination_sections = true;
321 return Ok(buf.len());
322 }
323
324 while self.tgz.boundaries_written <= self.index {
325 if let Err(err) = self.tgz.write_boundary() {
326 self.tgz.state = TgzWriterState::Failed(err.kind());
327 return Err(err);
328 }
329 }
330
331 let n = match &mut self.tgz.state {
332 TgzWriterState::Chapter(out) => out.write(buf)?,
333 TgzWriterState::Empty(_) | TgzWriterState::Finished(_) => unreachable!(),
334 TgzWriterState::Failed(kind) => {
335 return Err(Error::new(*kind, "TgzWriter error"));
336 }
337 };
338
339 self.tgz.crc.update(&buf[..n]);
340 Ok(n)
341 }
342
343 fn flush(&mut self) -> Result<()> {
344 match &mut self.tgz.state {
345 TgzWriterState::Empty(_) => Ok(()),
346 TgzWriterState::Chapter(out) => out.flush(),
347 TgzWriterState::Finished(_) => unreachable!(),
348 TgzWriterState::Failed(kind) => Err(Error::new(*kind, "TgzWriter error")),
349 }
350 }
351}
352
353impl<R> TgzReader<R>
354where
355 R: Read + Seek,
356{
357 /// Parse chapter information from a tgz.
358 ///
359 /// Time complexity: **O(_c_)** where _c_ is the number of chapters. In
360 /// particular, the runtime is not sensitive to the number of entries within
361 /// a chapter, the compressed size of chapter contents, or the uncompressed
362 /// size of chapter contents.
363 pub fn open(mut read: R) -> Result<Self> {
364 let start = read.stream_position()?;
365
366 let mut prefix_buf = [0u8; GZIP_HEADER.len()];
367 let mut payload_buf = [0u8; encode::MAX_BYTES_FINAL];
368 let mut boundaries = Vec::new();
369
370 if read.read_exact(&mut prefix_buf).is_ok()
371 && prefix_buf == GZIP_HEADER
372 && let Ok(seek) = read.seek(SeekFrom::End(-(encode::MAX_BYTES_FINAL as i64 + 4)))
373 && read.read_exact(&mut payload_buf).is_ok()
374 && let Some((payload_start, mut payload)) = decode_from_end(&payload_buf)
375 && {
376 let mut boundary = seek.strict_add(payload_start as u64);
377 boundaries.push(boundary);
378 loop {
379 let Some(prev) = boundary.checked_sub(payload) else {
380 break false;
381 };
382 if prev == start {
383 break true;
384 }
385 boundaries.push(prev);
386 if prev == start.strict_add(GZIP_HEADER.len() as u64) {
387 break true;
388 }
389 if read.seek(SeekFrom::Start(prev)).is_ok()
390 && read
391 .read_exact(&mut payload_buf[..encode::MAX_BYTES_NONFINAL])
392 .is_ok()
393 && let Some(prev_payload) = decode_from_start(&payload_buf)
394 {
395 boundary = prev;
396 payload = prev_payload;
397 } else {
398 break false;
399 }
400 }
401 }
402 && u32::try_from(boundaries.len()).is_ok()
403 {
404 boundaries.reverse();
405 } else {
406 let end = read.seek(SeekFrom::End(0))?;
407 boundaries.clear();
408 boundaries.push(0);
409 boundaries.push(end.saturating_sub(start));
410 read.seek(SeekFrom::Start(start))?;
411 }
412
413 Ok(TgzReader {
414 data: read,
415 boundaries,
416 chapter: 0,
417 })
418 }
419
420 /// Number of chapters in the tgz.
421 ///
422 /// Time complexity: **O(1)**
423 ///
424 /// Files without chapter information (i.e. not produced by this crate's
425 /// TgzWriter) have 1 chapter.
426 pub fn chapters(&self) -> u32 {
427 self.boundaries.len() as u32 - 1
428 }
429
430 /// Sequentially begin reading a single chapter. Returns `None` if there is
431 /// no next chapter.
432 ///
433 /// Time complexity: **O(1)**
434 ///
435 /// # Example
436 ///
437 /// This example uses a `while let` loop to list the contents of every
438 /// chapter in a tgz.
439 ///
440 /// ```no_run
441 /// use chapter_tgz::TgzReader;
442 /// use std::fs::File;
443 /// use std::io;
444 ///
445 /// fn main() -> io::Result<()> {
446 /// let file = File::open("example.tar.gz")?;
447 /// let mut tgz = TgzReader::open(file)?;
448 /// let mut i = 0;
449 /// while let Some(mut chapter) = tgz.next_chapter() {
450 /// println!("Chapter {i}:");
451 /// for entry in chapter.entries()? {
452 /// let entry = entry?;
453 /// let path = entry.path()?;
454 /// println!(" {}", path.display());
455 /// }
456 /// i += 1;
457 /// }
458 /// Ok(())
459 /// }
460 /// ```
461 pub fn next_chapter(&mut self) -> Option<tar::Archive<ChapterReader<'_, R>>> {
462 if self.boundaries[0] == 0 {
463 if self.chapter != 0 {
464 return None;
465 }
466 self.chapter = 1;
467 Some(tar::Archive::new(ChapterReader {
468 imp: ChapterReaderImpl::Dumb(GzDecoder::new(RefMut::Borrowed(&mut self.data))),
469 }))
470 } else {
471 if self.chapter == self.boundaries.len() as u32 - 1 {
472 return None;
473 }
474 let index = self.chapter;
475 let begin = self.boundaries[index as usize];
476 let end = self.boundaries[index as usize + 1];
477 self.chapter += 1;
478 Some(tar::Archive::new(ChapterReader {
479 imp: ChapterReaderImpl::SeekTo(begin..end, RefMut::Borrowed(&mut self.data)),
480 }))
481 }
482 }
483
484 /// Begin reading a single chapter by index.
485 ///
486 /// Time complexity: **O(1)**
487 ///
488 /// As a side effect, this also affects which chapter `next_chapter` will
489 /// pick up next. For example if you call `tgz.next_chapter()` followed by
490 /// `jump_to_chapter(10)` followed by `tgz.next_chapter()`, the three
491 /// chapters you get will be chapters 0, 10, 11.
492 ///
493 /// Chapters are indexed starting from 0.
494 ///
495 /// # Panics
496 ///
497 /// Requires `i < self.chapters()`.
498 #[track_caller]
499 pub fn jump_to_chapter(&mut self, i: u32) -> tar::Archive<ChapterReader<'_, R>> {
500 assert!(i < self.chapters());
501 self.chapter = i;
502 self.next_chapter().unwrap()
503 }
504
505 /// Begin reading a single chapter in a way that can be parallelized with
506 /// other reads from the same tgz.
507 ///
508 /// Time complexity: **O(1)**
509 ///
510 /// This does not affect the next chapter read by `next_chapter`, which will
511 /// continue with whichever would have been the next chapter if
512 /// `independent_read_chapter` had not been called.
513 ///
514 /// Note the `IndependentRead` trait bound, which enforces that the
515 /// underlying reader `R` given to `TgzReader::open` can be cloned, and that
516 /// the original and clone will each have a position that can read and seek
517 /// independently of the other. This is not the case, for example, for
518 /// `File`. While [`File::try_clone`] exists, it is documented that _"Reads,
519 /// writes, and seeks will affect both `File` instances simultaneously."_
520 ///
521 /// [`File::try_clone`]: std::fs::File::try_clone
522 ///
523 /// # Panics
524 ///
525 /// Requires `i < self.chapters()`.
526 ///
527 /// # Example
528 ///
529 /// This example demonstrates using `independent_read_chapter` to decompress
530 /// all chapters in parallel.
531 ///
532 /// A conveniently runnable form of this example is provided in this crate's
533 /// *examples/* directory, also with a progress bar that shows overall
534 /// progress.
535 ///
536 /// ```no_run
537 /// use chapter_tgz::TgzReader;
538 /// use std::fs;
539 /// use std::io::{self, Cursor};
540 /// use std::thread;
541 ///
542 /// fn main() -> io::Result<()> {
543 /// let data = fs::read("example.tar.gz")?;
544 /// let tgz = TgzReader::open(Cursor::new(data))?;
545 ///
546 /// let n = tgz.chapters();
547 /// let mut chapters = Vec::with_capacity(n as usize);
548 /// for i in 0..n {
549 /// let mut chapter = tgz.independent_read_chapter(i)?;
550 /// if i % 2 == 0 {
551 /// // Option 1: We can directly enqueue chapters into the thread pool.
552 /// chapters.push(chapter);
553 /// } else if let Some(first_entry) = chapter.entries()?.next()
554 /// && let Some(file_name) = first_entry?.path()?.file_name()
555 /// && let Some(file_name_str) = file_name.to_str()
556 /// && !file_name_str.starts_with("__")
557 /// {
558 /// // Option 2: We can examine chapter entries to decide whether to
559 /// // process or skip a chapter. Reading the first entry's tar header
560 /// // (path, size, PAX extensions) from a chapter is fast.
561 /// chapters.push(tgz.independent_read_chapter(i)?);
562 /// } else {
563 /// // Option 3: Also fine to pass i and call independent_read_chapter
564 /// // on the other thread.
565 /// }
566 /// }
567 ///
568 /// thread::scope(|scope| {
569 /// for mut chapter in chapters {
570 /// scope.spawn(move || {
571 /// if let Err(err) = (|| -> io::Result<()> {
572 /// for _entry in chapter.entries()? {}
573 /// Ok(())
574 /// })() {
575 /// eprintln!("Error: {err}");
576 /// }
577 /// });
578 /// }
579 /// });
580 ///
581 /// Ok(())
582 /// }
583 /// ```
584 #[track_caller]
585 pub fn independent_read_chapter<'a>(&self, i: u32) -> Result<tar::Archive<ChapterReader<'a, R>>>
586 where
587 R: IndependentRead + 'a,
588 {
589 assert!(i < self.chapters());
590 let data = self.data.independent_clone()?;
591 if self.boundaries[0] == 0 {
592 Ok(tar::Archive::new(ChapterReader {
593 imp: ChapterReaderImpl::Dumb(GzDecoder::new(RefMut::Owned(Box::new(data)))),
594 }))
595 } else {
596 let begin = self.boundaries[i as usize];
597 let end = self.boundaries[i as usize + 1];
598 Ok(tar::Archive::new(ChapterReader {
599 imp: ChapterReaderImpl::SeekTo(begin..end, RefMut::Owned(Box::new(data))),
600 }))
601 }
602 }
603
604 /// Approximate compressed size of a chapter: the distance in bytes between
605 /// the start and end of the chapter.
606 ///
607 /// Time complexity: **O(1)**
608 ///
609 /// This is primarily intended to enable progress reporting through the use
610 /// of a `Read` implementation that monitors reads performed against the
611 /// underlying compressed archive. Outside of this use case, the returned
612 /// quantity is not guaranteed to be meaningful.
613 ///
614 /// # Example
615 ///
616 /// This example demonstrates using `compressed_size_of_chapter` to size an
617 /// [Indicatif] progress bar that increments as bytes are read.
618 ///
619 /// [Indicatif]: https://crates.io/crates/indicatif
620 ///
621 /// The `ProgressRead` type is the one shown in the documentation of
622 /// [`IndependentRead`].
623 ///
624 /// A conveniently runnable multithreaded form of this example is provided
625 /// in this crate's *examples/* directory.
626 ///
627 /// ```no_run
628 /// use chapter_tgz::TgzReader;
629 /// use indicatif::ProgressBar;
630 /// use std::fs::File;
631 /// use std::io::{self, Cursor};
632 ///
633 /// fn main() -> io::Result<()> {
634 /// let file = File::open("example.tar.gz")?;
635 /// let pb = ProgressBar::no_length();
636 /// let mut tgz = TgzReader::open(ProgressRead {
637 /// inner: file,
638 /// progress: &pb,
639 /// })?;
640 ///
641 /// let n = tgz.chapters();
642 /// let mut total_compressed_size = 0;
643 /// for i in 0..n {
644 /// total_compressed_size += tgz.compressed_size_of_chapter(i);
645 /// }
646 ///
647 /// pb.reset();
648 /// pb.set_length(total_compressed_size);
649 ///
650 /// for i in 0..n {
651 /// for _entry in tgz.jump_to_chapter(i).entries()? {
652 /// // ...
653 /// }
654 /// }
655 /// Ok(())
656 /// }
657 /// #
658 /// # use chapter_tgz::IndependentRead;
659 /// # use std::io::{Read, Seek, SeekFrom};
660 /// #
661 /// # struct ProgressRead<'a, R> {
662 /// # inner: R,
663 /// # progress: &'a ProgressBar,
664 /// # }
665 /// #
666 /// # impl<R: Read> Read for ProgressRead<'_, R> {
667 /// # fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
668 /// # let n = self.inner.read(buf)?;
669 /// # self.progress.inc(n as u64);
670 /// # Ok(n)
671 /// # }
672 /// # }
673 /// #
674 /// # impl<R: Seek> Seek for ProgressRead<'_, R> {
675 /// # fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
676 /// # self.inner.seek(pos)
677 /// # }
678 /// # }
679 /// #
680 /// # impl<R: IndependentRead> IndependentRead for ProgressRead<'_, R> {
681 /// # fn independent_clone(&self) -> io::Result<Self> {
682 /// # Ok(ProgressRead {
683 /// # inner: self.inner.independent_clone()?,
684 /// # progress: self.progress,
685 /// # })
686 /// # }
687 /// # }
688 /// ```
689 #[track_caller]
690 pub fn compressed_size_of_chapter(&self, i: u32) -> u64 {
691 assert!(i < self.chapters());
692 let begin = self.boundaries[i as usize];
693 let end = self.boundaries[i as usize + 1];
694 end - begin
695 }
696}
697
698impl<'a, R> Read for ChapterReader<'a, R>
699where
700 R: Read + Seek,
701{
702 fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
703 match &mut self.imp {
704 imp @ ChapterReaderImpl::SeekTo(..) => {
705 let ChapterReaderImpl::SeekTo(range, mut data) =
706 mem::replace(imp, ChapterReaderImpl::Failed(ErrorKind::Other))
707 else {
708 unreachable!()
709 };
710 data.seek(SeekFrom::Start(range.start))?;
711 let mut decoder = DeflateDecoder::new(data.take(range.end - range.start));
712 let result = decoder.read(buf);
713 *imp = ChapterReaderImpl::Smart(decoder);
714 result
715 }
716 ChapterReaderImpl::Smart(data) => data.read(buf),
717 ChapterReaderImpl::Dumb(gz) => gz.read(buf),
718 ChapterReaderImpl::Failed(kind) => Err(Error::new(*kind, "TgzReader error")),
719 }
720 }
721}