use std::io::{Error, ErrorKind, Result, Write};
use crate::compressor::session::{EncoderSession, EncoderStatus, Operation};
use crate::io::FinishError;
use crate::shared::format::{BLOCK_HEADER_SIZE, BLOCK_SIZE_MAX};
const PULL_CHUNK: usize = BLOCK_SIZE_MAX + (BLOCK_SIZE_MAX >> 8) + BLOCK_HEADER_SIZE + 4;
pub struct EncoderWriter<'c, W: Write> {
session: EncoderSession<'c>,
sink: W,
buffer: Vec<u8>,
pending: core::ops::Range<usize>,
}
impl<W: Write> std::fmt::Debug for EncoderWriter<'_, W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EncoderWriter")
.field("session", &self.session)
.field("pending", &self.pending.len())
.finish_non_exhaustive()
}
}
impl<'c, W: Write> EncoderWriter<'c, W> {
pub(crate) fn new(session: EncoderSession<'c>, sink: W) -> Self {
Self {
session,
sink,
buffer: vec![0; PULL_CHUNK],
pending: 0..0,
}
}
#[must_use]
pub const fn get_ref(&self) -> &W {
&self.sink
}
fn drain(&mut self) -> Result<()> {
while !self.pending.is_empty() {
match self.sink.write(&self.buffer[self.pending.clone()]) {
Ok(0) => {
return Err(Error::new(
ErrorKind::WriteZero,
"the sink accepted no bytes",
));
}
Ok(n) => self.pending.start += n,
Err(error) if error.kind() == ErrorKind::Interrupted => {}
Err(error) => return Err(error),
}
}
Ok(())
}
fn run(&mut self, mut input: &[u8], operation: Operation) -> Result<usize> {
let mut consumed = 0;
loop {
self.drain()?;
let progress = self
.session
.process(input, &mut self.buffer, operation)
.map_err(Error::other)?;
consumed += progress.consumed;
input = &input[progress.consumed..];
self.pending = 0..progress.produced;
self.drain()?;
match progress.status {
EncoderStatus::NeedsOutput => {}
EncoderStatus::Finished => return Ok(consumed),
EncoderStatus::NeedsInput => {
if operation != Operation::Continue || input.is_empty() {
return Ok(consumed);
}
}
}
}
}
pub fn try_finish(&mut self) -> Result<()> {
if !self.session.is_finished() {
self.run(&[], Operation::End)?;
}
self.drain()?;
self.sink.flush()
}
pub fn finish(mut self) -> std::result::Result<W, FinishError<Self>> {
match self.try_finish() {
Ok(()) => Ok(self.sink),
Err(error) => Err(FinishError::from_parts(error, self)),
}
}
}
impl<W: Write> Write for EncoderWriter<'_, W> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
if self.session.is_finished() {
return Err(Error::other("the frame has already been finished"));
}
self.drain()?;
self.run(buf, Operation::Continue)
}
fn flush(&mut self) -> Result<()> {
if !self.session.is_finished() {
self.run(&[], Operation::Flush)?;
}
self.drain()?;
self.sink.flush()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pull_chunk_is_the_reference_stream_output_size() {
let bound = zstd_ffi::oracle::compress_bound(BLOCK_SIZE_MAX);
assert_eq!(PULL_CHUNK, bound + BLOCK_HEADER_SIZE + 4);
assert_eq!(PULL_CHUNK, unsafe { zstd_ffi::ZSTD_CStreamOutSize() });
}
}