use alloc::boxed::Box;
use alloc::vec::Vec;
use core::mem::MaybeUninit;
use core::ops::Range;
use super::config::{ConfigError, EncoderConfig, SizeOverflow};
use super::core::cctx::{Engine, RequestedParams, compress_bound};
use super::core::cdict::CDict;
use super::core::stream::Stream;
use super::dictionary::PreparedDictionary;
use super::error::EncodeError;
use super::internal::requested_params;
use super::session::{EncoderSession, EncoderSessionOwned, StreamConfig};
use crate::{Backend, RetentionPolicy};
#[derive(Debug)]
pub struct Compressor {
config: EncoderConfig,
pub(crate) requested: RequestedParams,
retention: RetentionPolicy,
backend: Backend,
pub(crate) engine: Box<Engine>,
pub(crate) stream: Stream,
next_input_id: u64,
}
impl Default for Compressor {
fn default() -> Self {
Self::validated(
EncoderConfig::default(),
RetentionPolicy::Aggressive,
Backend::default(),
)
}
}
impl Compressor {
fn validated(config: EncoderConfig, retention: RetentionPolicy, backend: Backend) -> Self {
Self {
config,
requested: requested_params(&config),
retention,
backend,
engine: Box::new(Engine::with_backend(backend)),
stream: Stream::new(),
next_input_id: 0,
}
}
pub fn new(config: EncoderConfig) -> Result<Self, ConfigError> {
Self::builder(config).build()
}
#[must_use]
pub const fn builder(config: EncoderConfig) -> CompressorBuilder {
CompressorBuilder {
config,
retention: RetentionPolicy::Aggressive,
backend: None,
}
}
#[must_use]
pub const fn config(&self) -> &EncoderConfig {
&self.config
}
#[must_use]
pub const fn backend(&self) -> Backend {
self.backend
}
#[must_use]
pub const fn retention(&self) -> RetentionPolicy {
self.retention
}
pub fn reconfigure(&mut self, config: EncoderConfig) -> Result<(), ConfigError> {
config.validate()?;
self.config = config;
self.requested = requested_params(&config);
self.stream.reset_session();
Ok(())
}
pub const fn max_compressed_size(input_size: usize) -> Result<usize, SizeOverflow> {
match compress_bound(input_size) {
Some(bound) => Ok(bound),
None => Err(SizeOverflow),
}
}
fn next_id(&mut self) -> u64 {
self.next_input_id += 1;
self.next_input_id
}
pub fn compress(&mut self, src: &[u8]) -> Result<Vec<u8>, EncodeError> {
let mut out = Vec::new();
self.compress_into(src, &mut out)?;
Ok(out)
}
pub fn compress_into(
&mut self,
src: &[u8],
dst: &mut Vec<u8>,
) -> Result<Range<usize>, EncodeError> {
self.compress_into_with(None, src, dst)
}
fn dictionary_tables<'d>(
&self,
dictionary: Option<&'d PreparedDictionary>,
) -> Result<Option<&'d CDict>, EncodeError> {
match dictionary {
None => Ok(None),
Some(dictionary) if dictionary.serves(&self.requested) => Ok(Some(dictionary.cdict())),
Some(_) => Err(EncodeError::DictionaryMismatch),
}
}
fn compress_into_with(
&mut self,
dictionary: Option<&PreparedDictionary>,
src: &[u8],
dst: &mut Vec<u8>,
) -> Result<Range<usize>, EncodeError> {
let cdict = self.dictionary_tables(dictionary)?;
let bound = Self::max_compressed_size(src.len())?;
let start = dst.len();
let limit = start.checked_add(bound).ok_or(SizeOverflow)?;
let result = if src.len() <= crate::shared::format::BLOCK_SIZE_MAX {
dst.resize(limit, 0);
self.compress_to_slice_with(cdict, src, &mut dst[start..])
} else {
self.stream.reset_session();
let id = self.next_id();
let result = super::core::compress_once_append(
&mut self.engine,
&self.requested,
cdict,
src,
id,
dst,
bound,
)
.map_err(|error| EncodeError::from_core(error, bound, Some(src.len() as u64)));
self.apply_retention();
result
};
match result {
Ok(written) => {
dst.truncate(start + written);
Ok(start..start + written)
}
Err(error) => {
dst.truncate(start);
Err(error)
}
}
}
pub fn compress_to_slice(&mut self, src: &[u8], dst: &mut [u8]) -> Result<usize, EncodeError> {
self.compress_to_slice_with(None, src, dst)
}
fn compress_to_slice_with(
&mut self,
cdict: Option<&CDict>,
src: &[u8],
dst: &mut [u8],
) -> Result<usize, EncodeError> {
self.stream.reset_session();
let id = self.next_id();
let result =
super::core::compress_once_into(&mut self.engine, &self.requested, cdict, src, id, dst)
.map_err(|error| EncodeError::from_core(error, dst.len(), Some(src.len() as u64)));
self.apply_retention();
result
}
pub fn compress_to_uninit(
&mut self,
src: &[u8],
dst: &mut [MaybeUninit<u8>],
) -> Result<usize, EncodeError> {
self.compress_to_uninit_with(None, src, dst)
}
fn compress_to_uninit_with(
&mut self,
cdict: Option<&CDict>,
src: &[u8],
dst: &mut [MaybeUninit<u8>],
) -> Result<usize, EncodeError> {
self.stream.reset_session();
let id = self.next_id();
let capacity = dst.len();
let result = super::core::compress_once_uninit(
&mut self.engine,
&self.requested,
cdict,
src,
id,
dst,
)
.map_err(|error| EncodeError::from_core(error, capacity, Some(src.len() as u64)));
self.apply_retention();
result
}
pub fn start(&mut self, stream: StreamConfig) -> EncoderSession<'_> {
self.begin_stream(stream);
EncoderSession::new(self, stream)
}
#[must_use]
pub fn into_session(mut self, stream: StreamConfig) -> EncoderSessionOwned {
self.begin_stream(stream);
EncoderSessionOwned::new(self, stream)
}
pub fn compress_with_dictionary(
&mut self,
dictionary: &PreparedDictionary,
src: &[u8],
) -> Result<Vec<u8>, EncodeError> {
let mut out = Vec::new();
self.compress_into_with(Some(dictionary), src, &mut out)?;
Ok(out)
}
pub fn compress_with_dictionary_into(
&mut self,
dictionary: &PreparedDictionary,
src: &[u8],
dst: &mut Vec<u8>,
) -> Result<Range<usize>, EncodeError> {
self.compress_into_with(Some(dictionary), src, dst)
}
pub fn compress_with_dictionary_to_slice(
&mut self,
dictionary: &PreparedDictionary,
src: &[u8],
dst: &mut [u8],
) -> Result<usize, EncodeError> {
let cdict = self.dictionary_tables(Some(dictionary))?;
self.compress_to_slice_with(cdict, src, dst)
}
pub fn compress_with_dictionary_to_uninit(
&mut self,
dictionary: &PreparedDictionary,
src: &[u8],
dst: &mut [MaybeUninit<u8>],
) -> Result<usize, EncodeError> {
let cdict = self.dictionary_tables(Some(dictionary))?;
self.compress_to_uninit_with(cdict, src, dst)
}
pub fn start_with_dictionary<'c>(
&'c mut self,
dictionary: &'c PreparedDictionary,
stream: StreamConfig,
) -> Result<EncoderSession<'c>, EncodeError> {
self.dictionary_tables(Some(dictionary))?;
self.begin_stream(stream);
Ok(EncoderSession::with_dictionary(
self,
Some(dictionary),
stream,
))
}
pub fn into_session_with_dictionary<D: AsRef<PreparedDictionary>>(
mut self,
dictionary: D,
stream: StreamConfig,
) -> Result<EncoderSessionOwned<D>, EncodeError> {
self.dictionary_tables(Some(dictionary.as_ref()))?;
self.begin_stream(stream);
Ok(EncoderSessionOwned::with_dictionary(
self,
Some(dictionary),
stream,
))
}
pub(crate) fn begin_stream(&mut self, stream: StreamConfig) {
self.stream.reset_session();
if let super::session::InputSize::Exact(size) = stream.input_size() {
let _ = self.stream.set_pledged_src_size(size);
}
}
#[must_use]
pub fn retained_bytes(&self) -> usize {
self.engine.retained_bytes() + self.stream.retained_bytes()
}
pub fn trim(&mut self, policy: RetentionPolicy) {
if !policy.allows(self.retained_bytes()) {
*self.engine = Engine::with_backend(self.backend);
self.stream.release();
}
}
pub(crate) fn apply_retention(&mut self) {
self.trim(self.retention);
}
}
#[derive(Debug, Clone, Copy)]
pub struct CompressorBuilder {
config: EncoderConfig,
retention: RetentionPolicy,
backend: Option<Backend>,
}
impl CompressorBuilder {
#[must_use]
pub const fn with_retention(mut self, retention: RetentionPolicy) -> Self {
self.retention = retention;
self
}
#[must_use]
pub const fn with_backend(mut self, backend: Backend) -> Self {
self.backend = Some(backend);
self
}
pub fn build(self) -> Result<Compressor, ConfigError> {
self.config.validate()?;
Ok(Compressor::validated(
self.config,
self.retention,
self.backend.unwrap_or_default(),
))
}
}