Merge branch 'master' into issue/281

This commit is contained in:
Alexander Zaitsev 2022-03-25 17:19:13 +03:00
commit 7fa870c3b7
No known key found for this signature in database
GPG key ID: FC891851263F1E5D
4 changed files with 132 additions and 12 deletions

View file

@ -23,6 +23,9 @@ sha1 = {version = "0.10.1", optional = true }
time = { version = "0.3.7", features = ["formatting", "macros" ], optional = true } time = { version = "0.3.7", features = ["formatting", "macros" ], optional = true }
zstd = { version = "0.10.0", optional = true } zstd = { version = "0.10.0", optional = true }
[target.'cfg(any(target_arch = "mips", target_arch = "powerpc"))'.dependencies]
crossbeam-utils = "0.8.8"
[dev-dependencies] [dev-dependencies]
bencher = "0.1.5" bencher = "0.1.5"
getrandom = "0.2.5" getrandom = "0.2.5"

View file

@ -691,6 +691,7 @@ pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
#[allow(deprecated)] #[allow(deprecated)]
CompressionMethod::from_u16(compression_method) CompressionMethod::from_u16(compression_method)
}, },
compression_level: None,
last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time), last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
crc32, crc32,
compressed_size: compressed_size as u64, compressed_size: compressed_size as u64,
@ -1086,6 +1087,7 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(
encrypted, encrypted,
using_data_descriptor, using_data_descriptor,
compression_method, compression_method,
compression_level: None,
last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time), last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
crc32, crc32,
compressed_size: compressed_size as u64, compressed_size: compressed_size as u64,

View file

