this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(7z): add progress bars during compression

+74 -25
+74 -25
src/backends/sevenz.rs
··· 1 + use super::containers::total_input_bytes; 2 + use crate::progress::{OutputTarget, ProgressArgs, ProgressReader, create_progress_bar}; 1 3 use crate::utils::{ 2 4 CmprssInput, CmprssOutput, CommonArgs, CompressionLevelValidator, Compressor, 3 5 DefaultCompressionValidator, ExtractedTarget, LevelArgs, Result, 4 6 }; 5 7 use anyhow::bail; 6 8 use clap::Args; 9 + use indicatif::ProgressBar; 7 10 use sevenz_rust2::{ 8 11 ArchiveEntry, ArchiveReader, ArchiveWriter, Password, decompress, decompress_file, 9 12 encoder_options::Lzma2Options, 10 13 }; 11 14 use std::fs::File; 12 - use std::io::{self, Seek, SeekFrom, Write}; 13 - use std::path::{Path, PathBuf}; 15 + use std::io::{self, Empty, Seek, SeekFrom, Write}; 16 + use std::path::Path; 14 17 use tempfile::tempfile; 15 18 16 19 #[derive(Args, Debug)] ··· 20 23 21 24 #[clap(flatten)] 22 25 pub level_args: LevelArgs, 26 + 27 + #[clap(flatten)] 28 + pub progress_args: ProgressArgs, 23 29 } 24 30 25 31 #[derive(Clone)] 26 32 pub struct SevenZ { 27 33 pub compression_level: i32, 34 + pub progress_args: ProgressArgs, 28 35 } 29 36 30 37 impl Default for SevenZ { 31 38 fn default() -> Self { 32 39 SevenZ { 33 40 compression_level: DefaultCompressionValidator.default_level(), 41 + progress_args: ProgressArgs::default(), 34 42 } 35 43 } 36 44 } ··· 39 47 pub fn new(args: &SevenZArgs) -> SevenZ { 40 48 SevenZ { 41 49 compression_level: args.level_args.resolve(&DefaultCompressionValidator), 50 + progress_args: args.progress_args, 42 51 } 43 52 } 44 53 45 - fn compress_to_file<W: Write + Seek>(&self, input: CmprssInput, writer: W) -> Result { 54 + /// Compress to the given seekable writer, walking path inputs ourselves 55 + /// so each file's read goes through `ProgressReader` sharing `bar`. 56 + fn compress_to_file<W: Write + Seek>( 57 + &self, 58 + input: CmprssInput, 59 + writer: W, 60 + bar: Option<&ProgressBar>, 61 + ) -> Result { 46 62 let mut aw = ArchiveWriter::new(writer)?; 47 63 let lzma = Lzma2Options::from_level(self.compression_level as u32); 48 64 aw.set_content_methods(vec![lzma.into()]); ··· 50 66 match input { 51 67 CmprssInput::Path(paths) => { 52 68 for path in paths { 69 + let name = path.file_name().unwrap().to_string_lossy().to_string(); 53 70 if path.is_file() { 54 - aw.push_source_path(&path, |_| true)?; 71 + push_file_entry(&mut aw, &name, &path, bar)?; 55 72 } else if path.is_dir() { 56 - add_directory_entries(&mut aw, &path)?; 73 + push_dir_entries(&mut aw, &name, &path, bar)?; 57 74 } else { 58 75 bail!("7z does not support this file type"); 59 76 } ··· 89 106 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result { 90 107 match output { 91 108 CmprssOutput::Path(ref path) => { 109 + let total = match &input { 110 + CmprssInput::Path(paths) => Some(total_input_bytes(paths)), 111 + _ => None, 112 + }; 113 + let bar = 114 + create_progress_bar(total, self.progress_args.progress, OutputTarget::File); 92 115 let file = File::create(path)?; 93 - self.compress_to_file(input, file) 116 + self.compress_to_file(input, file, bar.as_ref())?; 117 + if let Some(b) = bar { 118 + b.finish(); 119 + } 120 + Ok(()) 94 121 } 95 122 CmprssOutput::Pipe(mut pipe) => { 96 123 let mut temp_file = tempfile()?; 97 - self.compress_to_file(input, &mut temp_file)?; 124 + self.compress_to_file(input, &mut temp_file, None)?; 98 125 temp_file.seek(SeekFrom::Start(0))?; 99 126 io::copy(&mut temp_file, &mut pipe)?; 100 127 Ok(()) 101 128 } 102 129 CmprssOutput::Writer(mut writer) => { 103 130 let mut temp_file = tempfile()?; 104 - self.compress_to_file(input, &mut temp_file)?; 131 + self.compress_to_file(input, &mut temp_file, None)?; 105 132 temp_file.seek(SeekFrom::Start(0))?; 106 133 io::copy(&mut temp_file, &mut writer)?; 107 134 Ok(()) ··· 198 225 } 199 226 } 200 227 201 - /// Archive the contents of `dir` under the directory's basename. 202 - /// 203 - /// `push_source_path` strips the src prefix from each entry name, so to keep 204 - /// the directory itself as a prefix in the archive (e.g. `indir/file.txt` 205 - /// instead of `file.txt`), we pass the *parent* as the src and filter to just 206 - /// `dir`'s subtree. 207 - fn add_directory_entries<W: Write + Seek>(aw: &mut ArchiveWriter<W>, dir: &Path) -> Result { 208 - let abs_dir = std::path::absolute(dir).unwrap_or_else(|_| dir.to_path_buf()); 209 - let base: PathBuf = abs_dir 210 - .parent() 211 - .map(Path::to_path_buf) 212 - .unwrap_or_else(|| abs_dir.clone()); 213 - let base_filter = base.clone(); 214 - let target = abs_dir.clone(); 215 - aw.push_source_path(&base, move |p| { 216 - p == base_filter.as_path() || p.starts_with(&target) 217 - })?; 228 + /// Push a single regular file as an archive entry, with reads flowing 229 + /// through `ProgressReader` so they tick the shared bar. 230 + fn push_file_entry<W: Write + Seek>( 231 + aw: &mut ArchiveWriter<W>, 232 + archive_name: &str, 233 + disk_path: &Path, 234 + bar: Option<&ProgressBar>, 235 + ) -> Result { 236 + let entry = ArchiveEntry::from_path(disk_path, archive_name.to_string()); 237 + let file = File::open(disk_path)?; 238 + let reader = ProgressReader::new(file, bar.cloned()); 239 + aw.push_archive_entry(entry, Some(reader))?; 240 + Ok(()) 241 + } 242 + 243 + /// Push a directory entry, then recurse into its children. Mirrors the 244 + /// layout that `push_source_path` would produce (entries named 245 + /// `<dir>/<child>`), but gives us a read hook for each file. 246 + fn push_dir_entries<W: Write + Seek>( 247 + aw: &mut ArchiveWriter<W>, 248 + archive_name: &str, 249 + disk_path: &Path, 250 + bar: Option<&ProgressBar>, 251 + ) -> Result { 252 + let entry = ArchiveEntry::from_path(disk_path, archive_name.to_string()); 253 + aw.push_archive_entry::<Empty>(entry, None)?; 254 + for child in std::fs::read_dir(disk_path)? { 255 + let child = child?; 256 + let child_path = child.path(); 257 + let child_name = format!("{}/{}", archive_name, child.file_name().to_string_lossy()); 258 + if child_path.is_file() { 259 + push_file_entry(aw, &child_name, &child_path, bar)?; 260 + } else if child_path.is_dir() { 261 + push_dir_entries(aw, &child_name, &child_path, bar)?; 262 + } 263 + // Skip symlinks/other types — parity with prior behavior. 264 + } 218 265 Ok(()) 219 266 } 220 267 ··· 242 289 fn test_sevenz_fast_compression() -> Result { 243 290 let fast_compressor = SevenZ { 244 291 compression_level: 1, 292 + progress_args: ProgressArgs::default(), 245 293 }; 246 294 test_compression(&fast_compressor) 247 295 } ··· 250 298 fn test_sevenz_best_compression() -> Result { 251 299 let best_compressor = SevenZ { 252 300 compression_level: 9, 301 + progress_args: ProgressArgs::default(), 253 302 }; 254 303 test_compression(&best_compressor) 255 304 }