this repo has no description
0
fork

Configure Feed

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

refactor(backends): extract shared I/O helpers for single-stream codecs

Every single-stream backend (gzip, xz, bzip2, zstd, lz4, brotli, snappy,
lzma) had the same ~40 lines of boilerplate per compress/extract for
resolving CmprssInput into a Read, resolving CmprssOutput into a Write,
and rejecting directory inputs/outputs. Consolidate that into a new
stream module and have each backend call open_input, open_output, and
guard_file_output.

Net -323 lines across the eight backends. Also normalizes the
directory-rejection error messages and extends the directory-input
guard to xz and bzip2, which previously only surfaced the lower-level
'Is a directory' error from File::open.

+140 -463
+8 -65
src/backends/brotli.rs
··· 1 + use super::stream::{guard_file_output, open_input, open_output}; 1 2 use crate::progress::{ProgressArgs, copy_with_progress}; 2 3 use crate::utils::*; 3 - use anyhow::bail; 4 4 use brotli::{CompressorWriter, Decompressor}; 5 5 use clap::Args; 6 - use std::fs::File; 7 - use std::io::{self, BufReader, BufWriter, Read, Write}; 6 + use std::io::{self, Write}; 8 7 9 8 /// Brotli buffer size used when constructing the encoder/decoder. 10 9 const BROTLI_BUFFER_SIZE: usize = 4096; ··· 91 90 92 91 /// Compress an input file or pipe to a brotli archive 93 92 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result { 94 - if let CmprssOutput::Path(out_path) = &output 95 - && out_path.is_dir() 96 - { 97 - bail!( 98 - "Brotli does not support compressing to a directory. Please specify an output file." 99 - ); 100 - } 101 - if let CmprssInput::Path(input_paths) = &input { 102 - for x in input_paths { 103 - if x.is_dir() { 104 - bail!( 105 - "Brotli does not support compressing a directory. Please specify only files." 106 - ); 107 - } 108 - } 109 - } 110 - let mut file_size = None; 111 - let mut input_stream: Box<dyn Read + Send> = match input { 112 - CmprssInput::Path(paths) => { 113 - if paths.len() > 1 { 114 - bail!("Multiple input files not supported for brotli"); 115 - } 116 - let path = &paths[0]; 117 - file_size = Some(std::fs::metadata(path)?.len()); 118 - Box::new(BufReader::new(File::open(path)?)) 119 - } 120 - CmprssInput::Pipe(stdin) => Box::new(BufReader::new(stdin)), 121 - CmprssInput::Reader(reader) => reader.0, 122 - }; 123 - 93 + guard_file_output(&output, "Brotli")?; 94 + let (mut input_stream, file_size) = open_input(input, "Brotli")?; 124 95 let quality = self.compression_level as u32; 125 96 126 97 if let CmprssOutput::Writer(writer) = output { ··· 129 100 io::copy(&mut input_stream, &mut encoder)?; 130 101 encoder.flush()?; 131 102 } else { 132 - let output_stream: Box<dyn Write + Send> = match &output { 133 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 134 - CmprssOutput::Pipe(stdout) => Box::new(BufWriter::new(stdout)), 135 - CmprssOutput::Writer(_) => unreachable!(), 136 - }; 103 + let output_stream = open_output(&output)?; 137 104 let mut encoder = 138 105 CompressorWriter::new(output_stream, BROTLI_BUFFER_SIZE, quality, BROTLI_LGWIN); 139 106 copy_with_progress( ··· 152 119 153 120 /// Extract a brotli archive to an output file or pipe 154 121 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result { 155 - if let CmprssOutput::Path(out_path) = &output 156 - && out_path.is_dir() 157 - { 158 - bail!( 159 - "Brotli does not support extracting to a directory. Please specify an output file." 160 - ); 161 - } 162 - 163 - let mut file_size = None; 164 - let input_stream: Box<dyn Read + Send> = match input { 165 - CmprssInput::Path(paths) => { 166 - if paths.len() > 1 { 167 - bail!("Multiple input files not supported for brotli extraction"); 168 - } 169 - let path = &paths[0]; 170 - file_size = Some(std::fs::metadata(path)?.len()); 171 - Box::new(BufReader::new(File::open(path)?)) 172 - } 173 - CmprssInput::Pipe(stdin) => Box::new(BufReader::new(stdin)), 174 - CmprssInput::Reader(reader) => reader.0, 175 - }; 176 - 122 + guard_file_output(&output, "Brotli")?; 123 + let (input_stream, file_size) = open_input(input, "Brotli")?; 177 124 let mut decoder = Decompressor::new(input_stream, BROTLI_BUFFER_SIZE); 178 125 179 126 if let CmprssOutput::Writer(mut writer) = output { 180 127 io::copy(&mut decoder, &mut writer)?; 181 128 } else { 182 - let mut output_stream: Box<dyn Write + Send> = match &output { 183 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 184 - CmprssOutput::Pipe(stdout) => Box::new(BufWriter::new(stdout)), 185 - CmprssOutput::Writer(_) => unreachable!(), 186 - }; 129 + let mut output_stream = open_output(&output)?; 187 130 copy_with_progress( 188 131 &mut decoder, 189 132 &mut output_stream,
+13 -43
src/backends/bzip2.rs
··· 1 + use super::stream::{guard_file_output, open_input, open_output}; 1 2 use crate::{ 2 3 progress::{ProgressArgs, copy_with_progress}, 3 4 utils::{ ··· 5 6 ExtractedTarget, LevelArgs, Result, 6 7 }, 7 8 }; 8 - use anyhow::bail; 9 9 use bzip2::Compression; 10 10 use bzip2::write::{BzDecoder, BzEncoder}; 11 11 use clap::Args; 12 - use std::{ 13 - fs::File, 14 - io::{self, BufReader, BufWriter, Read, Write}, 15 - }; 12 + use std::io; 16 13 17 14 /// BZip2-specific compression validator (1-9 range) 18 15 #[derive(Debug, Clone, Copy)] ··· 95 92 96 93 /// Compress an input file or pipe to a bz2 archive 97 94 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result { 98 - let mut file_size = None; 99 - let mut input_stream = match input { 100 - CmprssInput::Path(paths) => { 101 - if paths.len() > 1 { 102 - bail!("Multiple input files not supported for bzip2"); 103 - } 104 - let path = &paths[0]; 105 - file_size = Some(std::fs::metadata(path)?.len()); 106 - Box::new(BufReader::new(File::open(path)?)) as Box<dyn Read + Send> 107 - } 108 - CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>, 109 - CmprssInput::Reader(reader) => reader.0, 110 - }; 95 + guard_file_output(&output, "Bzip2")?; 96 + let (mut input_stream, file_size) = open_input(input, "Bzip2")?; 97 + let level = Compression::new(self.level as u32); 98 + 111 99 if let CmprssOutput::Writer(writer) = output { 112 - let mut encoder = BzEncoder::new(writer, Compression::new(self.level as u32)); 100 + let mut encoder = BzEncoder::new(writer, level); 113 101 io::copy(&mut input_stream, &mut encoder)?; 114 102 } else { 115 - let output_stream: Box<dyn Write + Send> = match &output { 116 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 117 - CmprssOutput::Pipe(pipe) => Box::new(pipe), 118 - CmprssOutput::Writer(_) => unreachable!(), 119 - }; 120 - let mut encoder = BzEncoder::new(output_stream, Compression::new(self.level as u32)); 103 + let output_stream = open_output(&output)?; 104 + let mut encoder = BzEncoder::new(output_stream, level); 121 105 copy_with_progress( 122 106 &mut input_stream, 123 107 &mut encoder, ··· 133 117 134 118 /// Extract a bz2 archive to a file or pipe 135 119 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result { 136 - let mut file_size = None; 137 - let mut input_stream = match input { 138 - CmprssInput::Path(paths) => { 139 - if paths.len() > 1 { 140 - bail!("Multiple input files not supported for bzip2 extraction"); 141 - } 142 - let path = &paths[0]; 143 - file_size = Some(std::fs::metadata(path)?.len()); 144 - Box::new(BufReader::new(File::open(path)?)) as Box<dyn Read + Send> 145 - } 146 - CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>, 147 - CmprssInput::Reader(reader) => reader.0, 148 - }; 120 + guard_file_output(&output, "Bzip2")?; 121 + let (mut input_stream, file_size) = open_input(input, "Bzip2")?; 122 + 149 123 if let CmprssOutput::Writer(writer) = output { 150 124 let mut decoder = BzDecoder::new(writer); 151 125 io::copy(&mut input_stream, &mut decoder)?; 152 126 } else { 153 - let output_stream: Box<dyn Write + Send> = match &output { 154 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 155 - CmprssOutput::Pipe(pipe) => Box::new(pipe), 156 - CmprssOutput::Writer(_) => unreachable!(), 157 - }; 127 + let output_stream = open_output(&output)?; 158 128 let mut decoder = BzDecoder::new(output_stream); 159 129 copy_with_progress( 160 130 &mut input_stream,
+11 -63
src/backends/gzip.rs
··· 1 + use super::stream::{guard_file_output, open_input, open_output}; 1 2 use crate::progress::{ProgressArgs, copy_with_progress}; 2 3 use crate::utils::*; 3 - use anyhow::bail; 4 4 use clap::Args; 5 5 use flate2::write::GzEncoder; 6 6 use flate2::{Compression, read::GzDecoder}; 7 - use std::fs::File; 8 - use std::io::{self, BufReader, BufWriter, Read, Write}; 7 + use std::io; 9 8 10 9 #[derive(Args, Debug)] 11 10 pub struct GzipArgs { ··· 67 66 68 67 /// Compress an input file or pipe to a gzip archive 69 68 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result { 70 - if let CmprssOutput::Path(out_path) = &output 71 - && out_path.is_dir() 72 - { 73 - bail!( 74 - "Gzip does not support compressing to a directory. Please specify an output file." 75 - ); 76 - } 77 - if let CmprssInput::Path(input_paths) = &input { 78 - for x in input_paths { 79 - if x.is_dir() { 80 - bail!( 81 - "Gzip does not support compressing a directory. Please specify only files." 82 - ); 83 - } 84 - } 85 - } 86 - let mut file_size = None; 87 - let mut input_stream: Box<dyn Read + Send> = match input { 88 - CmprssInput::Path(paths) => { 89 - if paths.len() > 1 { 90 - bail!("Multiple input files not supported for gzip"); 91 - } 92 - let path = &paths[0]; 93 - file_size = Some(std::fs::metadata(path)?.len()); 94 - Box::new(BufReader::new(File::open(path)?)) 95 - } 96 - CmprssInput::Pipe(stdin) => Box::new(BufReader::new(stdin)), 97 - CmprssInput::Reader(reader) => reader.0, 98 - }; 69 + guard_file_output(&output, "Gzip")?; 70 + let (mut input_stream, file_size) = open_input(input, "Gzip")?; 71 + let level = Compression::new(self.compression_level as u32); 99 72 100 73 if let CmprssOutput::Writer(writer) = output { 101 - let mut encoder = 102 - GzEncoder::new(writer, Compression::new(self.compression_level as u32)); 74 + let mut encoder = GzEncoder::new(writer, level); 103 75 io::copy(&mut input_stream, &mut encoder)?; 104 76 encoder.finish()?; 105 77 } else { 106 - let output_stream: Box<dyn Write + Send> = match &output { 107 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 108 - CmprssOutput::Pipe(stdout) => Box::new(BufWriter::new(stdout)), 109 - CmprssOutput::Writer(_) => unreachable!(), 110 - }; 111 - let mut encoder = GzEncoder::new( 112 - output_stream, 113 - Compression::new(self.compression_level as u32), 114 - ); 78 + let output_stream = open_output(&output)?; 79 + let mut encoder = GzEncoder::new(output_stream, level); 115 80 copy_with_progress( 116 81 &mut input_stream, 117 82 &mut encoder, ··· 127 92 128 93 /// Extract a gzip archive 129 94 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result { 130 - let mut file_size = None; 131 - let input_stream: Box<dyn Read + Send> = match input { 132 - CmprssInput::Path(paths) => { 133 - if paths.len() > 1 { 134 - bail!("Multiple input files not supported for gzip extraction"); 135 - } 136 - let path = &paths[0]; 137 - file_size = Some(std::fs::metadata(path)?.len()); 138 - Box::new(BufReader::new(File::open(path)?)) 139 - } 140 - CmprssInput::Pipe(stdin) => Box::new(BufReader::new(stdin)), 141 - CmprssInput::Reader(reader) => reader.0, 142 - }; 143 - 95 + guard_file_output(&output, "Gzip")?; 96 + let (input_stream, file_size) = open_input(input, "Gzip")?; 144 97 let mut decoder = GzDecoder::new(input_stream); 145 98 146 99 if let CmprssOutput::Writer(mut writer) = output { 147 100 io::copy(&mut decoder, &mut writer)?; 148 101 } else { 149 - let mut output_stream: Box<dyn Write + Send> = match &output { 150 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 151 - CmprssOutput::Pipe(stdout) => Box::new(BufWriter::new(stdout)), 152 - CmprssOutput::Writer(_) => unreachable!(), 153 - }; 102 + let mut output_stream = open_output(&output)?; 154 103 copy_with_progress( 155 104 &mut decoder, 156 105 &mut output_stream, ··· 160 109 &output, 161 110 )?; 162 111 } 163 - 164 112 Ok(()) 165 113 } 166 114 }
+8 -63
src/backends/lz4.rs
··· 1 + use super::stream::{guard_file_output, open_input, open_output}; 1 2 use crate::progress::{ProgressArgs, copy_with_progress}; 2 3 use crate::utils::*; 3 - use anyhow::bail; 4 4 use clap::Args; 5 5 use lz4_flex::frame::{FrameDecoder, FrameEncoder}; 6 - use std::fs::File; 7 - use std::io::{self, BufReader, BufWriter, Read, Write}; 6 + use std::io; 8 7 9 8 #[derive(Args, Debug)] 10 9 pub struct Lz4Args { ··· 41 40 42 41 /// Compress an input file or pipe to a lz4 archive 43 42 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result { 44 - if let CmprssOutput::Path(out_path) = &output 45 - && out_path.is_dir() 46 - { 47 - bail!( 48 - "LZ4 does not support compressing to a directory. Please specify an output file." 49 - ); 50 - } 51 - if let CmprssInput::Path(input_paths) = &input { 52 - for x in input_paths { 53 - if x.is_dir() { 54 - bail!( 55 - "LZ4 does not support compressing a directory. Please specify only files." 56 - ); 57 - } 58 - } 59 - } 60 - let mut file_size = None; 61 - let mut input_stream: Box<dyn Read + Send> = match input { 62 - CmprssInput::Path(paths) => { 63 - if paths.len() > 1 { 64 - bail!("Multiple input files not supported for lz4"); 65 - } 66 - let path = &paths[0]; 67 - file_size = Some(std::fs::metadata(path)?.len()); 68 - Box::new(BufReader::new(File::open(path)?)) 69 - } 70 - CmprssInput::Pipe(stdin) => Box::new(BufReader::new(stdin)), 71 - CmprssInput::Reader(reader) => reader.0, 72 - }; 43 + guard_file_output(&output, "LZ4")?; 44 + let (mut input_stream, file_size) = open_input(input, "LZ4")?; 73 45 74 46 if let CmprssOutput::Writer(writer) = output { 75 47 let mut encoder = FrameEncoder::new(writer); 76 48 io::copy(&mut input_stream, &mut encoder)?; 77 49 encoder.finish()?; 78 50 } else { 79 - let output_stream: Box<dyn Write + Send> = match &output { 80 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 81 - CmprssOutput::Pipe(stdout) => Box::new(BufWriter::new(stdout)), 82 - CmprssOutput::Writer(_) => unreachable!(), 83 - }; 51 + let output_stream = open_output(&output)?; 84 52 let mut encoder = FrameEncoder::new(output_stream); 85 53 copy_with_progress( 86 54 &mut input_stream, ··· 98 66 99 67 /// Extract a lz4 archive to an output file or pipe 100 68 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result { 101 - if let CmprssOutput::Path(out_path) = &output 102 - && out_path.is_dir() 103 - { 104 - bail!("LZ4 does not support extracting to a directory. Please specify an output file."); 105 - } 106 - 107 - let mut file_size = None; 108 - let input_stream: Box<dyn Read + Send> = match input { 109 - CmprssInput::Path(paths) => { 110 - if paths.len() > 1 { 111 - bail!("Multiple input files not supported for lz4 extraction"); 112 - } 113 - let path = &paths[0]; 114 - file_size = Some(std::fs::metadata(path)?.len()); 115 - Box::new(BufReader::new(File::open(path)?)) 116 - } 117 - CmprssInput::Pipe(stdin) => Box::new(BufReader::new(stdin)), 118 - CmprssInput::Reader(reader) => reader.0, 119 - }; 120 - 121 - // Create a lz4 decoder 69 + guard_file_output(&output, "LZ4")?; 70 + let (input_stream, file_size) = open_input(input, "LZ4")?; 122 71 let mut decoder = FrameDecoder::new(input_stream); 123 72 124 73 if let CmprssOutput::Writer(mut writer) = output { 125 74 io::copy(&mut decoder, &mut writer)?; 126 75 } else { 127 - let mut output_stream: Box<dyn Write + Send> = match &output { 128 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 129 - CmprssOutput::Pipe(stdout) => Box::new(BufWriter::new(stdout)), 130 - CmprssOutput::Writer(_) => unreachable!(), 131 - }; 76 + let mut output_stream = open_output(&output)?; 132 77 copy_with_progress( 133 78 &mut decoder, 134 79 &mut output_stream,
+9 -59
src/backends/lzma.rs
··· 1 + use super::stream::{guard_file_output, open_input, open_output}; 1 2 use crate::{ 2 3 progress::{ProgressArgs, copy_with_progress}, 3 4 utils::*, 4 5 }; 5 - use anyhow::bail; 6 6 use clap::Args; 7 - use std::{ 8 - fs::File, 9 - io::{self, BufReader, BufWriter, Read, Write}, 10 - }; 7 + use std::io::{self, Write}; 11 8 use xz2::read::XzDecoder; 12 9 use xz2::stream::{LzmaOptions, Stream}; 13 10 use xz2::write::XzEncoder; ··· 95 92 } 96 93 97 94 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result { 98 - if let CmprssOutput::Path(out_path) = &output 99 - && out_path.is_dir() 100 - { 101 - bail!( 102 - "LZMA does not support compressing to a directory. Please specify an output file." 103 - ); 104 - } 105 - if let CmprssInput::Path(input_paths) = &input { 106 - for x in input_paths { 107 - if x.is_dir() { 108 - bail!( 109 - "LZMA does not support compressing a directory. Please specify only files." 110 - ); 111 - } 112 - } 113 - } 114 - 115 - let mut file_size = None; 116 - let mut input_stream: Box<dyn Read + Send> = match input { 117 - CmprssInput::Path(paths) => { 118 - if paths.len() > 1 { 119 - bail!("Multiple input files not supported for lzma"); 120 - } 121 - let path = &paths[0]; 122 - file_size = Some(std::fs::metadata(path)?.len()); 123 - Box::new(BufReader::new(File::open(path)?)) 124 - } 125 - CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>, 126 - CmprssInput::Reader(reader) => reader.0, 127 - }; 95 + guard_file_output(&output, "LZMA")?; 96 + let (mut input_stream, file_size) = open_input(input, "LZMA")?; 128 97 129 98 if let CmprssOutput::Writer(writer) = output { 130 99 let mut encoder = XzEncoder::new_stream(writer, self.encoder_stream()?); 131 100 io::copy(&mut input_stream, &mut encoder)?; 132 101 encoder.try_finish()?; 133 102 } else { 134 - let output_stream: Box<dyn Write + Send> = match &output { 135 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 136 - CmprssOutput::Pipe(pipe) => Box::new(pipe) as Box<dyn Write + Send>, 137 - CmprssOutput::Writer(_) => unreachable!(), 138 - }; 103 + let output_stream = open_output(&output)?; 139 104 let mut encoder = XzEncoder::new_stream(output_stream, self.encoder_stream()?); 140 105 copy_with_progress( 141 106 &mut input_stream, ··· 152 117 } 153 118 154 119 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result { 155 - let mut file_size = None; 156 - let input_stream: Box<dyn Read + Send> = match input { 157 - CmprssInput::Path(paths) => { 158 - if paths.len() > 1 { 159 - bail!("Multiple input files not supported for lzma extraction"); 160 - } 161 - let path = &paths[0]; 162 - file_size = Some(std::fs::metadata(path)?.len()); 163 - Box::new(BufReader::new(File::open(path)?)) as Box<dyn Read + Send> 164 - } 165 - CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>, 166 - CmprssInput::Reader(reader) => reader.0, 167 - }; 120 + guard_file_output(&output, "LZMA")?; 121 + let (input_stream, file_size) = open_input(input, "LZMA")?; 168 122 let mut decoder = XzDecoder::new_stream(input_stream, Self::decoder_stream()?); 169 123 170 124 if let CmprssOutput::Writer(mut writer) = output { 171 125 io::copy(&mut decoder, &mut writer)?; 172 126 } else { 173 - let mut output_stream: Box<dyn Write + Send> = match &output { 174 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 175 - CmprssOutput::Pipe(pipe) => Box::new(pipe) as Box<dyn Write + Send>, 176 - CmprssOutput::Writer(_) => unreachable!(), 177 - }; 127 + let mut output_stream = open_output(&output)?; 178 128 copy_with_progress( 179 129 &mut decoder, 180 - &mut *output_stream, 130 + &mut output_stream, 181 131 self.progress_args.chunk_size.size_in_bytes, 182 132 file_size, 183 133 self.progress_args.progress,
+1
src/backends/mod.rs
··· 5 5 mod lzma; 6 6 mod pipeline; 7 7 mod snappy; 8 + mod stream; 8 9 mod tar; 9 10 mod xz; 10 11 mod zip;
+8 -64
src/backends/snappy.rs
··· 1 + use super::stream::{guard_file_output, open_input, open_output}; 1 2 use crate::progress::{ProgressArgs, copy_with_progress}; 2 3 use crate::utils::*; 3 - use anyhow::bail; 4 4 use clap::Args; 5 5 use snap::read::FrameDecoder; 6 6 use snap::write::FrameEncoder; 7 - use std::fs::File; 8 - use std::io::{self, BufReader, BufWriter, Read, Write}; 7 + use std::io::{self, Write}; 9 8 10 9 #[derive(Args, Debug)] 11 10 pub struct SnappyArgs { ··· 43 42 44 43 /// Compress an input file or pipe to a snappy frame-format archive 45 44 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result { 46 - if let CmprssOutput::Path(out_path) = &output 47 - && out_path.is_dir() 48 - { 49 - bail!( 50 - "Snappy does not support compressing to a directory. Please specify an output file." 51 - ); 52 - } 53 - if let CmprssInput::Path(input_paths) = &input { 54 - for x in input_paths { 55 - if x.is_dir() { 56 - bail!( 57 - "Snappy does not support compressing a directory. Please specify only files." 58 - ); 59 - } 60 - } 61 - } 62 - let mut file_size = None; 63 - let mut input_stream: Box<dyn Read + Send> = match input { 64 - CmprssInput::Path(paths) => { 65 - if paths.len() > 1 { 66 - bail!("Multiple input files not supported for snappy"); 67 - } 68 - let path = &paths[0]; 69 - file_size = Some(std::fs::metadata(path)?.len()); 70 - Box::new(BufReader::new(File::open(path)?)) 71 - } 72 - CmprssInput::Pipe(stdin) => Box::new(BufReader::new(stdin)), 73 - CmprssInput::Reader(reader) => reader.0, 74 - }; 45 + guard_file_output(&output, "Snappy")?; 46 + let (mut input_stream, file_size) = open_input(input, "Snappy")?; 75 47 76 48 if let CmprssOutput::Writer(writer) = output { 77 49 let mut encoder = FrameEncoder::new(writer); 78 50 io::copy(&mut input_stream, &mut encoder)?; 79 51 encoder.flush()?; 80 52 } else { 81 - let output_stream: Box<dyn Write + Send> = match &output { 82 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 83 - CmprssOutput::Pipe(stdout) => Box::new(BufWriter::new(stdout)), 84 - CmprssOutput::Writer(_) => unreachable!(), 85 - }; 53 + let output_stream = open_output(&output)?; 86 54 let mut encoder = FrameEncoder::new(output_stream); 87 55 copy_with_progress( 88 56 &mut input_stream, ··· 100 68 101 69 /// Extract a snappy frame-format archive to an output file or pipe 102 70 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result { 103 - if let CmprssOutput::Path(out_path) = &output 104 - && out_path.is_dir() 105 - { 106 - bail!( 107 - "Snappy does not support extracting to a directory. Please specify an output file." 108 - ); 109 - } 110 - 111 - let mut file_size = None; 112 - let input_stream: Box<dyn Read + Send> = match input { 113 - CmprssInput::Path(paths) => { 114 - if paths.len() > 1 { 115 - bail!("Multiple input files not supported for snappy extraction"); 116 - } 117 - let path = &paths[0]; 118 - file_size = Some(std::fs::metadata(path)?.len()); 119 - Box::new(BufReader::new(File::open(path)?)) 120 - } 121 - CmprssInput::Pipe(stdin) => Box::new(BufReader::new(stdin)), 122 - CmprssInput::Reader(reader) => reader.0, 123 - }; 124 - 71 + guard_file_output(&output, "Snappy")?; 72 + let (input_stream, file_size) = open_input(input, "Snappy")?; 125 73 let mut decoder = FrameDecoder::new(input_stream); 126 74 127 75 if let CmprssOutput::Writer(mut writer) = output { 128 76 io::copy(&mut decoder, &mut writer)?; 129 77 } else { 130 - let mut output_stream: Box<dyn Write + Send> = match &output { 131 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 132 - CmprssOutput::Pipe(stdout) => Box::new(BufWriter::new(stdout)), 133 - CmprssOutput::Writer(_) => unreachable!(), 134 - }; 78 + let mut output_stream = open_output(&output)?; 135 79 copy_with_progress( 136 80 &mut decoder, 137 81 &mut output_stream,
+64
src/backends/stream.rs
··· 1 + //! Shared I/O plumbing for single-stream compressors. 2 + //! 3 + //! Every single-file codec (gzip, xz, bzip2, zstd, lz4, brotli, snappy, lzma) 4 + //! has the same shape: resolve the input into a `Read`, resolve the output 5 + //! into a `Write`, reject directory inputs/outputs, and forward the in-memory 6 + //! `Reader`/`Writer` variants untouched for pipeline stages. These helpers 7 + //! consolidate that plumbing so each backend only expresses its codec choice. 8 + 9 + use crate::utils::{CmprssInput, CmprssOutput, Result}; 10 + use anyhow::bail; 11 + use std::fs::File; 12 + use std::io::{BufReader, BufWriter, Read, Write}; 13 + 14 + /// Resolve a `CmprssInput` into a single boxed `Read` stream for single-stream 15 + /// codecs. Returns the stream together with the input file's size when known 16 + /// (used to drive progress bars). 17 + /// 18 + /// Bails when multiple input paths are given, or when a path input is a 19 + /// directory — single-stream codecs operate on exactly one byte stream. 20 + pub fn open_input(input: CmprssInput, name: &str) -> Result<(Box<dyn Read + Send>, Option<u64>)> { 21 + match input { 22 + CmprssInput::Path(paths) => { 23 + if paths.len() > 1 { 24 + bail!("Multiple input files not supported for {name}"); 25 + } 26 + let path = &paths[0]; 27 + if path.is_dir() { 28 + bail!("{name} does not operate on directories; specify a file instead."); 29 + } 30 + let size = std::fs::metadata(path)?.len(); 31 + let reader: Box<dyn Read + Send> = Box::new(BufReader::new(File::open(path)?)); 32 + Ok((reader, Some(size))) 33 + } 34 + CmprssInput::Pipe(stdin) => Ok((Box::new(BufReader::new(stdin)), None)), 35 + CmprssInput::Reader(reader) => Ok((reader.0, None)), 36 + } 37 + } 38 + 39 + /// Bail if the output path refers to an existing directory. Single-stream 40 + /// codecs always emit a single byte stream, so they can't write to a 41 + /// directory. 42 + pub fn guard_file_output(output: &CmprssOutput, name: &str) -> Result { 43 + if let CmprssOutput::Path(path) = output 44 + && path.is_dir() 45 + { 46 + bail!("{name} does not operate on directories; specify an output file instead."); 47 + } 48 + Ok(()) 49 + } 50 + 51 + /// Open a `CmprssOutput` as a boxed `Write`. 52 + /// 53 + /// Callers must destructure `CmprssOutput::Writer` themselves before calling 54 + /// this — the in-memory `Writer` is already a boxed `Write` and doesn't need 55 + /// an additional buffering layer. 56 + pub fn open_output(output: &CmprssOutput) -> Result<Box<dyn Write + Send + '_>> { 57 + match output { 58 + CmprssOutput::Path(path) => Ok(Box::new(BufWriter::new(File::create(path)?))), 59 + CmprssOutput::Pipe(stdout) => Ok(Box::new(BufWriter::new(stdout))), 60 + CmprssOutput::Writer(_) => { 61 + unreachable!("open_output called with CmprssOutput::Writer; destructure it first") 62 + } 63 + } 64 + }
+10 -42
src/backends/xz.rs
··· 1 + use super::stream::{guard_file_output, open_input, open_output}; 1 2 use crate::{ 2 3 progress::{ProgressArgs, copy_with_progress}, 3 4 utils::*, 4 5 }; 5 - use anyhow::bail; 6 6 use clap::Args; 7 - use std::{ 8 - fs::File, 9 - io::{self, BufReader, BufWriter, Read, Write}, 10 - }; 7 + use std::io; 11 8 use xz2::read::XzDecoder; 12 9 use xz2::write::XzEncoder; 13 10 ··· 62 59 } 63 60 64 61 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result { 65 - let mut file_size = None; 66 - let mut input_stream = match input { 67 - CmprssInput::Path(paths) => { 68 - if paths.len() > 1 { 69 - bail!("Multiple input files not supported for xz"); 70 - } 71 - let path = &paths[0]; 72 - file_size = Some(std::fs::metadata(path)?.len()); 73 - Box::new(BufReader::new(File::open(path)?)) as Box<dyn Read + Send> 74 - } 75 - CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>, 76 - CmprssInput::Reader(reader) => reader.0, 77 - }; 62 + guard_file_output(&output, "Xz")?; 63 + let (mut input_stream, file_size) = open_input(input, "Xz")?; 64 + 78 65 if let CmprssOutput::Writer(writer) = output { 79 66 let mut encoder = XzEncoder::new(writer, self.level as u32); 80 67 io::copy(&mut input_stream, &mut encoder)?; 81 68 encoder.finish()?; 82 69 } else { 83 - let output_stream: Box<dyn Write + Send> = match &output { 84 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 85 - CmprssOutput::Pipe(pipe) => Box::new(pipe) as Box<dyn Write + Send>, 86 - CmprssOutput::Writer(_) => unreachable!(), 87 - }; 70 + let output_stream = open_output(&output)?; 88 71 let mut encoder = XzEncoder::new(output_stream, self.level as u32); 89 72 copy_with_progress( 90 73 &mut input_stream, ··· 100 83 } 101 84 102 85 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result { 103 - let mut file_size = None; 104 - let input_stream: Box<dyn Read + Send> = match input { 105 - CmprssInput::Path(paths) => { 106 - if paths.len() > 1 { 107 - bail!("Multiple input files not supported for xz extraction"); 108 - } 109 - let path = &paths[0]; 110 - file_size = Some(std::fs::metadata(path)?.len()); 111 - Box::new(BufReader::new(File::open(path)?)) as Box<dyn Read + Send> 112 - } 113 - CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>, 114 - CmprssInput::Reader(reader) => reader.0, 115 - }; 86 + guard_file_output(&output, "Xz")?; 87 + let (input_stream, file_size) = open_input(input, "Xz")?; 116 88 let mut decoder = XzDecoder::new(input_stream); 117 89 118 90 if let CmprssOutput::Writer(mut writer) = output { 119 91 io::copy(&mut decoder, &mut writer)?; 120 92 } else { 121 - let mut output_stream: Box<dyn Write + Send> = match &output { 122 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 123 - CmprssOutput::Pipe(pipe) => Box::new(pipe) as Box<dyn Write + Send>, 124 - CmprssOutput::Writer(_) => unreachable!(), 125 - }; 93 + let mut output_stream = open_output(&output)?; 126 94 copy_with_progress( 127 95 &mut decoder, 128 - &mut *output_stream, 96 + &mut output_stream, 129 97 self.progress_args.chunk_size.size_in_bytes, 130 98 file_size, 131 99 self.progress_args.progress,
+8 -64
src/backends/zstd.rs
··· 1 + use super::stream::{guard_file_output, open_input, open_output}; 1 2 use crate::progress::{ProgressArgs, copy_with_progress}; 2 3 use crate::utils::*; 3 - use anyhow::bail; 4 4 use clap::Args; 5 - use std::fs::File; 6 - use std::io::{self, BufReader, BufWriter, Read, Write}; 5 + use std::io; 7 6 use zstd::stream::{read::Decoder, write::Encoder}; 8 7 9 8 /// Zstd-specific compression validator (-7 to 22 range) ··· 86 85 87 86 /// Compress an input file or pipe to a zstd archive 88 87 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result { 89 - if let CmprssOutput::Path(out_path) = &output 90 - && out_path.is_dir() 91 - { 92 - bail!( 93 - "Zstd does not support compressing to a directory. Please specify an output file." 94 - ); 95 - } 96 - if let CmprssInput::Path(input_paths) = &input { 97 - for x in input_paths { 98 - if x.is_dir() { 99 - bail!( 100 - "Zstd does not support compressing a directory. Please specify only files." 101 - ); 102 - } 103 - } 104 - } 105 - let mut file_size = None; 106 - let mut input_stream: Box<dyn Read + Send> = match input { 107 - CmprssInput::Path(paths) => { 108 - if paths.len() > 1 { 109 - bail!("Multiple input files not supported for zstd"); 110 - } 111 - let path = &paths[0]; 112 - file_size = Some(std::fs::metadata(path)?.len()); 113 - Box::new(BufReader::new(File::open(path)?)) 114 - } 115 - CmprssInput::Pipe(stdin) => Box::new(BufReader::new(stdin)), 116 - CmprssInput::Reader(reader) => reader.0, 117 - }; 88 + guard_file_output(&output, "Zstd")?; 89 + let (mut input_stream, file_size) = open_input(input, "Zstd")?; 118 90 119 91 if let CmprssOutput::Writer(writer) = output { 120 92 let mut encoder = Encoder::new(writer, self.compression_level)?; 121 93 io::copy(&mut input_stream, &mut encoder)?; 122 94 encoder.finish()?; 123 95 } else { 124 - let output_stream: Box<dyn Write + Send> = match &output { 125 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 126 - CmprssOutput::Pipe(stdout) => Box::new(BufWriter::new(stdout)), 127 - CmprssOutput::Writer(_) => unreachable!(), 128 - }; 96 + let output_stream = open_output(&output)?; 129 97 let mut encoder = Encoder::new(output_stream, self.compression_level)?; 130 98 copy_with_progress( 131 99 &mut input_stream, ··· 143 111 144 112 /// Extract a zstd archive to an output file or pipe 145 113 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result { 146 - if let CmprssOutput::Path(out_path) = &output 147 - && out_path.is_dir() 148 - { 149 - bail!( 150 - "Zstd does not support extracting to a directory. Please specify an output file." 151 - ); 152 - } 153 - 154 - let mut file_size = None; 155 - let input_stream: Box<dyn Read + Send> = match input { 156 - CmprssInput::Path(paths) => { 157 - if paths.len() > 1 { 158 - bail!("Multiple input files not supported for zstd extraction"); 159 - } 160 - let path = &paths[0]; 161 - file_size = Some(std::fs::metadata(path)?.len()); 162 - Box::new(BufReader::new(File::open(path)?)) 163 - } 164 - CmprssInput::Pipe(stdin) => Box::new(BufReader::new(stdin)), 165 - CmprssInput::Reader(reader) => reader.0, 166 - }; 167 - 114 + guard_file_output(&output, "Zstd")?; 115 + let (input_stream, file_size) = open_input(input, "Zstd")?; 168 116 let mut decoder = Decoder::new(input_stream)?; 169 117 170 118 if let CmprssOutput::Writer(mut writer) = output { 171 119 io::copy(&mut decoder, &mut writer)?; 172 120 } else { 173 - let mut output_stream: Box<dyn Write + Send> = match &output { 174 - CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)), 175 - CmprssOutput::Pipe(stdout) => Box::new(BufWriter::new(stdout)), 176 - CmprssOutput::Writer(_) => unreachable!(), 177 - }; 121 + let mut output_stream = open_output(&output)?; 178 122 copy_with_progress( 179 123 &mut decoder, 180 124 &mut output_stream,