use std::sync::{Arc, Mutex};
pub type ProgressCallback = Box<dyn FnMut(ProgressEvent) -> bool + Send>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProgressPhase {
Compressing,
Decompressing,
}
#[derive(Debug, Clone)]
pub struct ProgressEvent {
pub bytes_processed: u64,
pub blocks_completed: u64,
pub total_blocks: Option<u64>,
pub phase: ProgressPhase,
}
#[derive(Clone)]
pub struct EngineConfiguration {
pub workers: usize,
pub(crate) thread_pool: Option<Arc<rayon::ThreadPool>>,
pub block_size: u32,
pub compression_level: u8,
pub max_expansion_ratio: f64,
pub max_decompression_ratio: f64,
pub checksums: bool,
pub progress: Option<Arc<Mutex<ProgressCallback>>>,
}
impl Default for EngineConfiguration {
fn default() -> Self {
Self {
workers: 0,
thread_pool: None,
block_size: 1_048_576, compression_level: 6,
max_expansion_ratio: 1.0,
max_decompression_ratio: 1024.0,
checksums: true,
progress: None,
}
}
}
impl EngineConfiguration {
#[must_use]
pub fn builder() -> EngineConfigurationBuilder {
EngineConfigurationBuilder::new()
}
}
#[derive(Default)]
pub struct EngineConfigurationBuilder {
inner: EngineConfiguration,
}
impl EngineConfigurationBuilder {
fn new() -> Self {
Self {
inner: EngineConfiguration::default(),
}
}
#[must_use]
pub fn workers(mut self, n: usize) -> Self {
self.inner.workers = n;
self
}
#[must_use]
pub fn block_size(mut self, bytes: u32) -> Self {
self.inner.block_size = bytes;
self
}
#[must_use]
pub fn compression_level(mut self, level: u8) -> Self {
self.inner.compression_level = level;
self
}
#[must_use]
pub fn max_expansion_ratio(mut self, ratio: f64) -> Self {
self.inner.max_expansion_ratio = ratio;
self
}
#[must_use]
pub fn max_decompression_ratio(mut self, ratio: f64) -> Self {
self.inner.max_decompression_ratio = ratio;
self
}
#[must_use]
pub fn checksums(mut self, enabled: bool) -> Self {
self.inner.checksums = enabled;
self
}
#[must_use]
pub fn progress(mut self, cb: Arc<Mutex<ProgressCallback>>) -> Self {
self.inner.progress = Some(cb);
self
}
pub fn build(self) -> crush_core::error::Result<EngineConfiguration> {
use crush_core::error::CrushError;
let mut cfg = self.inner;
if cfg.block_size < 65_536 || cfg.block_size > 268_435_456 {
return Err(CrushError::InvalidConfig(format!(
"block_size {} is out of range [65536, 268435456]",
cfg.block_size
)));
}
if cfg.compression_level > 9 {
return Err(CrushError::InvalidConfig(format!(
"compression_level {} must be in [0, 9]",
cfg.compression_level
)));
}
if cfg.max_expansion_ratio <= 0.0 {
return Err(CrushError::InvalidConfig(
"max_expansion_ratio must be > 0.0".to_owned(),
));
}
if cfg.max_decompression_ratio <= 0.0 {
return Err(CrushError::InvalidConfig(
"max_decompression_ratio must be > 0.0".to_owned(),
));
}
if cfg.workers > 0 {
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(cfg.workers)
.build()
.map_err(|e| CrushError::InvalidConfig(format!("thread pool: {e}")))?;
cfg.thread_pool = Some(Arc::new(pool));
}
Ok(cfg)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_engine_configuration_builder_validates_fields() {
let err = EngineConfiguration::builder().block_size(1024).build();
assert!(err.is_err());
let err = EngineConfiguration::builder().block_size(u32::MAX).build();
assert!(err.is_err());
let err = EngineConfiguration::builder().compression_level(10).build();
assert!(err.is_err());
let err = EngineConfiguration::builder()
.max_expansion_ratio(0.0)
.build();
assert!(err.is_err());
let err = EngineConfiguration::builder()
.max_decompression_ratio(0.0)
.build();
assert!(err.is_err());
let ok = EngineConfiguration::builder()
.workers(4)
.block_size(1_048_576)
.compression_level(6)
.build();
assert!(ok.is_ok());
}
}