use std::fs::{self, File}; use std::io; use std::path::PathBuf; use anyhow::*; use log::*; use memmap::Mmap; use rayon::prelude::*; use structopt::*; use piz::read::*; #[derive(Debug, StructOpt)] #[structopt(name = "unzip", about = "Dumps a .zip file into the current directory")] struct Opt { /// Pass multiple times for additional verbosity (info, debug, trace) #[structopt(short, long, parse(from_occurrences))] verbosity: usize, /// Change to the given directory before perfoming any operations. #[structopt(short = "C", long)] directory: Option, /// Prints the tree of files in the ZIP archive instead of extracting them. #[structopt(short = "n", long)] dry_run: bool, #[structopt(name("ZIP file"))] zip_path: PathBuf, } fn main() -> Result<()> { let args = Opt::from_args(); let mut errlog = stderrlog::new(); errlog.verbosity(args.verbosity + 1); errlog.init()?; if let Some(chto) = args.directory { std::env::set_current_dir(&chto) .with_context(|| format!("Couldn't set working directory to {}", chto.display()))?; } info!("Memory mapping {:#?}", &args.zip_path); let zip_file = File::open(&args.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; let tree = as_tree(archive.entries())?; if args.dry_run { print_tree(&tree) } else { read_zip(&tree, &archive) } } fn print_tree(tree: &DirectoryContents) -> Result<()> { for entry in tree.traverse() { println!("{}", entry.metadata().path); } Ok(()) } fn read_zip(tree: &DirectoryContents, archive: &ZipArchive) -> Result<()> { tree.files().par_bridge().try_for_each(|entry| { if let Some(parent) = entry.path.parent() { fs::create_dir_all(parent) .with_context(|| format!("Couldn't create directory {}", parent))?; } let mut reader = archive.read(entry)?; let mut sink = File::create(&*entry.path) .with_context(|| format!("Couldn't create file {}", entry.path))?; io::copy(&mut reader, &mut sink)?; Ok(()) }) }