1use crate::error::{CDResult, CargoDebError};
2use std::io::{BufWriter, Read};
3#[cfg(feature = "lzma")]
4use std::num::NonZeroUsize;
5use std::process::{Child, ChildStdin, Command, Stdio};
6use std::{io, ops};
7use zopfli::{BlockType, GzipEncoder, Options};
8
9pub struct CompressConfig {
10 pub fast: bool,
12 pub compress_type: Format,
13 pub compress_system: bool,
14 pub rsyncable: bool,
15}
16
17#[derive(Clone, Copy)]
18pub enum Format {
19 Xz,
20 Gzip,
21}
22
23impl Format {
24 #[must_use]
25 pub fn extension(self) -> &'static str {
26 match self {
27 Self::Xz => "xz",
28 Self::Gzip => "gz",
29 }
30 }
31
32 fn program(self) -> &'static str {
33 match self {
34 Self::Xz => "xz",
35 Self::Gzip => "gzip",
36 }
37 }
38
39 const fn level(self, fast: bool) -> u32 {
40 match self {
41 Self::Xz => if fast { 1 } else { 6 },
42 Self::Gzip => if fast { 1 } else { 9 },
43 }
44 }
45}
46
47enum Writer {
48 #[cfg(feature = "lzma")]
49 Xz(xz2::write::XzEncoder<Vec<u8>>),
50 #[cfg(feature = "gzip")]
51 Gz(flate2::write::GzEncoder<Vec<u8>>),
52 ZopfliGz(BufWriter<GzipEncoder<Vec<u8>>>),
53 StdIn {
54 compress_format: Format,
55 child: Child,
56 handle: std::thread::JoinHandle<io::Result<Vec<u8>>>,
57 stdin: BufWriter<ChildStdin>,
58 },
59}
60
61impl Writer {
62 fn finish(self) -> io::Result<Compressed> {
63 match self {
64 #[cfg(feature = "lzma")]
65 Self::Xz(w) => w.finish().map(|data| Compressed { compress_format: Format::Xz, data }),
66 Self::StdIn {
67 compress_format,
68 mut child,
69 handle,
70 stdin,
71 } => {
72 drop(stdin);
73 child.wait()?;
74 handle.join().unwrap().map(|data| Compressed { compress_format, data })
75 },
76 #[cfg(feature = "gzip")]
77 Self::Gz(w) => w.finish().map(|data| Compressed { compress_format: Format::Gzip, data }),
78 Self::ZopfliGz(w) => w.into_inner()?.finish().map(|data| Compressed { compress_format: Format::Gzip, data }),
79 }
80 }
81}
82
83pub struct Compressor {
84 writer: Writer,
85 pub uncompressed_size: usize,
86}
87
88impl io::Write for Compressor {
89 fn flush(&mut self) -> io::Result<()> {
90 match &mut self.writer {
91 #[cfg(feature = "lzma")]
92 Writer::Xz(w) => w.flush(),
93 #[cfg(feature = "gzip")]
94 Writer::Gz(w) => w.flush(),
95 Writer::ZopfliGz(w) => w.flush(),
96 Writer::StdIn { stdin, .. } => stdin.flush(),
97 }
98 }
99
100 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
101 let len = match &mut self.writer {
102 #[cfg(feature = "lzma")]
103 Writer::Xz(w) => w.write(buf),
104 #[cfg(feature = "gzip")]
105 Writer::Gz(w) => w.write(buf),
106 Writer::ZopfliGz(w) => w.write(buf),
107 Writer::StdIn { stdin, .. } => stdin.write(buf),
108 }?;
109 self.uncompressed_size += len;
110 Ok(len)
111 }
112
113 fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
114 match &mut self.writer {
115 #[cfg(feature = "lzma")]
116 Writer::Xz(w) => w.write_all(buf),
117 #[cfg(feature = "gzip")]
118 Writer::Gz(w) => w.write_all(buf),
119 Writer::ZopfliGz(w) => w.write_all(buf),
120 Writer::StdIn { stdin, .. } => stdin.write_all(buf),
121 }?;
122 self.uncompressed_size += buf.len();
123 Ok(())
124 }
125}
126
127impl Compressor {
128 const fn new(writer: Writer) -> Self {
129 Self { writer, uncompressed_size: 0 }
130 }
131
132 pub fn finish(self) -> CDResult<Compressed> {
133 self.writer.finish().map_err(CargoDebError::Io)
134 }
135}
136
137pub struct Compressed {
138 compress_format: Format,
139 data: Vec<u8>,
140}
141
142impl Compressed {
143 #[must_use]
144 pub fn extension(&self) -> &'static str {
145 self.compress_format.extension()
146 }
147}
148
149impl ops::Deref for Compressed {
150 type Target = Vec<u8>;
151
152 fn deref(&self) -> &Self::Target {
153 &self.data
154 }
155}
156
157fn system_compressor(compress_format: Format, fast: bool) -> CDResult<Compressor> {
158 let mut child = Command::new(compress_format.program())
159 .arg(format!("-{}", compress_format.level(fast)))
160 .stdin(Stdio::piped())
161 .stdout(Stdio::piped())
162 .stderr(Stdio::inherit())
163 .spawn()
164 .map_err(|e| CargoDebError::CommandFailed(e, compress_format.program().into()))?;
165 let mut stdout = child.stdout.take().unwrap();
166
167 let handle = std::thread::spawn(move || {
168 let mut buf = Vec::new();
169 stdout.read_to_end(&mut buf).map(|_| buf)
170 });
171
172 let stdin = BufWriter::with_capacity(1<<16, child.stdin.take().unwrap());
173 Ok(Compressor::new(Writer::StdIn { compress_format, child, handle, stdin }))
174}
175
176pub fn select_compressor(fast: bool, compress_format: Format, use_system: bool) -> CDResult<Compressor> {
177 if use_system {
178 return system_compressor(compress_format, fast);
179 }
180
181 match compress_format {
182 #[cfg(feature = "lzma")]
183 Format::Xz => {
184 let encoder = xz2::stream::MtStreamBuilder::new()
186 .threads(std::thread::available_parallelism().unwrap_or(NonZeroUsize::new(1).unwrap()).get() as u32)
187 .preset(compress_format.level(fast))
188 .encoder()
189 .map_err(CargoDebError::LzmaCompressionError)?;
190
191 let writer = xz2::write::XzEncoder::new_stream(Vec::new(), encoder);
192 Ok(Compressor::new(Writer::Xz(writer)))
193 },
194 #[cfg(not(feature = "lzma"))]
195 Format::Xz => system_compressor(compress_format, fast),
196 Format::Gzip => {
197 #[cfg(feature = "gzip")]
198 if fast {
199 let level = flate2::Compression::new(compress_format.level(fast));
200 let inner_writer = flate2::write::GzEncoder::new(Vec::new(), level);
201 return Ok(Compressor::new(Writer::Gz(inner_writer)));
202 }
203
204 let inner_writer = GzipEncoder::new_buffered(Options {
205 iteration_count: (if fast { 1 } else { 7 }).try_into().unwrap(),
206 ..Options::default()
207 }, BlockType::Dynamic, Vec::new()).unwrap();
208 Ok(Compressor::new(Writer::ZopfliGz(inner_writer)))
209 },
210 }
211}
212
213pub(crate) fn gzipped(mut content: &[u8]) -> io::Result<Vec<u8>> {
214 let mut compressed = Vec::new();
215 compressed.try_reserve(content.len() * 2 / 3)
216 .map_err(|_| io::ErrorKind::OutOfMemory)?;
217 let mut encoder = GzipEncoder::new(
218 Options {
219 iteration_count: 7.try_into().unwrap(),
220 ..Options::default()
221 },
222 BlockType::Dynamic,
223 &mut compressed,
224 )?;
225 io::copy(&mut content, &mut encoder)?;
226 encoder.finish()?;
227 Ok(compressed)
228}