Skip to main content

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            {
    ::core::panicking::panic_fmt(format_args!("called create_chapter on a TgzWriter that is already finished"));
};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            {
    ::core::panicking::panic_fmt(format_args!("TgzWriter got finished twice"));
};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            ::core::panicking::panic("internal error: entered unreachable code")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(_) => ::core::panicking::panic("internal error: entered unreachable code")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(_) => ::core::panicking::panic("internal error: entered unreachable code")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(_) => ::core::panicking::panic("internal error: entered unreachable code")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(_) => ::core::panicking::panic("internal error: entered unreachable code")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(_) => ::core::panicking::panic("internal error: entered unreachable code")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        if !(i < self.chapters()) {
    ::core::panicking::panic("assertion failed: i < self.chapters()")
};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        if !(i < self.chapters()) {
    ::core::panicking::panic("assertion failed: i < self.chapters()")
};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        if !(i < self.chapters()) {
    ::core::panicking::panic("assertion failed: i < self.chapters()")
};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                    ::core::panicking::panic("internal error: entered unreachable code")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}