@ -2,8 +2,37 @@
#[cfg(doc)] #[cfg(doc)]
use {crate::read::ZipFile, crate::write::FileOptions}; use {crate::read::ZipFile, crate::write::FileOptions};
#[cfg(not(any(target_arch = "mips", target_arch = "powerpc")))]
use std::sync::atomic; use std::sync::atomic;
#[cfg(any(target_arch = "mips", target_arch = "powerpc"))]
mod atomic {
use crossbeam_utils::sync::ShardedLock;
pub use std::sync::atomic::Ordering;
#[derive(Debug, Default)]
pub struct AtomicU64 {
value: ShardedLock<u64>,
}
impl AtomicU64 {
pub fn new(v: u64) -> Self {
Self {
value: ShardedLock::new(v),
}
}
pub fn get_mut(&mut self) -> &mut u64 {
self.value.get_mut().unwrap()
}
pub fn load(&self, _: Ordering) -> u64 {
*self.value.read().unwrap()
}
pub fn store(&self, value: u64, _: Ordering) {
*self.value.write().unwrap() = value;
}
}
}
#[cfg(feature = "time")] #[cfg(feature = "time")]
use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time}; use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
@ -265,6 +294,8 @@ pub struct ZipFileData {
pub using_data_descriptor: bool, pub using_data_descriptor: bool,
/// Compression method used to store the file /// Compression method used to store the file
pub compression_method: crate::compression::CompressionMethod, 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. /// Last modified time. This will only have a 2 second precision.
pub last_modified_time: DateTime, pub last_modified_time: DateTime,
/// CRC32 checksum /// CRC32 checksum
@ -396,6 +427,7 @@ mod test {
encrypted: false, encrypted: false,
using_data_descriptor: false, using_data_descriptor: false,
compression_method: crate::compression::CompressionMethod::Stored, compression_method: crate::compression::CompressionMethod::Stored,
compression_level: None,
last_modified_time: DateTime::default(), last_modified_time: DateTime::default(),
crc32: 0, crc32: 0,
compressed_size: 0, compressed_size: 0,

View file

@ -11,6 +11,7 @@ use std::default::Default;
use std::io; use std::io;
use std::io::prelude::*; use std::io::prelude::*;
use std::mem; use std::mem;
use std::ops::RangeInclusive;
#[cfg(any( #[cfg(any(
feature = "deflate", feature = "deflate",
@ -103,6 +104,7 @@ struct ZipRawValues {
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct FileOptions { pub struct FileOptions {
compression_method: CompressionMethod, compression_method: CompressionMethod,
compression_level: Option<i32>,
last_modified_time: DateTime, last_modified_time: DateTime,
permissions: Option<u32>, permissions: Option<u32>,
large_file: bool, large_file: bool,
@ -124,6 +126,7 @@ impl FileOptions {
feature = "deflate-zlib" feature = "deflate-zlib"
)))] )))]
compression_method: CompressionMethod::Stored, compression_method: CompressionMethod::Stored,
compression_level: None,
#[cfg(feature = "time")] #[cfg(feature = "time")]
last_modified_time: DateTime::from_time(OffsetDateTime::now_utc()).unwrap_or_default(), last_modified_time: DateTime::from_time(OffsetDateTime::now_utc()).unwrap_or_default(),
#[cfg(not(feature = "time"))] #[cfg(not(feature = "time"))]
@ -143,6 +146,21 @@ impl FileOptions {
self 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 /// Set the last modified time
/// ///
/// The default is the current timestamp if the 'time' feature is enabled, and 1980-01-01 /// 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, encrypted: false,
using_data_descriptor: false, using_data_descriptor: false,
compression_method: options.compression_method, compression_method: options.compression_method,
compression_level: options.compression_level,
last_modified_time: options.last_modified_time, last_modified_time: options.last_modified_time,
crc32: raw_values.crc32, crc32: raw_values.crc32,
compressed_size: raw_values.compressed_size, 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. // Implicitly calling [`ZipWriter::end_extra_data`] for empty files.
self.end_extra_data()?; self.end_extra_data()?;
} }
self.inner.switch_to(CompressionMethod::Stored)?; self.inner.switch_to(CompressionMethod::Stored, None)?;
let writer = self.inner.get_plain(); let writer = self.inner.get_plain();
if !self.writing_raw { if !self.writing_raw {
@ -410,7 +429,8 @@ impl<W: Write + io::Seek> ZipWriter<W> {
} }
*options.permissions.as_mut().unwrap() |= 0o100000; *options.permissions.as_mut().unwrap() |= 0o100000;
self.start_entry(name, options, None)?; 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; self.writing_to_file = true;
Ok(()) Ok(())
} }
@ -587,7 +607,8 @@ impl<W: Write + io::Seek> ZipWriter<W> {
writer.write_u16::<LittleEndian>(extra_field_length)?; writer.write_u16::<LittleEndian>(extra_field_length)?;
writer.seek(io::SeekFrom::Start(header_end))?; 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; 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> { 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() { match self.current_compression() {
Some(method) if method == compression => return Ok(()), Some(method) if method == compression => return Ok(()),
None => { None => {
@ -840,7 +865,15 @@ impl<W: Write + io::Seek> GenericZipWriter<W> {
*self = { *self = {
#[allow(deprecated)] #[allow(deprecated)]
match compression { 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( #[cfg(any(
feature = "deflate", feature = "deflate",
feature = "deflate-miniz", feature = "deflate-miniz",
@ -848,21 +881,50 @@ impl<W: Write + io::Seek> GenericZipWriter<W> {
))] ))]
CompressionMethod::Deflated => GenericZipWriter::Deflater(DeflateEncoder::new( CompressionMethod::Deflated => GenericZipWriter::Deflater(DeflateEncoder::new(
bare, 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")] #[cfg(feature = "bzip2")]
CompressionMethod::Bzip2 => { CompressionMethod::Bzip2 => GenericZipWriter::Bzip2(BzEncoder::new(
GenericZipWriter::Bzip2(BzEncoder::new(bare, bzip2::Compression::default())) 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 => { CompressionMethod::AES => {
return Err(ZipError::UnsupportedArchive( return Err(ZipError::UnsupportedArchive(
"AES compression is not supported for writing", "AES compression is not supported for writing",
)) ))
} }
#[cfg(feature = "zstd")] #[cfg(feature = "zstd")]
CompressionMethod::Zstd => { CompressionMethod::Zstd => GenericZipWriter::Zstd(
GenericZipWriter::Zstd(ZstdEncoder::new(bare, 0).unwrap()) 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(..) => { CompressionMethod::Unsupported(..) => {
return Err(ZipError::UnsupportedArchive("Unsupported compression")) 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<()> { fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
// local file header signature // local file header signature
writer.write_u32::<LittleEndian>(spec::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 mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
let options = FileOptions { let options = FileOptions {
compression_method: CompressionMethod::Stored, compression_method: CompressionMethod::Stored,
compression_level: None,
last_modified_time: DateTime::default(), last_modified_time: DateTime::default(),
permissions: Some(33188), permissions: Some(33188),
large_file: false, large_file: false,