Skip to main content

TgzReader

Struct TgzReader 

Source
pub struct TgzReader<R> { /* private fields */ }
Expand description

Decompressor for reading .tar.gz files containing embedded chapter information.

Tgz files without chapter information will simply be observed to have a single chapter encompassing their entire contents.

Implementations§

Source§

impl<R> TgzReader<R>
where R: Read + Seek,

Source

pub fn open(read: R) -> Result<Self>

Parse chapter information from a tgz.

Time complexity: O(c) where c is the number of chapters. In particular, the runtime is not sensitive to the number of entries within a chapter, the compressed size of chapter contents, or the uncompressed size of chapter contents.

Source

pub fn chapters(&self) -> u32

Number of chapters in the tgz.

Time complexity: O(1)

Files without chapter information (i.e. not produced by this crate’s TgzWriter) have 1 chapter.

Source

pub fn next_chapter(&mut self) -> Option<Archive<ChapterReader<'_, R>>>

Sequentially begin reading a single chapter. Returns None if there is no next chapter.

Time complexity: O(1)

§Example

This example uses a while let loop to list the contents of every chapter in a tgz.

use chapter_tgz::TgzReader;
use std::fs::File;
use std::io;

fn main() -> io::Result<()> {
    let file = File::open("example.tar.gz")?;
    let mut tgz = TgzReader::open(file)?;
    let mut i = 0;
    while let Some(mut chapter) = tgz.next_chapter() {
        println!("Chapter {i}:");
        for entry in chapter.entries()? {
            let entry = entry?;
            let path = entry.path()?;
            println!("  {}", path.display());
        }
        i += 1;
    }
    Ok(())
}
Source

pub fn jump_to_chapter(&mut self, i: u32) -> Archive<ChapterReader<'_, R>>

Begin reading a single chapter by index.

Time complexity: O(1)

As a side effect, this also affects which chapter next_chapter will pick up next. For example if you call tgz.next_chapter() followed by jump_to_chapter(10) followed by tgz.next_chapter(), the three chapters you get will be chapters 0, 10, 11.

Chapters are indexed starting from 0.

§Panics

Requires i < self.chapters().

Source

pub fn independent_read_chapter<'a>( &self, i: u32, ) -> Result<Archive<ChapterReader<'a, R>>>
where R: IndependentRead + 'a,

Begin reading a single chapter in a way that can be parallelized with other reads from the same tgz.

Time complexity: O(1)

This does not affect the next chapter read by next_chapter, which will continue with whichever would have been the next chapter if independent_read_chapter had not been called.

Note the IndependentRead trait bound, which enforces that the underlying reader R given to TgzReader::open can be cloned, and that the original and clone will each have a position that can read and seek independently of the other. This is not the case, for example, for File. While File::try_clone exists, it is documented that “Reads, writes, and seeks will affect both File instances simultaneously.”

§Panics

Requires i < self.chapters().

§Example

This example demonstrates using independent_read_chapter to decompress all chapters in parallel.

A conveniently runnable form of this example is provided in this crate’s examples/ directory, also with a progress bar that shows overall progress.

use chapter_tgz::TgzReader;
use std::fs;
use std::io::{self, Cursor};
use std::thread;

fn main() -> io::Result<()> {
    let data = fs::read("example.tar.gz")?;
    let tgz = TgzReader::open(Cursor::new(data))?;

    let n = tgz.chapters();
    let mut chapters = Vec::with_capacity(n as usize);
    for i in 0..n {
        let mut chapter = tgz.independent_read_chapter(i)?;
        if i % 2 == 0 {
            // Option 1: We can directly enqueue chapters into the thread pool.
            chapters.push(chapter);
        } else if let Some(first_entry) = chapter.entries()?.next()
            && let Some(file_name) = first_entry?.path()?.file_name()
            && let Some(file_name_str) = file_name.to_str()
            && !file_name_str.starts_with("__")
        {
            // Option 2: We can examine chapter entries to decide whether to
            // process or skip a chapter. Reading the first entry's tar header
            // (path, size, PAX extensions) from a chapter is fast.
            chapters.push(tgz.independent_read_chapter(i)?);
        } else {
            // Option 3: Also fine to pass i and call independent_read_chapter
            // on the other thread.
        }
    }

    thread::scope(|scope| {
        for mut chapter in chapters {
            scope.spawn(move || {
                if let Err(err) = (|| -> io::Result<()> {
                    for _entry in chapter.entries()? {}
                    Ok(())
                })() {
                    eprintln!("Error: {err}");
                }
            });
        }
    });

    Ok(())
}
Source

pub fn compressed_size_of_chapter(&self, i: u32) -> u64

Approximate compressed size of a chapter: the distance in bytes between the start and end of the chapter.

Time complexity: O(1)

This is primarily intended to enable progress reporting through the use of a Read implementation that monitors reads performed against the underlying compressed archive. Outside of this use case, the returned quantity is not guaranteed to be meaningful.

§Example

This example demonstrates using compressed_size_of_chapter to size an Indicatif progress bar that increments as bytes are read.

The ProgressRead type is the one shown in the documentation of IndependentRead.

A conveniently runnable multithreaded form of this example is provided in this crate’s examples/ directory.

use chapter_tgz::TgzReader;
use indicatif::ProgressBar;
use std::fs::File;
use std::io::{self, Cursor};

fn main() -> io::Result<()> {
    let file = File::open("example.tar.gz")?;
    let pb = ProgressBar::no_length();
    let mut tgz = TgzReader::open(ProgressRead {
        inner: file,
        progress: &pb,
    })?;

    let n = tgz.chapters();
    let mut total_compressed_size = 0;
    for i in 0..n {
        total_compressed_size += tgz.compressed_size_of_chapter(i);
    }

    pb.reset();
    pb.set_length(total_compressed_size);

    for i in 0..n {
        for _entry in tgz.jump_to_chapter(i).entries()? {
            // ...
        }
    }
    Ok(())
}

Auto Trait Implementations§

§

impl<R> Freeze for TgzReader<R>
where R: Freeze,

§

impl<R> RefUnwindSafe for TgzReader<R>
where R: RefUnwindSafe,

§

impl<R> Send for TgzReader<R>
where R: Send,

§

impl<R> Sync for TgzReader<R>
where R: Sync,

§

impl<R> Unpin for TgzReader<R>
where R: Unpin,

§

impl<R> UnsafeUnpin for TgzReader<R>
where R: UnsafeUnpin,

§

impl<R> UnwindSafe for TgzReader<R>
where R: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.