diff --git a/src/archive/tar.rs b/src/archive/tar.rs index f05449a..330c950 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -4,6 +4,8 @@ use std::{ env, io::prelude::*, path::{Path, PathBuf}, + sync::mpsc::{self, Receiver}, + thread, }; use fs_err as fs; @@ -48,20 +50,32 @@ pub fn unpack_archive( } /// List contents of `archive`, returning a vector of archive entries -pub fn list_archive(reader: Box) -> crate::Result> { - let mut archive = tar::Archive::new(reader); +pub fn list_archive( + mut archive: tar::Archive, +) -> impl Iterator> { + struct Files(Receiver>); + impl Iterator for Files { + type Item = crate::Result; - let mut files = vec![]; - for file in archive.entries()? { - let file = file?; - - let path = file.path()?.into_owned(); - let is_dir = file.header().entry_type().is_dir(); - - files.push(FileInArchive { path, is_dir }); + fn next(&mut self) -> Option { + self.0.recv().ok() + } } - Ok(files) + let (tx, rx) = mpsc::channel(); + thread::spawn(move || { + for file in archive.entries().expect("entries is only used once") { + let file_in_archive = (|| { + let file = file?; + let path = file.path()?.into_owned(); + let is_dir = file.header().entry_type().is_dir(); + Ok(FileInArchive { path, is_dir }) + })(); + tx.send(file_in_archive).unwrap(); + } + }); + + Files(rx) } /// Compresses the archives given by `input_filenames` into the file given previously to `writer`. diff --git a/src/archive/zip.rs b/src/archive/zip.rs index 359fbe6..b661358 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -4,6 +4,8 @@ use std::{ env, io::{self, prelude::*}, path::{Path, PathBuf}, + sync::mpsc, + thread, }; use fs_err as fs; @@ -77,23 +79,43 @@ where } /// List contents of `archive`, returning a vector of archive entries -pub fn list_archive(mut archive: ZipArchive) -> crate::Result> +pub fn list_archive(mut archive: ZipArchive) -> impl Iterator> where - R: Read + Seek, + R: Read + Seek + Send + 'static, { - let mut files = vec![]; - for idx in 0..archive.len() { - let file = archive.by_index(idx)?; + struct Files(mpsc::Receiver>); + impl Iterator for Files { + type Item = crate::Result; - let path = match file.enclosed_name() { - Some(path) => path.to_owned(), - None => continue, - }; - let is_dir = file.is_dir(); - - files.push(FileInArchive { path, is_dir }); + fn next(&mut self) -> Option { + self.0.recv().ok() + } } - Ok(files) + + let (tx, rx) = mpsc::channel(); + thread::spawn(move || { + for idx in 0..archive.len() { + let maybe_file_in_archive = (|| { + let file = match archive.by_index(idx) { + Ok(f) => f, + Err(e) => return Some(Err(e.into())), + }; + + let path = match file.enclosed_name() { + Some(path) => path.to_owned(), + None => return None, + }; + let is_dir = file.is_dir(); + + Some(Ok(FileInArchive { path, is_dir })) + })(); + if let Some(file_in_archive) = maybe_file_in_archive { + tx.send(file_in_archive).unwrap(); + } + } + }); + + Files(rx) } /// Compresses the archives given by `input_filenames` into the file given previously to `writer`. diff --git a/src/commands.rs b/src/commands.rs index ecc1407..fe96eb7 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -20,7 +20,7 @@ use crate::{ Extension, }, info, - list::{self, ListOptions}, + list::{self, FileInArchive, ListOptions}, progress::Progress, utils::{ self, concatenate_os_str_list, dir_is_empty, nice_directory_display, to_utf, try_infer_extension, @@ -580,35 +580,37 @@ fn list_archive_contents( // Any other Zip decompression done can take up the whole RAM and freeze ouch. if let [Zip] = *formats.as_slice() { let zip_archive = zip::ZipArchive::new(reader)?; - let files = crate::archive::zip::list_archive(zip_archive)?; - list::list_files(archive_path, files, list_options); + let files = crate::archive::zip::list_archive(zip_archive); + list::list_files(archive_path, files, list_options)?; + return Ok(()); } // Will be used in decoder chaining let reader = BufReader::with_capacity(BUFFER_CAPACITY, reader); - let mut reader: Box = Box::new(reader); + let mut reader: Box = Box::new(reader); // Grab previous decoder and wrap it inside of a new one - let chain_reader_decoder = |format: &CompressionFormat, decoder: Box| -> crate::Result> { - let decoder: Box = match format { - Gzip => Box::new(flate2::read::GzDecoder::new(decoder)), - Bzip => Box::new(bzip2::read::BzDecoder::new(decoder)), - Lz4 => Box::new(lzzzz::lz4f::ReadDecompressor::new(decoder)?), - Lzma => Box::new(xz2::read::XzDecoder::new(decoder)), - Snappy => Box::new(snap::read::FrameDecoder::new(decoder)), - Zstd => Box::new(zstd::stream::Decoder::new(decoder)?), - Tar | Zip => unreachable!(), + let chain_reader_decoder = + |format: &CompressionFormat, decoder: Box| -> crate::Result> { + let decoder: Box = match format { + Gzip => Box::new(flate2::read::GzDecoder::new(decoder)), + Bzip => Box::new(bzip2::read::BzDecoder::new(decoder)), + Lz4 => Box::new(lzzzz::lz4f::ReadDecompressor::new(decoder)?), + Lzma => Box::new(xz2::read::XzDecoder::new(decoder)), + Snappy => Box::new(snap::read::FrameDecoder::new(decoder)), + Zstd => Box::new(zstd::stream::Decoder::new(decoder)?), + Tar | Zip => unreachable!(), + }; + Ok(decoder) }; - Ok(decoder) - }; for format in formats.iter().skip(1).rev() { reader = chain_reader_decoder(format, reader)?; } - let files = match formats[0] { - Tar => crate::archive::tar::list_archive(reader)?, + let files: Box>> = match formats[0] { + Tar => Box::new(crate::archive::tar::list_archive(tar::Archive::new(reader))), Zip => { eprintln!("{orange}[WARNING]{reset}", orange = *colors::ORANGE, reset = *colors::RESET); eprintln!( @@ -626,13 +628,13 @@ fn list_archive_contents( io::copy(&mut reader, &mut vec)?; let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?; - crate::archive::zip::list_archive(zip_archive)? + Box::new(crate::archive::zip::list_archive(zip_archive)) } Gzip | Bzip | Lz4 | Lzma | Snappy | Zstd => { panic!("Not an archive! This should never happen, if it does, something is wrong with `CompressionFormat::is_archive()`. Please report this error!"); } }; - list::list_files(archive_path, files, list_options); + list::list_files(archive_path, files, list_options)?; Ok(()) } diff --git a/src/list.rs b/src/list.rs index 1549b30..c8bd8ab 100644 --- a/src/list.rs +++ b/src/list.rs @@ -2,6 +2,8 @@ use std::path::{Path, PathBuf}; +use indicatif::{ProgressBar, ProgressStyle}; + use self::tree::Tree; /// Options controlling how archive contents should be listed @@ -22,16 +24,43 @@ pub struct FileInArchive { } /// Actually print the files -pub fn list_files(archive: &Path, files: Vec, list_options: ListOptions) { +/// Returns an Error, if one of the files can't be read +pub fn list_files( + archive: &Path, + files: impl IntoIterator>, + list_options: ListOptions, +) -> crate::Result<()> { println!("Archive: {}", archive.display()); + if list_options.tree { - let tree: Tree = files.into_iter().collect(); + let pb = if !crate::cli::ACCESSIBLE.get().unwrap() { + let template = "{wide_msg} [{elapsed_precise}] {spinner:.green}"; + let pb = ProgressBar::new_spinner(); + pb.set_style(ProgressStyle::default_bar().template(template)); + Some(pb) + } else { + None + }; + + let tree: Tree = files + .into_iter() + .map(|file| { + let file = file?; + if !crate::cli::ACCESSIBLE.get().unwrap() { + pb.as_ref().expect("exists").set_message(format!("Processing: {}", file.path.display())); + } + Ok(file) + }) + .collect::>()?; + drop(pb); tree.print(); } else { - for FileInArchive { path, is_dir } in files { + for file in files { + let FileInArchive { path, is_dir } = file?; print_entry(path.display(), is_dir); } } + Ok(()) } /// Print an entry and highlight directories, either by coloring them