piz 0.5.1

piz (a Parallel Implementation of Zip) is a ZIP archive reader designed to concurrently decompress files using a simple API.
Documentation
use std::fs::File;
use std::io;
use std::process::Command;

use anyhow::{Context, Result};
use camino::Utf8Path;
use log::*;
use memmap::Mmap;
use rayon::prelude::*;

use piz::read::*;
use piz::result::ZipError;

#[test]
fn smoke() -> Result<()> {
    let _ = env_logger::builder().is_test(true).try_init();

    let inputs = [
        "tests/inputs/hello.zip",
        "tests/inputs/hello-prefixed.zip",
        "tests/inputs/zip64.zip",
    ];

    if inputs.iter().any(|i| !Utf8Path::new(i).exists()) {
        Command::new("tests/create-inputs.sh")
            .status()
            .expect("Couldn't set up input files");
    }

    for input in &inputs {
        read_zip(input)?;
    }

    Ok(())
}

fn read_zip(zip_path: &str) -> Result<()> {
    info!("Memory mapping {:#?}", zip_path);
    let zip_file = File::open(zip_path).context("Couldn't open zip file")?;
    let mapping = unsafe { Mmap::map(&zip_file).context("Couldn't mmap zip file")? };

    let archive = ZipArchive::with_prepended_data(&mapping)
        .context("Couldn't load archive")?
        .0;

    // Make sure we can treeify the entries (i.e., they form a valid directory)
    let tree = as_tree(archive.entries())?;

    match zip_path {
        "tests/inputs/hello.zip" | "tests/inputs/hello-prefixed.zip" => {
            tree.lookup("hello/hi.txt")?;
            tree.lookup("hello/rip.txt")?;
            tree.lookup("hello/sr71.txt")?;

            let no_such_file = Utf8Path::new("no/such/file");
            match tree.lookup(no_such_file) {
                Err(ZipError::NoSuchFile(p)) => {
                    assert_eq!(no_such_file, p);
                }
                Err(other) => panic!("Got incorrect error from path with no file: {:?}", other),
                Ok(_) => panic!("Got a file back from a path with no file"),
            };
            let no_such_file = Utf8Path::new("top-level-no-such-file");
            match tree.lookup(no_such_file) {
                Err(ZipError::NoSuchFile(p)) => {
                    assert_eq!(no_such_file, p);
                }
                Err(other) => panic!("Got incorrect error from path with no file: {:?}", other),
                Ok(_) => panic!("Got a file back from a path with no file"),
            };

            let invalid_path = Utf8Path::new("../nope");
            match tree.lookup(invalid_path) {
                Err(ZipError::InvalidPath(_)) => { /* Cool. */ }
                Err(other) => panic!("Got incorrect error from invalid path: {:?}", other),
                Ok(_) => panic!("Got a file back from invalid path"),
            };
        }
        "tests/inputs/zip64.zip" => {
            tree.lookup("zip64/zero100")?;
            tree.lookup("zip64/zero4400")?;
            tree.lookup("zip64/zero5000")?;
        }
        wut => unreachable!("{}", wut),
    };

    // Try reading out each file in the archive.
    // (When the reader gets dropped, the file's CRC32 will be checked
    // against the one stored in the archive.)
    tree.files()
        .map(|e| archive.read(e))
        .par_bridge()
        .try_for_each::<_, Result<()>>(|reader| {
            let mut sink = io::sink();
            io::copy(&mut reader?, &mut sink)?;
            Ok(())
        })?;
    Ok(())
}