zopfli/
gzip.rs

1use crate::{BlockType, DeflateEncoder, Error, Options, Write};
2
3/// A Gzip encoder powered by the Zopfli algorithm, that compresses data using
4/// a [`DeflateEncoder`]. Most users will find using [`compress`](crate::compress)
5/// easier and more performant.
6///
7/// The caveats about short writes in [`DeflateEncoder`]s carry over to `GzipEncoder`s:
8/// for best performance and compression, it is best to avoid them. One way to ensure
9/// this is to use the [`new_buffered`](GzipEncoder::new_buffered) method.
10pub struct GzipEncoder<W: Write> {
11    deflate_encoder: Option<DeflateEncoder<W>>,
12    crc32_hasher: crc32fast::Hasher,
13    input_size: u32,
14}
15
16impl<W: Write> GzipEncoder<W> {
17    /// Creates a new Gzip encoder that will operate according to the
18    /// specified options.
19    pub fn new(options: Options, btype: BlockType, mut sink: W) -> Result<Self, Error> {
20        static HEADER: &[u8] = &[
21            31,  // ID1
22            139, // ID2
23            8,   // CM
24            0,   // FLG
25            0,   // MTIME
26            0, 0, 0, 2, // XFL, 2 indicates best compression.
27            3, // OS follows Unix conventions.
28        ];
29
30        sink.write_all(HEADER)?;
31
32        Ok(Self {
33            deflate_encoder: Some(DeflateEncoder::new(options, btype, sink)),
34            crc32_hasher: crc32fast::Hasher::new(),
35            input_size: 0,
36        })
37    }
38
39    /// Creates a new Gzip encoder that operates according to the specified
40    /// options and is wrapped with a buffer to guarantee that data is
41    /// compressed in large chunks, which is necessary for decent performance
42    /// and good compression ratio.
43    #[cfg(feature = "std")]
44    pub fn new_buffered(
45        options: Options,
46        btype: BlockType,
47        sink: W,
48    ) -> Result<std::io::BufWriter<Self>, Error> {
49        Ok(std::io::BufWriter::with_capacity(
50            crate::util::ZOPFLI_MASTER_BLOCK_SIZE,
51            Self::new(options, btype, sink)?,
52        ))
53    }
54
55    /// Encodes any pending chunks of data and writes them to the sink,
56    /// consuming the encoder and returning the wrapped sink. The sink
57    /// will have received a complete Gzip stream when this method
58    /// returns.
59    ///
60    /// The encoder is automatically [`finish`](Self::finish)ed when
61    /// dropped, but explicitly finishing it with this method allows
62    /// handling I/O errors.
63    pub fn finish(mut self) -> Result<W, Error> {
64        self.__finish().map(|sink| sink.unwrap())
65    }
66
67    fn __finish(&mut self) -> Result<Option<W>, Error> {
68        if self.deflate_encoder.is_none() {
69            return Ok(None);
70        }
71
72        let mut sink = self.deflate_encoder.take().unwrap().finish()?;
73
74        sink.write_all(&self.crc32_hasher.clone().finalize().to_le_bytes())?;
75        sink.write_all(&self.input_size.to_le_bytes())?;
76
77        Ok(Some(sink))
78    }
79
80    /// Gets a reference to the underlying writer.
81    pub fn get_ref(&self) -> &W {
82        self.deflate_encoder.as_ref().unwrap().get_ref()
83    }
84
85    /// Gets a mutable reference to the underlying writer.
86    ///
87    /// Note that mutating the output/input state of the stream may corrupt
88    /// this object, so care must be taken when using this method.
89    pub fn get_mut(&mut self) -> &mut W {
90        self.deflate_encoder.as_mut().unwrap().get_mut()
91    }
92}
93
94impl<W: Write> Write for GzipEncoder<W> {
95    fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
96        self.deflate_encoder
97            .as_mut()
98            .unwrap()
99            .write(buf)
100            .map(|bytes_written| {
101                self.crc32_hasher.update(&buf[..bytes_written]);
102                self.input_size = self.input_size.wrapping_add(bytes_written as u32);
103                bytes_written
104            })
105    }
106
107    fn flush(&mut self) -> Result<(), Error> {
108        self.deflate_encoder.as_mut().unwrap().flush()
109    }
110}
111
112impl<W: Write> Drop for GzipEncoder<W> {
113    fn drop(&mut self) {
114        self.__finish().ok();
115    }
116}
117
118// Boilerplate to make latest Rustdoc happy: https://github.com/rust-lang/rust/issues/117796
119#[cfg(all(doc, feature = "std"))]
120impl<W: crate::io::Write> std::io::Write for GzipEncoder<W> {
121    fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
122        unimplemented!()
123    }
124
125    fn flush(&mut self) -> std::io::Result<()> {
126        unimplemented!()
127    }
128}