aven-core 0.1.13

Core library for the Aven local-first task manager
Documentation
use std::time::Duration;

use anyhow::Result;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImageOptimizationPolicy {
    Preserve,
    Optimize,
}

pub struct OptimizedBytes {
    pub bytes: Vec<u8>,
    pub optimized: bool,
}

pub async fn optimize_image_bytes(
    media_type: &str,
    bytes: Vec<u8>,
    policy: ImageOptimizationPolicy,
) -> Result<OptimizedBytes> {
    if policy == ImageOptimizationPolicy::Preserve || media_type != "image/png" {
        return Ok(OptimizedBytes {
            bytes,
            optimized: false,
        });
    }

    let optimized = crate::attachments::blocking::run(move || Ok(optimize_png(bytes))).await?;
    match optimized {
        Ok((original, optimized)) if optimized.len() < original.len() => Ok(OptimizedBytes {
            bytes: optimized,
            optimized: true,
        }),
        Ok((original, _)) | Err((original, _)) => Ok(OptimizedBytes {
            bytes: original,
            optimized: false,
        }),
    }
}

type PngOptimizationResult = std::result::Result<(Vec<u8>, Vec<u8>), (Vec<u8>, oxipng::PngError)>;

fn optimize_png(bytes: Vec<u8>) -> PngOptimizationResult {
    let mut options = oxipng::Options::from_preset(4);
    options.strip = oxipng::StripChunks::Safe;
    options.timeout = Some(Duration::from_secs(10));
    options.max_decompressed_size = Some(
        usize::try_from(crate::attachments::decode::MAX_DECODER_ALLOCATION).unwrap_or(usize::MAX),
    );
    match oxipng::optimize_from_memory(&bytes, &options) {
        Ok(optimized) => Ok((bytes, optimized)),
        Err(error) => Err((bytes, error)),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn preserve_policy_keeps_bytes() {
        let bytes = b"png bytes".to_vec();
        let optimized = optimize_image_bytes(
            "image/png",
            bytes.clone(),
            ImageOptimizationPolicy::Preserve,
        )
        .await
        .unwrap();

        assert_eq!(optimized.bytes, bytes);
        assert!(!optimized.optimized);
    }

    #[tokio::test]
    async fn non_png_keeps_bytes() {
        let bytes = b"gif bytes".to_vec();
        let optimized = optimize_image_bytes(
            "image/gif",
            bytes.clone(),
            ImageOptimizationPolicy::Optimize,
        )
        .await
        .unwrap();

        assert_eq!(optimized.bytes, bytes);
        assert!(!optimized.optimized);
    }

    #[tokio::test]
    async fn invalid_png_keeps_bytes() {
        let bytes = b"not a png".to_vec();
        let optimized = optimize_image_bytes(
            "image/png",
            bytes.clone(),
            ImageOptimizationPolicy::Optimize,
        )
        .await
        .unwrap();

        assert_eq!(optimized.bytes, bytes);
        assert!(!optimized.optimized);
    }
}