diff --git a/Cargo.lock b/Cargo.lock index 894015c..6b9c137 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,9 @@ name = "cc" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -124,6 +127,15 @@ dependencies = [ "libc", ] +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -184,6 +196,7 @@ dependencies = [ "walkdir", "xz2", "zip", + "zstd", ] [[package]] @@ -434,3 +447,32 @@ dependencies = [ "flate2", "thiserror", ] + +[[package]] +name = "zstd" +version = "0.9.0+zstd.1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07749a5dc2cb6b36661290245e350f15ec3bbb304e493db54a1d354480522ccd" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "4.1.1+zstd.1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91c90f2c593b003603e5e0493c837088df4469da25aafff8bce42ba48caf079" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "1.6.1+zstd.1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "615120c7a2431d16cf1cf979e7fc31ba7a5b5e5707b29c8a99e5dbf8a8392a33" +dependencies = [ + "cc", + "libc", +] diff --git a/Cargo.toml b/Cargo.toml index 30531fb..6baa41e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ tar = "0.4.37" xz2 = "0.1.6" zip = { version = "0.5.13", default-features = false, features = ["deflate-miniz"] } flate2 = { version = "1.0.22", default-features = false, features = ["zlib"] } +zstd = "0.9.0+zstd.1.5.0" [dev-dependencies] tempfile = "3.2.0" diff --git a/src/commands.rs b/src/commands.rs index 000c5b5..04630b7 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -170,6 +170,13 @@ fn compress_files( Gzip => Box::new(flate2::write::GzEncoder::new(encoder, Default::default())), Bzip => Box::new(bzip2::write::BzEncoder::new(encoder, Default::default())), Lzma => Box::new(xz2::write::XzEncoder::new(encoder, 6)), + Zstd => { + let zstd_encoder = zstd::stream::write::Encoder::new(encoder, Default::default()); + // Safety: + // Encoder::new() can only fail if `level` is invalid, but Default::default() + // is guaranteed to be valid + Box::new(zstd_encoder.unwrap().auto_finish()) + } _ => unreachable!(), }; encoder @@ -180,7 +187,7 @@ fn compress_files( } match formats[0] { - Gzip | Bzip | Lzma => { + Gzip | Bzip | Lzma | Zstd => { writer = chain_writer_encoder(&formats[0], writer); let mut reader = fs::File::open(&files[0]).unwrap(); io::copy(&mut reader, &mut writer)?; @@ -251,23 +258,24 @@ fn decompress_file( 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| { + 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)), Lzma => Box::new(xz2::read::XzDecoder::new(decoder)), + Zstd => Box::new(zstd::stream::Decoder::new(decoder)?), _ => unreachable!(), }; - decoder + Ok(decoder) }; for format in formats.iter().skip(1).rev() { - reader = chain_reader_decoder(format, reader); + reader = chain_reader_decoder(format, reader)?; } match formats[0] { - Gzip | Bzip | Lzma => { - reader = chain_reader_decoder(&formats[0], reader); + Gzip | Bzip | Lzma | Zstd => { + reader = chain_reader_decoder(&formats[0], reader)?; // TODO: improve error treatment let mut writer = fs::File::create(&output_path)?; diff --git a/src/extension.rs b/src/extension.rs index 1e77165..ed24776 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -11,6 +11,7 @@ pub enum CompressionFormat { Bzip, // .bz Lzma, // .lzma Tar, // .tar (technically not a compression extension, but will do for now) + Zstd, // .zst Zip, // .zip } @@ -22,6 +23,7 @@ impl fmt::Display for CompressionFormat { match self { Gzip => ".gz", Bzip => ".bz", + Zstd => ".zst", Lzma => ".lz", Tar => ".tar", Zip => ".zip", @@ -49,6 +51,7 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec Bzip, _ if extension == "gz" || extension == "bz2" => Gzip, _ if extension == "xz" || extension == "lzma" || extension == "lz" => Lzma, + _ if extension == "zst" => Zstd, _ => break, }; diff --git a/tests/compress_and_decompress.rs b/tests/compress_and_decompress.rs index cb207ad..724ef46 100644 --- a/tests/compress_and_decompress.rs +++ b/tests/compress_and_decompress.rs @@ -21,6 +21,7 @@ fn test_each_format() { test_compressing_and_decompressing_archive("tar.xz"); test_compressing_and_decompressing_archive("tar.lz"); test_compressing_and_decompressing_archive("tar.lzma"); + test_compressing_and_decompressing_archive("tar.zst"); test_compressing_and_decompressing_archive("zip"); test_compressing_and_decompressing_archive("zip.gz"); test_compressing_and_decompressing_archive("zip.bz"); @@ -31,7 +32,7 @@ fn test_each_format() { // Why not test_compressing_and_decompressing_archive( - "tar.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.lz.lz.lz.lz.lz.lz.lz.lz.lz.lz.bz.bz.bz.bz.bz.bz.bz", + "tar.gz.gz.gz.gz.gz.gz.gz.gz.zst.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.lz.lz.lz.lz.lz.lz.lz.lz.lz.lz.bz.bz.bz.bz.bz.bz.bz", ); }