this repo has no description
0
fork

Configure Feed

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

feat(snappy): add snappy framed compression support

+352
+3
src/backends/mod.rs
··· 3 3 mod gzip; 4 4 mod lz4; 5 5 mod pipeline; 6 + mod snappy; 6 7 mod tar; 7 8 mod xz; 8 9 mod zip; ··· 13 14 pub use gzip::{Gzip, GzipArgs}; 14 15 pub use lz4::{Lz4, Lz4Args}; 15 16 pub use pipeline::Pipeline; 17 + pub use snappy::{Snappy, SnappyArgs}; 16 18 pub use tar::{Tar, TarArgs}; 17 19 pub use xz::{Xz, XzArgs}; 18 20 pub use zip::{Zip, ZipArgs}; ··· 32 34 "zstd" | "zst" => Some(Box::<Zstd>::default()), 33 35 "lz4" => Some(Box::<Lz4>::default()), 34 36 "brotli" | "br" => Some(Box::<Brotli>::default()), 37 + "snappy" | "sz" => Some(Box::<Snappy>::default()), 35 38 _ => None, 36 39 } 37 40 }
+167
src/backends/snappy.rs
··· 1 + use crate::progress::{ProgressArgs, copy_with_progress}; 2 + use crate::utils::*; 3 + use anyhow::bail; 4 + use clap::Args; 5 + use snap::read::FrameDecoder; 6 + use snap::write::FrameEncoder; 7 + use std::fs::File; 8 + use std::io::{self, BufReader, BufWriter, Read, Write}; 9 + 10 + #[derive(Args, Debug)] 11 + pub struct SnappyArgs { 12 + #[clap(flatten)] 13 + pub common_args: CommonArgs, 14 + 15 + #[clap(flatten)] 16 + pub progress_args: ProgressArgs, 17 + } 18 + 19 + #[derive(Default)] 20 + pub struct Snappy { 21 + pub progress_args: ProgressArgs, 22 + } 23 + 24 + impl Snappy { 25 + pub fn new(args: &SnappyArgs) -> Snappy { 26 + Snappy { 27 + progress_args: args.progress_args, 28 + } 29 + } 30 + } 31 + 32 + impl Compressor for Snappy { 33 + /// The standard extension for framed snappy files, per Google's reference 34 + /// implementation. 35 + fn extension(&self) -> &str { 36 + "sz" 37 + } 38 + 39 + /// Full name for snappy. 40 + fn name(&self) -> &str { 41 + "snappy" 42 + } 43 + 44 + /// Compress an input file or pipe to a snappy frame-format archive 45 + 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 + }; 75 + 76 + if let CmprssOutput::Writer(writer) = output { 77 + let mut encoder = FrameEncoder::new(writer); 78 + io::copy(&mut input_stream, &mut encoder)?; 79 + encoder.flush()?; 80 + } 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 + }; 86 + let mut encoder = FrameEncoder::new(output_stream); 87 + copy_with_progress( 88 + &mut input_stream, 89 + &mut encoder, 90 + self.progress_args.chunk_size.size_in_bytes, 91 + file_size, 92 + self.progress_args.progress, 93 + &output, 94 + )?; 95 + encoder.flush()?; 96 + } 97 + 98 + Ok(()) 99 + } 100 + 101 + /// Extract a snappy frame-format archive to an output file or pipe 102 + 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 + 125 + let mut decoder = FrameDecoder::new(input_stream); 126 + 127 + if let CmprssOutput::Writer(mut writer) = output { 128 + io::copy(&mut decoder, &mut writer)?; 129 + } 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 + }; 135 + copy_with_progress( 136 + &mut decoder, 137 + &mut output_stream, 138 + self.progress_args.chunk_size.size_in_bytes, 139 + file_size, 140 + self.progress_args.progress, 141 + &output, 142 + )?; 143 + } 144 + 145 + Ok(()) 146 + } 147 + } 148 + 149 + #[cfg(test)] 150 + mod tests { 151 + use super::*; 152 + use crate::test_utils::*; 153 + 154 + /// Test the basic interface of the Snappy compressor 155 + #[test] 156 + fn test_snappy_interface() { 157 + let compressor = Snappy::default(); 158 + test_compressor_interface(&compressor, "snappy", Some("sz")); 159 + } 160 + 161 + /// Test that the round-trip produces identical data 162 + #[test] 163 + fn test_snappy_default_compression() -> Result { 164 + let compressor = Snappy::default(); 165 + test_compression(&compressor) 166 + } 167 + }
+6
src/main.rs
··· 51 51 /// brotli compression 52 52 #[clap(visible_alias = "br")] 53 53 Brotli(BrotliArgs), 54 + 55 + /// snappy framed compression 56 + #[clap(visible_alias = "sz")] 57 + Snappy(SnappyArgs), 54 58 } 55 59 56 60 /// Get the input filename or return a default file ··· 513 517 Some(Format::Zstd(a)) => command(Some(Box::new(Zstd::new(&a))), &a.common_args), 514 518 Some(Format::Lz4(a)) => command(Some(Box::new(Lz4::new(&a))), &a.common_args), 515 519 Some(Format::Brotli(a)) => command(Some(Box::new(Brotli::new(&a))), &a.common_args), 520 + Some(Format::Snappy(a)) => command(Some(Box::new(Snappy::new(&a))), &a.common_args), 516 521 _ => command(None, &args.base_args), 517 522 } 518 523 .unwrap_or_else(|e| { ··· 542 547 assert_eq!(compressor_name("file.zst"), Some("zstd".into())); 543 548 assert_eq!(compressor_name("file.lz4"), Some("lz4".into())); 544 549 assert_eq!(compressor_name("file.br"), Some("brotli".into())); 550 + assert_eq!(compressor_name("file.sz"), Some("snappy".into())); 545 551 assert_eq!(compressor_name("file.tar"), Some("tar".into())); 546 552 assert_eq!(compressor_name("file.zip"), Some("zip".into())); 547 553 }
+176
tests/snappy.rs
··· 1 + use assert_cmd::prelude::*; 2 + use assert_fs::prelude::*; 3 + use predicates::prelude::*; 4 + use std::{ 5 + fs::File, 6 + process::{Command, Stdio}, 7 + }; 8 + 9 + mod common; 10 + use common::*; 11 + 12 + mod snappy { 13 + use super::*; 14 + 15 + mod roundtrip { 16 + use super::*; 17 + 18 + /// Snappy roundtrip using explicit filenames 19 + /// Compressing: input = test.txt, output = test.txt.sz 20 + /// Extracting: input = test.txt.sz, output = test.txt 21 + /// 22 + /// ``` bash 23 + /// cmprss snappy test.txt test.txt.sz 24 + /// cmprss snappy --extract --ignore-pipes test.txt.sz 25 + /// ``` 26 + #[test] 27 + fn explicit() -> Result<(), Box<dyn std::error::Error>> { 28 + let file = create_test_file("test.txt", "garbage data for testing")?; 29 + let working_dir = create_working_dir()?; 30 + let archive = working_dir.child("test.txt.sz"); 31 + archive.assert(predicate::path::missing()); 32 + 33 + let mut compress = Command::cargo_bin("cmprss")?; 34 + compress 35 + .current_dir(&working_dir) 36 + .arg("snappy") 37 + .arg(file.path()) 38 + .arg(archive.path()); 39 + compress.assert().success(); 40 + archive.assert(predicate::path::is_file()); 41 + 42 + let mut extract = Command::cargo_bin("cmprss")?; 43 + extract 44 + .current_dir(&working_dir) 45 + .arg("snappy") 46 + .arg("--ignore-pipes") 47 + .arg("--extract") 48 + .arg(archive.path()); 49 + extract.assert().success(); 50 + 51 + // Assert the files are identical 52 + assert_files_equal(file.path(), &working_dir.child("test.txt")); 53 + 54 + Ok(()) 55 + } 56 + 57 + /// Snappy roundtrip via the `sz` alias 58 + /// Compressing: input = test.txt, output = test.txt.sz 59 + /// Extracting: input = test.txt.sz, output = out.txt 60 + /// 61 + /// ``` bash 62 + /// cmprss sz test.txt test.txt.sz 63 + /// cmprss sz --extract test.txt.sz out.txt 64 + /// ``` 65 + #[test] 66 + fn alias() -> Result<(), Box<dyn std::error::Error>> { 67 + let file = create_test_file("test.txt", "garbage data for the alias test")?; 68 + let working_dir = create_working_dir()?; 69 + let archive = working_dir.child("test.txt.sz"); 70 + 71 + let mut compress = Command::cargo_bin("cmprss")?; 72 + compress 73 + .current_dir(&working_dir) 74 + .arg("sz") 75 + .arg(file.path()) 76 + .arg(archive.path()); 77 + compress.assert().success(); 78 + archive.assert(predicate::path::is_file()); 79 + 80 + let output = working_dir.child("out.txt"); 81 + let mut extract = Command::cargo_bin("cmprss")?; 82 + extract 83 + .current_dir(&working_dir) 84 + .arg("sz") 85 + .arg("--extract") 86 + .arg(archive.path()) 87 + .arg(output.path()); 88 + extract.assert().success(); 89 + 90 + assert_files_equal(file.path(), output.path()); 91 + 92 + Ok(()) 93 + } 94 + 95 + /// Snappy roundtrip using stdin 96 + /// Compressing: input = stdin, output = test.txt.sz 97 + /// Extracting: input = stdin(test.txt.sz), output = out.txt 98 + /// 99 + /// ``` bash 100 + /// cat test.txt | cmprss snappy test.txt.sz 101 + /// cat test.txt.sz | cmprss snappy --extract out.txt 102 + /// ``` 103 + #[test] 104 + fn stdin() -> Result<(), Box<dyn std::error::Error>> { 105 + let file = create_test_file("test.txt", "garbage data for testing")?; 106 + let working_dir = create_working_dir()?; 107 + let archive = working_dir.child("test.txt.sz"); 108 + archive.assert(predicate::path::missing()); 109 + 110 + let mut compress = Command::cargo_bin("cmprss")?; 111 + compress 112 + .current_dir(&working_dir) 113 + .arg("snappy") 114 + .arg("test.txt.sz") 115 + .stdin(Stdio::from(File::open(file.path())?)); 116 + compress.assert().success(); 117 + archive.assert(predicate::path::is_file()); 118 + 119 + let mut extract = Command::cargo_bin("cmprss")?; 120 + extract 121 + .current_dir(&working_dir) 122 + .arg("snappy") 123 + .stdin(Stdio::from(File::open(archive.path())?)) 124 + .arg("--extract") 125 + .arg("out.txt"); 126 + extract.assert().success(); 127 + 128 + // Assert the files are identical 129 + assert_files_equal(file.path(), &working_dir.child("out.txt")); 130 + 131 + Ok(()) 132 + } 133 + 134 + /// Snappy roundtrip using stdout 135 + /// Compressing: input = test.txt, output = stdout 136 + /// Extracting: input = test.txt.sz, output = stdout 137 + /// 138 + /// ``` bash 139 + /// cmprss snappy test.txt > test.txt.sz 140 + /// cmprss snappy --extract test.txt.sz > out.txt 141 + /// ``` 142 + #[test] 143 + fn stdout() -> Result<(), Box<dyn std::error::Error>> { 144 + let file = create_test_file("test.txt", "garbage data for testing")?; 145 + let working_dir = create_working_dir()?; 146 + let archive = working_dir.child("test.txt.sz"); 147 + archive.assert(predicate::path::missing()); 148 + 149 + let mut compress = Command::cargo_bin("cmprss")?; 150 + compress 151 + .current_dir(&working_dir) 152 + .arg("snappy") 153 + .arg(file.path()) 154 + .stdout(Stdio::from(File::create(archive.path())?)); 155 + compress.assert().success(); 156 + archive.assert(predicate::path::is_file()); 157 + 158 + let output = working_dir.child("out.txt"); 159 + output.assert(predicate::path::missing()); 160 + 161 + let mut extract = Command::cargo_bin("cmprss")?; 162 + extract 163 + .current_dir(&working_dir) 164 + .arg("snappy") 165 + .arg("--extract") 166 + .arg(archive.path()) 167 + .stdout(Stdio::from(File::create(output.path())?)); 168 + extract.assert().success(); 169 + output.assert(predicate::path::is_file()); 170 + 171 + assert_files_equal(file.path(), output.path()); 172 + 173 + Ok(()) 174 + } 175 + } 176 + }