@@ -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
@@ -71,6 +75,16 @@ impl CompressionFormat {
7175 ) ;
7276 Box :: new ( compressor)
7377 }
78+ CompressionFormat :: Zstd => {
79+ // zstd's default compression level is 3, which is on par with gzip but much faster
80+ let mut enc = ZstdEncoder :: new ( file, 3 ) . context ( "failed to initialize zstd encoder" ) ?;
81+ // Long-distance matching provides a substantial benefit for our tarballs
82+ enc. long_distance_matching ( true ) . context ( "zst long_distance_matching" ) ?;
83+ // Enable multithreaded mode. zstd seems to be faster when using the number of
84+ // physical CPU cores rather than logical/SMT threads.
85+ enc. multithread ( num_cpus:: get_physical ( ) as u32 ) . context ( "zst multithreaded" ) ?;
86+ Box :: new ( enc)
87+ }
7488 } )
7589 }
7690
@@ -79,6 +93,7 @@ impl CompressionFormat {
7993 Ok ( match self {
8094 CompressionFormat :: Gz => Box :: new ( GzDecoder :: new ( file) ) ,
8195 CompressionFormat :: Xz => Box :: new ( XzDecoder :: new ( file) ) ,
96+ CompressionFormat :: Zstd => Box :: new ( ZstdDecoder :: new ( file) ?) ,
8297 } )
8398 }
8499}
@@ -96,6 +111,7 @@ impl TryFrom<&'_ str> for CompressionFormats {
96111 match format. trim ( ) {
97112 "gz" => parsed. push ( CompressionFormat :: Gz ) ,
98113 "xz" => parsed. push ( CompressionFormat :: Xz ) ,
114+ "zst" => parsed. push ( CompressionFormat :: Zstd ) ,
99115 other => anyhow:: bail!( "unknown compression format: {}" , other) ,
100116 }
101117 }
@@ -121,6 +137,7 @@ impl fmt::Display for CompressionFormats {
121137 match format {
122138 CompressionFormat :: Xz => "xz" ,
123139 CompressionFormat :: Gz => "gz" ,
140+ CompressionFormat :: Zstd => "zst" ,
124141 } ,
125142 f,
126143 ) ?;
@@ -139,6 +156,10 @@ impl CompressionFormats {
139156 pub ( crate ) fn iter ( & self ) -> impl Iterator < Item = CompressionFormat > + ' _ {
140157 self . 0 . iter ( ) . map ( |i| * i)
141158 }
159+
160+ pub ( crate ) fn len ( & self ) -> usize {
161+ self . 0 . len ( )
162+ }
142163}
143164
144165pub ( crate ) trait Encoder : Send + Write {
@@ -159,6 +180,13 @@ impl<W: Send + Write> Encoder for XzEncoder<W> {
159180 }
160181}
161182
183+ impl < W : Send + Write > Encoder for ZstdEncoder < ' _ , W > {
184+ fn finish ( mut self : Box < Self > ) -> Result < ( ) , Error > {
185+ ZstdEncoder :: do_finish ( self . as_mut ( ) ) . context ( "failed to finish .zst file" ) ?;
186+ Ok ( ( ) )
187+ }
188+ }
189+
162190pub ( crate ) struct CombinedEncoder {
163191 encoders : Vec < Box < dyn Encoder > > ,
164192}
0 commit comments