files_diff/
compress.rs

1use rkyv::Archive;
2use std::io::{Read, Write};
3
4use crate::Error;
5
6const ZSTD_COMPRESSION_LEVEL: i32 = 21;
7
8/// Compression algorithms available for patch data.
9///
10/// Determines how patch data is compressed before storage or transmission.
11/// Different algorithms offer tradeoffs between compression ratio and speed.
12///
13/// # Example
14/// ```rust
15/// use files_diff::{diff, DiffAlgorithm, CompressAlgorithm};
16///
17/// let before = b"original content";
18/// let after = b"modified content";
19///
20/// // Use no compression for debugging or when speed is critical
21/// let uncompressed = diff(
22///     before,
23///     after,
24///     DiffAlgorithm::Rsync020,
25///     CompressAlgorithm::None
26/// )?;
27///
28/// // Use Zstd for maximum compression
29/// let compressed = diff(
30///     before,
31///     after,
32///     DiffAlgorithm::Rsync020,
33///     CompressAlgorithm::Zstd
34/// )?;
35/// # Ok::<(), files_diff::Error>(())
36/// ```
37#[derive(Archive, rkyv::Deserialize, rkyv::Serialize, Debug, PartialEq, Copy, Clone, Eq, Hash)]
38#[rkyv(derive(Debug, PartialEq, Copy, Clone))]
39pub enum CompressAlgorithm {
40    /// No compression. Patch data is stored as-is.
41    /// Use this when:
42    /// - Debugging patches
43    /// - Working with already compressed data
44    /// - Speed is more important than size
45    None,
46
47    /// Zstandard compression with level 21 (maximum compression).
48    /// Use this when:
49    /// - Minimizing patch size is critical
50    /// - Network bandwidth or storage is limited
51    /// - Compression time is not a concern
52    Zstd,
53}
54
55impl std::fmt::Display for CompressAlgorithm {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        write!(f, "{:?}", self)
58    }
59}
60
61impl CompressAlgorithm {
62    /// Compresses the input data using the selected algorithm.
63    pub fn compress(self, input: &[u8]) -> Result<Vec<u8>, Error> {
64        match self {
65            Self::None => Ok(input.to_vec()),
66            Self::Zstd => {
67                let mut encoder =
68                    zstd::Encoder::new(Vec::new(), ZSTD_COMPRESSION_LEVEL).map_err(|e| {
69                        Error::ZipError(format!("failed to create zstd encoder: {}", e))
70                    })?;
71                encoder
72                    .write_all(input)
73                    .map_err(|e| Error::ZipError(format!("failed to write: {}", e)))?;
74                Ok(encoder
75                    .finish()
76                    .map_err(|e| Error::ZipError(format!("failed to finish: {}", e)))?)
77            }
78        }
79    }
80
81    /// Decompresses the input data using the selected algorithm.
82    pub(crate) fn decompress(self, input: &[u8]) -> Result<Vec<u8>, Error> {
83        match self {
84            Self::None => Ok(input.to_vec()),
85            Self::Zstd => {
86                let mut output = Vec::new();
87                let mut decoder = zstd::Decoder::new(input).map_err(|e| {
88                    Error::ZipError(format!("failed to create zstd decoder: {}", e))
89                })?;
90                decoder
91                    .read_to_end(&mut output)
92                    .map_err(|e| Error::ZipError(format!("failed to read: {}", e)))?;
93                Ok(output)
94            }
95        }
96    }
97}