use std::fs::File;
use std::io::{self, Read, Write};
use std::path::Path;
use crate::backends::zopfli_compress::{compress_deflate, ZopfliTuning};
use crate::compress::parallel::GzipHeaderInfo;
pub struct ZopfliGzEncoder {
tuning: ZopfliTuning,
header_info: GzipHeaderInfo,
}
impl ZopfliGzEncoder {
pub fn new(tuning: ZopfliTuning) -> Self {
Self {
tuning,
header_info: GzipHeaderInfo::default(),
}
}
pub fn set_header_info(&mut self, info: GzipHeaderInfo) {
self.header_info = info;
}
pub fn compress<R: Read, W: Write>(&self, mut reader: R, writer: W) -> io::Result<u64> {
let mut data = Vec::new();
let bytes_read = reader.read_to_end(&mut data)? as u64;
self.compress_buffer(&data, writer)?;
Ok(bytes_read)
}
#[allow(dead_code)]
pub fn compress_file<P: AsRef<Path>, W: Write>(&self, path: P, writer: W) -> io::Result<u64> {
let file = File::open(path)?;
let file_size = file.metadata()?.len();
let mut data = Vec::with_capacity(file_size as usize);
std::io::BufReader::new(file).read_to_end(&mut data)?;
self.compress_buffer(&data, writer)?;
Ok(data.len() as u64)
}
fn compress_buffer<W: Write>(&self, data: &[u8], writer: W) -> io::Result<()> {
if data.is_empty() {
return self.write_empty_gzip(writer);
}
self.compress_single(data, writer)
}
fn compress_single<W: Write>(&self, data: &[u8], mut writer: W) -> io::Result<()> {
let deflate_data = compress_deflate(data, &self.tuning);
self.write_gzip_header(&mut writer)?;
writer.write_all(&deflate_data)?;
let crc = crc32fast::hash(data);
let size = data.len() as u32;
writer.write_all(&crc.to_le_bytes())?;
writer.write_all(&size.to_le_bytes())?;
Ok(())
}
fn write_gzip_header<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&[0x1f, 0x8b])?;
writer.write_all(&[8])?;
let mut flg = 0u8;
if self.header_info.filename.is_some() {
flg |= 0x08; }
if self.header_info.comment.is_some() {
flg |= 0x10; }
writer.write_all(&[flg])?;
writer.write_all(&self.header_info.mtime.to_le_bytes())?;
writer.write_all(&[2])?;
writer.write_all(&[255])?;
if let Some(ref name) = self.header_info.filename {
writer.write_all(name.as_bytes())?;
writer.write_all(&[0])?; }
if let Some(ref comment) = self.header_info.comment {
writer.write_all(comment.as_bytes())?;
writer.write_all(&[0])?; }
Ok(())
}
fn write_empty_gzip<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&[0x1f, 0x8b])?;
writer.write_all(&[8])?;
writer.write_all(&[0])?;
writer.write_all(&[0, 0, 0, 0])?;
writer.write_all(&[0])?;
writer.write_all(&[255])?;
writer.write_all(&[0, 0, 0, 0])?;
writer.write_all(&[0, 0, 0, 0])?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Read;
#[test]
fn ultra_output_is_single_member_and_deterministic() {
let data = std::fs::read("test_data/alice.txt").expect("test_data/alice.txt missing");
let tuning = ZopfliTuning::default();
let encode = || {
let encoder = ZopfliGzEncoder::new(tuning.clone());
let mut out = Vec::new();
encoder
.compress(std::io::Cursor::new(&data), &mut out)
.unwrap();
out
};
let a = encode();
let b = encode();
assert_eq!(a, b, "encoder is not deterministic");
assert_eq!(&a[0..2], &[0x1f, 0x8b], "missing gzip magic");
let mut decoded = Vec::new();
flate2::read::GzDecoder::new(a.as_slice())
.read_to_end(&mut decoded)
.expect("single-member gunzip");
assert_eq!(
decoded, data,
"single-member decode must yield the original input \
— multi-member output would short-decode (Phase 11.1.A)"
);
}
}