@@ -3,18 +3,21 @@ use flate2::{read::GzDecoder, write::GzEncoder};
33use rayon:: prelude:: * ;
44use std:: { convert:: TryFrom , fmt, io:: Read , io:: Write , path:: Path , str:: FromStr } ;
55use xz2:: { read:: XzDecoder , write:: XzEncoder } ;
6+ use zstd:: stream:: { read:: Decoder as ZstdDecoder , write:: Encoder as ZstdEncoder } ;
67
78#[ derive( Debug , Copy , Clone ) ]
89pub enum CompressionFormat {
910 Gz ,
1011 Xz ,
12+ Zstd ,
1113}
1214
1315impl CompressionFormat {
1416 pub ( crate ) fn detect_from_path ( path : impl AsRef < Path > ) -> Option < Self > {
1517 match path. as_ref ( ) . extension ( ) . and_then ( |e| e. to_str ( ) ) {
1618 Some ( "gz" ) => Some ( CompressionFormat :: Gz ) ,
1719 Some ( "xz" ) => Some ( CompressionFormat :: Xz ) ,
20+ Some ( "zst" ) => Some ( CompressionFormat :: Zstd ) ,
1821 _ => None ,
1922 }
2023 }
@@ -23,6 +26,7 @@ impl CompressionFormat {
2326 match self {
2427 CompressionFormat :: Gz => "gz" ,
2528 CompressionFormat :: Xz => "xz" ,
29+ CompressionFormat :: Zstd => "zst" ,
2630 }
2731 }
2832
@@ -48,6 +52,16 @@ impl CompressionFormat {
4852 . encoder ( ) ?;
4953 Box :: new ( XzEncoder :: new_stream ( file, stream) )
5054 }
55+ CompressionFormat :: Zstd => {
56+ // zstd's default compression level is 3, which is on par with gzip but much faster
57+ let mut enc = ZstdEncoder :: new ( file, 3 ) . context ( "failed to initialize zstd encoder" ) ?;
58+ // Long-distance matching provides a substantial benefit for our tarballs
59+ enc. long_distance_matching ( true ) . context ( "zst long_distance_matching" ) ?;
60+ // Enable multithreaded mode. zstd seems to be faster when using the number of
61+ // physical CPU cores rather than logical/SMT threads.
62+ enc. multithread ( num_cpus:: get_physical ( ) as u32 ) . context ( "zst multithreaded" ) ?;
63+ Box :: new ( enc)
64+ }
5165 } )
5266 }
5367
@@ -56,6 +70,7 @@ impl CompressionFormat {
5670 Ok ( match self {
5771 CompressionFormat :: Gz => Box :: new ( GzDecoder :: new ( file) ) ,
5872 CompressionFormat :: Xz => Box :: new ( XzDecoder :: new ( file) ) ,
73+ CompressionFormat :: Zstd => Box :: new ( ZstdDecoder :: new ( file) ?) ,
5974 } )
6075 }
6176}
@@ -73,6 +88,7 @@ impl TryFrom<&'_ str> for CompressionFormats {
7388 match format. trim ( ) {
7489 "gz" => parsed. push ( CompressionFormat :: Gz ) ,
7590 "xz" => parsed. push ( CompressionFormat :: Xz ) ,
91+ "zst" => parsed. push ( CompressionFormat :: Zstd ) ,
7692 other => anyhow:: bail!( "unknown compression format: {}" , other) ,
7793 }
7894 }
@@ -97,6 +113,7 @@ impl fmt::Display for CompressionFormats {
97113 fmt:: Display :: fmt ( match format {
98114 CompressionFormat :: Xz => "xz" ,
99115 CompressionFormat :: Gz => "gz" ,
116+ CompressionFormat :: Zstd => "zst" ,
100117 } , f) ?;
101118 }
102119 Ok ( ( ) )
@@ -113,6 +130,10 @@ impl CompressionFormats {
113130 pub ( crate ) fn iter ( & self ) -> impl Iterator < Item = CompressionFormat > + ' _ {
114131 self . 0 . iter ( ) . map ( |i| * i)
115132 }
133+
134+ pub ( crate ) fn len ( & self ) -> usize {
135+ self . 0 . len ( )
136+ }
116137}
117138
118139pub ( crate ) trait Encoder : Send + Write {
@@ -133,6 +154,13 @@ impl<W: Send + Write> Encoder for XzEncoder<W> {
133154 }
134155}
135156
157+ impl < W : Send + Write > Encoder for ZstdEncoder < ' _ , W > {
158+ fn finish ( mut self : Box < Self > ) -> Result < ( ) , Error > {
159+ ZstdEncoder :: do_finish ( self . as_mut ( ) ) . context ( "failed to finish .zst file" ) ?;
160+ Ok ( ( ) )
161+ }
162+ }
163+
136164pub ( crate ) struct CombinedEncoder {
137165 encoders : Vec < Box < dyn Encoder > > ,
138166}
0 commit comments