Merge pull request #286 from barsgroup/add-compression-level
Add support for specifying compression level
This commit is contained in:
commit
cdcef7db09
3 changed files with 104 additions and 16 deletions
|
@ -679,6 +679,7 @@ pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
|
|||
#[allow(deprecated)]
|
||||
CompressionMethod::from_u16(compression_method)
|
||||
},
|
||||
compression_level: None,
|
||||
last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
|
||||
crc32,
|
||||
compressed_size: compressed_size as u64,
|
||||
|
@ -1074,6 +1075,7 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(
|
|||
encrypted,
|
||||
using_data_descriptor,
|
||||
compression_method,
|
||||
compression_level: None,
|
||||
last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
|
||||
crc32,
|
||||
compressed_size: compressed_size as u64,
|
||||
|
|
|
@ -265,6 +265,8 @@ pub struct ZipFileData {
|
|||
pub using_data_descriptor: bool,
|
||||
/// Compression method used to store the file
|
||||
pub compression_method: crate::compression::CompressionMethod,
|
||||
/// Compression level to store the file
|
||||
pub compression_level: Option<i32>,
|
||||
/// Last modified time. This will only have a 2 second precision.
|
||||
pub last_modified_time: DateTime,
|
||||
/// CRC32 checksum
|
||||
|
@ -396,6 +398,7 @@ mod test {
|
|||
encrypted: false,
|
||||
using_data_descriptor: false,
|
||||
compression_method: crate::compression::CompressionMethod::Stored,
|
||||
compression_level: None,
|
||||
last_modified_time: DateTime::default(),
|
||||
crc32: 0,
|
||||
compressed_size: 0,
|
||||
|
|
107
src/write.rs
107
src/write.rs
|
@ -11,6 +11,7 @@ use std::default::Default;
|
|||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::mem;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
#[cfg(any(
|
||||
feature = "deflate",
|
||||
|
@ -103,6 +104,7 @@ struct ZipRawValues {
|
|||
#[derive(Copy, Clone)]
|
||||
pub struct FileOptions {
|
||||
compression_method: CompressionMethod,
|
||||
compression_level: Option<i32>,
|
||||
last_modified_time: DateTime,
|
||||
permissions: Option<u32>,
|
||||
large_file: bool,
|
||||
|
@ -124,6 +126,7 @@ impl FileOptions {
|
|||
feature = "deflate-zlib"
|
||||
)))]
|
||||
compression_method: CompressionMethod::Stored,
|
||||
compression_level: None,
|
||||
#[cfg(feature = "time")]
|
||||
last_modified_time: DateTime::from_time(OffsetDateTime::now_utc()).unwrap_or_default(),
|
||||
#[cfg(not(feature = "time"))]
|
||||
|
@ -143,6 +146,21 @@ impl FileOptions {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set the compression level for the new file
|
||||
///
|
||||
/// `None` value specifies default compression level.
|
||||
///
|
||||
/// Range of values depends on compression method:
|
||||
/// * `Deflated`: 0 - 9. Default is 6
|
||||
/// * `Bzip2`: 0 - 9. Default is 6
|
||||
/// * `Zstd`: -7 - 22, with zero being mapped to default level. Default is 3
|
||||
/// * others: only `None` is allowed
|
||||
#[must_use]
|
||||
pub fn compression_level(mut self, level: Option<i32>) -> FileOptions {
|
||||
self.compression_level = level;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the last modified time
|
||||
///
|
||||
/// The default is the current timestamp if the 'time' feature is enabled, and 1980-01-01
|
||||
|
@ -340,6 +358,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
|||
encrypted: false,
|
||||
using_data_descriptor: false,
|
||||
compression_method: options.compression_method,
|
||||
compression_level: options.compression_level,
|
||||
last_modified_time: options.last_modified_time,
|
||||
crc32: raw_values.crc32,
|
||||
compressed_size: raw_values.compressed_size,
|
||||
|
@ -375,7 +394,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
|||
// Implicitly calling [`ZipWriter::end_extra_data`] for empty files.
|
||||
self.end_extra_data()?;
|
||||
}
|
||||
self.inner.switch_to(CompressionMethod::Stored)?;
|
||||
self.inner.switch_to(CompressionMethod::Stored, None)?;
|
||||
let writer = self.inner.get_plain();
|
||||
|
||||
if !self.writing_raw {
|
||||
|
@ -410,7 +429,8 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
|||
}
|
||||
*options.permissions.as_mut().unwrap() |= 0o100000;
|
||||
self.start_entry(name, options, None)?;
|
||||
self.inner.switch_to(options.compression_method)?;
|
||||
self.inner
|
||||
.switch_to(options.compression_method, options.compression_level)?;
|
||||
self.writing_to_file = true;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -587,7 +607,8 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
|||
writer.write_u16::<LittleEndian>(extra_field_length)?;
|
||||
writer.seek(io::SeekFrom::Start(header_end))?;
|
||||
|
||||
self.inner.switch_to(file.compression_method)?;
|
||||
self.inner
|
||||
.switch_to(file.compression_method, file.compression_level)?;
|
||||
}
|
||||
|
||||
self.writing_to_extra_field = false;
|
||||
|
@ -803,7 +824,11 @@ impl<W: Write + io::Seek> Drop for ZipWriter<W> {
|
|||
}
|
||||
|
||||
impl<W: Write + io::Seek> GenericZipWriter<W> {
|
||||
fn switch_to(&mut self, compression: CompressionMethod) -> ZipResult<()> {
|
||||
fn switch_to(
|
||||
&mut self,
|
||||
compression: CompressionMethod,
|
||||
compression_level: Option<i32>,
|
||||
) -> ZipResult<()> {
|
||||
match self.current_compression() {
|
||||
Some(method) if method == compression => return Ok(()),
|
||||
None => {
|
||||
|
@ -840,7 +865,15 @@ impl<W: Write + io::Seek> GenericZipWriter<W> {
|
|||
*self = {
|
||||
#[allow(deprecated)]
|
||||
match compression {
|
||||
CompressionMethod::Stored => GenericZipWriter::Storer(bare),
|
||||
CompressionMethod::Stored => {
|
||||
if let Some(_) = compression_level {
|
||||
return Err(ZipError::UnsupportedArchive(
|
||||
"Unsupported compression level",
|
||||
));
|
||||
}
|
||||
|
||||
GenericZipWriter::Storer(bare)
|
||||
}
|
||||
#[cfg(any(
|
||||
feature = "deflate",
|
||||
feature = "deflate-miniz",
|
||||
|
@ -848,21 +881,50 @@ impl<W: Write + io::Seek> GenericZipWriter<W> {
|
|||
))]
|
||||
CompressionMethod::Deflated => GenericZipWriter::Deflater(DeflateEncoder::new(
|
||||
bare,
|
||||
flate2::Compression::default(),
|
||||
flate2::Compression::new(
|
||||
clamp_opt(
|
||||
compression_level
|
||||
.unwrap_or(flate2::Compression::default().level() as i32),
|
||||
deflate_compression_level_range(),
|
||||
)
|
||||
.ok_or(ZipError::UnsupportedArchive(
|
||||
"Unsupported compression level",
|
||||
))? as u32,
|
||||
),
|
||||
)),
|
||||
#[cfg(feature = "bzip2")]
|
||||
CompressionMethod::Bzip2 => {
|
||||
GenericZipWriter::Bzip2(BzEncoder::new(bare, bzip2::Compression::default()))
|
||||
}
|
||||
CompressionMethod::Bzip2 => GenericZipWriter::Bzip2(BzEncoder::new(
|
||||
bare,
|
||||
bzip2::Compression::new(
|
||||
clamp_opt(
|
||||
compression_level
|
||||
.unwrap_or(bzip2::Compression::default().level() as i32),
|
||||
bzip2_compression_level_range(),
|
||||
)
|
||||
.ok_or(ZipError::UnsupportedArchive(
|
||||
"Unsupported compression level",
|
||||
))? as u32,
|
||||
),
|
||||
)),
|
||||
CompressionMethod::AES => {
|
||||
return Err(ZipError::UnsupportedArchive(
|
||||
"AES compression is not supported for writing",
|
||||
))
|
||||
}
|
||||
#[cfg(feature = "zstd")]
|
||||
CompressionMethod::Zstd => {
|
||||
GenericZipWriter::Zstd(ZstdEncoder::new(bare, 0).unwrap())
|
||||
}
|
||||
CompressionMethod::Zstd => GenericZipWriter::Zstd(
|
||||
ZstdEncoder::new(
|
||||
bare,
|
||||
clamp_opt(
|
||||
compression_level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL),
|
||||
zstd::compression_level_range().clone(),
|
||||
)
|
||||
.ok_or(ZipError::UnsupportedArchive(
|
||||
"Unsupported compression level",
|
||||
))?,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
CompressionMethod::Unsupported(..) => {
|
||||
return Err(ZipError::UnsupportedArchive("Unsupported compression"))
|
||||
}
|
||||
|
@ -925,6 +987,26 @@ impl<W: Write + io::Seek> GenericZipWriter<W> {
|
|||
}
|
||||
}
|
||||
|
||||
fn deflate_compression_level_range() -> RangeInclusive<i32> {
|
||||
let min = flate2::Compression::none().level() as i32;
|
||||
let max = flate2::Compression::best().level() as i32;
|
||||
min..=max
|
||||
}
|
||||
|
||||
fn bzip2_compression_level_range() -> RangeInclusive<i32> {
|
||||
let min = bzip2::Compression::none().level() as i32;
|
||||
let max = bzip2::Compression::best().level() as i32;
|
||||
min..=max
|
||||
}
|
||||
|
||||
fn clamp_opt<T: Ord + Copy>(value: T, range: RangeInclusive<T>) -> Option<T> {
|
||||
if range.contains(&value) {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
|
||||
// local file header signature
|
||||
writer.write_u32::<LittleEndian>(spec::LOCAL_FILE_HEADER_SIGNATURE)?;
|
||||
|
@ -1258,6 +1340,7 @@ mod test {
|
|||
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
|
||||
let options = FileOptions {
|
||||
compression_method: CompressionMethod::Stored,
|
||||
compression_level: None,
|
||||
last_modified_time: DateTime::default(),
|
||||
permissions: Some(33188),
|
||||
large_file: false,
|
||||
|
|
Loading…
Add table
Reference in a new issue