use std::io::{Read, Seek, Write};
use crate::codec::Encoder;
use crate::{ArchivePath, Error, Result};
use super::options::{EntryMeta, WriteOptions};
use super::{PendingEntry, Writer};
pub(crate) const STREAMING_THRESHOLD: u64 = 64 * 1024 * 1024;
const READ_CHUNK: usize = 256 * 1024;
struct CountingWriter<W> {
inner: W,
written: u64,
}
impl<W: Write> CountingWriter<W> {
fn new(inner: W) -> Self {
Self { inner, written: 0 }
}
}
impl<W: Write> Write for CountingWriter<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let n = self.inner.write(buf)?;
self.written += n as u64;
Ok(n)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
fn read_some(source: &mut dyn Read, buffer: &mut [u8]) -> Result<usize> {
let mut filled = 0;
while filled < buffer.len() {
let read = source.read(&mut buffer[filled..]).map_err(Error::Io)?;
if read == 0 {
break;
}
filled += read;
}
Ok(filled)
}
pub(crate) fn can_stream(options: &WriteOptions, size: u64) -> bool {
if size < STREAMING_THRESHOLD {
return false;
}
if options.solid.is_solid() || options.filter.is_active() {
return false;
}
#[cfg(feature = "aes")]
if options.is_data_encrypted() {
return false;
}
encoder_is_available(options)
}
fn encoder_is_available(options: &WriteOptions) -> bool {
use crate::codec::CodecMethod;
match options.method {
CodecMethod::Copy => true,
#[cfg(feature = "lzma2")]
CodecMethod::Lzma2 => true,
#[cfg(feature = "lzma")]
CodecMethod::Lzma => true,
_ => false,
}
}
struct StoreEncoder<W> {
inner: W,
}
impl<W: Write + Send> Write for StoreEncoder<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
impl<W: Write + Send> Encoder for StoreEncoder<W> {
fn method_id(&self) -> &'static [u8] {
crate::codec::method::COPY
}
fn finish(mut self: Box<Self>) -> std::io::Result<()> {
self.inner.flush()
}
}
fn encoder_for<'a, W: Write + Send + 'a>(
options: &WriteOptions,
output: W,
#[cfg_attr(not(feature = "lzma"), allow(unused_variables))] size: u64,
) -> Result<(Box<dyn Encoder + 'a>, Vec<u8>)> {
use crate::codec::CodecMethod;
match options.method {
CodecMethod::Copy => Ok((Box::new(StoreEncoder { inner: output }), Vec::new())),
#[cfg(feature = "lzma2")]
CodecMethod::Lzma2 => {
use crate::codec::lzma::{Lzma2Encoder, Lzma2EncoderOptions};
let opts = Lzma2EncoderOptions {
preset: options.level,
dict_size: Some(super::codecs::dictionary_size(options, size as usize)),
};
let properties = opts.properties();
Ok((Box::new(Lzma2Encoder::new(output, &opts)), properties))
}
#[cfg(feature = "lzma")]
CodecMethod::Lzma => {
use crate::codec::lzma::{LzmaEncoder, LzmaEncoderOptions};
let opts = LzmaEncoderOptions {
preset: options.level,
dict_size: Some(super::codecs::dictionary_size(options, size as usize)),
};
let properties = opts.properties();
Ok((Box::new(LzmaEncoder::new(output, &opts)?), properties))
}
method => Err(Error::UnsupportedMethod {
method_id: method.method_id(),
}),
}
}
impl<W: Write + Seek + Send> Writer<W> {
pub(crate) fn compress_entry_streaming(
&mut self,
archive_path: ArchivePath,
source: &mut dyn Read,
meta: EntryMeta,
size: u64,
) -> Result<()> {
self.flush_buffered_entries()?;
let mut buffer = vec![0u8; READ_CHUNK];
let first = read_some(source, &mut buffer)?;
if first == 0 {
self.entries.push(PendingEntry {
path: archive_path,
meta,
uncompressed_size: 0,
});
return Ok(());
}
let mut crc = crc32fast::Hasher::new();
let mut uncompressed_size = 0u64;
let outcome = {
let mut counting = CountingWriter::new(&mut self.sink);
let (mut encoder, properties) = encoder_for(&self.options, &mut counting, size)?;
let mut result = Ok(());
let mut read = first;
while read > 0 {
crc.update(&buffer[..read]);
uncompressed_size += read as u64;
if let Err(e) = encoder.write_all(&buffer[..read]) {
result = Err(Error::Io(e));
break;
}
match read_some(source, &mut buffer) {
Ok(n) => read = n,
Err(e) => {
result = Err(e);
break;
}
}
}
let finished = encoder.finish().map_err(Error::Io);
result
.and(finished)
.map(|()| (counting.written, properties))
};
let (packed_size, properties) = match outcome {
Ok(values) => values,
Err(e) => return self.fail(e),
};
self.entries.push(PendingEntry {
path: archive_path,
meta,
uncompressed_size,
});
self.compressed_bytes += packed_size;
self.stream_info.pack_sizes.push(packed_size);
self.stream_info.unpack_sizes.push(uncompressed_size);
self.stream_info.coder_methods.push(self.options.method);
self.stream_info.coder_properties.push(properties);
self.stream_info.crcs.push(None);
self.stream_info.substream_sizes.push(uncompressed_size);
self.stream_info.substream_crcs.push(crc.finalize());
#[cfg(feature = "aes")]
self.stream_info.encryption_info.push(None);
self.stream_info.filter_info.push(None);
self.stream_info.bcj2_folder_info.push(None);
self.stream_info.num_unpack_streams_per_folder.push(1);
Ok(())
}
}