1use crate::error::CompressionResult;
19use crate::{BrotliCompressor, CompressionLevel, Compressor, ZlibCompressor, ZstdCompressor};
20use std::fmt;
21use std::path::Path;
22
23#[allow(missing_docs)]
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum ObjectType {
27 Jpeg,
29 Png,
30 Gif,
31 Webp,
32 Avif,
33 Heic,
34
35 GpuTexture,
37
38 Tiff,
40 Bmp,
41 Raw,
42 Exr,
43 Hdr,
44
45 Mp4,
47 Mov,
48 Avi,
49 Mkv,
50 Webm,
51 Flv,
52 Wmv,
53 Mpg,
54
55 Mp3,
57 Aac,
58 Ogg,
59 Opus,
60
61 Flac,
63 Wav,
64 Aiff,
65 Alac,
66
67 Pdf,
69 Svg,
70 Eps,
71
72 Text,
74 Json,
75 Xml,
76 Yaml,
77 Toml,
78 Csv,
79
80 Zip,
82 Tar,
83 Gz,
84 SevenZ,
85 Rar,
86
87 Parquet,
89
90 MlData,
92
93 MlModel,
95
96 MlDeployment,
98
99 MlCheckpoint,
101
102 MlInference,
104
105 AdobePhotoshop, AdobeIllustrator, AdobeIndesign, AdobeAfterEffects, AdobePremiere, DavinciResolve, FinalCutPro, AvidMediaComposer, Model3D, Blender, Maya, ThreeDsMax, Cinema4D, Houdini, ProTools, AbletonLive, FLStudio, LogicPro, AutoCad, SketchUp, Revit, UnityProject, UnrealProject, GodotProject, WordDocument, ExcelSpreadsheet, PowerpointPresentation, OpenDocument, SqliteDatabase, CompressedLog, GitBlob,
157 GitTree,
158 GitCommit,
159
160 Unknown,
162}
163
164impl ObjectType {
165 pub fn from_extension(ext: &str) -> Self {
167 match ext.to_lowercase().as_str() {
168 "jpg" | "jpeg" => ObjectType::Jpeg,
170 "png" => ObjectType::Png,
171 "gif" => ObjectType::Gif,
172 "webp" => ObjectType::Webp,
173 "avif" => ObjectType::Avif,
174 "heic" | "heif" => ObjectType::Heic,
175
176 "dds" | "ktx" | "ktx2" | "astc" | "pvr" | "basis" => ObjectType::GpuTexture,
178
179 "tif" | "tiff" => ObjectType::Tiff,
181 "bmp" | "dib" => ObjectType::Bmp,
182 "psd" | "psb" => ObjectType::AdobePhotoshop, "raw" | "cr2" | "cr3" | "nef" | "arw" | "dng" | "orf" | "rw2" => ObjectType::Raw,
184 "exr" => ObjectType::Exr,
185 "hdr" | "pic" => ObjectType::Hdr,
186
187 "mp4" | "m4v" => ObjectType::Mp4,
189 "mov" | "qt" => ObjectType::Mov,
190 "avi" => ObjectType::Avi,
191 "mkv" => ObjectType::Mkv,
192 "webm" => ObjectType::Webm,
193 "flv" | "f4v" => ObjectType::Flv,
194 "wmv" | "asf" => ObjectType::Wmv,
195 "mpg" | "mpeg" | "m2v" => ObjectType::Mpg,
196
197 "mp3" => ObjectType::Mp3,
199 "aac" => ObjectType::Aac,
200 "m4a" => ObjectType::Aac, "ogg" | "oga" => ObjectType::Ogg,
202 "opus" => ObjectType::Opus,
203
204 "flac" => ObjectType::Flac,
206 "wav" => ObjectType::Wav,
207 "aiff" | "aif" | "aifc" => ObjectType::Aiff,
208 "alac" => ObjectType::Alac,
209
210 "pdf" => ObjectType::Pdf,
212 "svg" | "svgz" => ObjectType::Svg,
213 "eps" => ObjectType::Eps, "txt" | "md" | "markdown" | "rst" | "adoc" | "rs" | "js" | "ts" | "jsx" | "tsx"
217 | "py" | "go" | "c" | "cpp" | "cc" | "cxx" | "h" | "hpp" | "hh" | "hxx" | "java"
218 | "kt" | "swift" | "rb" | "php" | "sh" | "bash" | "zsh" | "fish" | "vim" | "lua"
219 | "pl" | "r" | "m" => ObjectType::Text,
220 "json" | "json5" | "jsonc" => ObjectType::Json,
221 "xml" | "html" | "xhtml" | "htm" | "xsl" | "xslt" => ObjectType::Xml,
222 "yml" | "yaml" => ObjectType::Yaml,
223 "toml" => ObjectType::Toml,
224 "csv" | "tsv" | "psv" => ObjectType::Csv,
225
226 "zip" | "zipx" => ObjectType::Zip,
228 "tar" => ObjectType::Tar,
229 "gz" | "gzip" => ObjectType::Gz,
230 "7z" => ObjectType::SevenZ,
231 "rar" => ObjectType::Rar,
232
233 "parquet" | "arrow" | "feather" | "orc" | "avro" => ObjectType::Parquet,
235
236 "hdf5" | "h5" | "nc" | "netcdf" | "npy" | "npz" | "tfrecords" | "petastorm" => {
238 ObjectType::MlData
239 }
240
241 "pb" | "safetensors" | "pkl" | "joblib" => ObjectType::MlModel,
243
244 "ckpt" | "pt" | "pth" | "bin" => ObjectType::MlCheckpoint,
248
249 "onnx" | "gguf" | "ggml" | "tflite" | "mlmodel" | "coreml" | "keras" | "pte"
251 | "mleap" | "pmml" | "llamafile" => ObjectType::MlInference,
252
253 "ai" | "ait" => ObjectType::AdobeIllustrator,
255 "indd" | "idml" | "indt" => ObjectType::AdobeIndesign,
256 "aep" | "aet" => ObjectType::AdobeAfterEffects,
257 "prproj" | "psq" => ObjectType::AdobePremiere,
258
259 "drp" | "drp_proxies" => ObjectType::DavinciResolve,
261 "fcpbundle" | "fcpxml" | "fcpxmld" => ObjectType::FinalCutPro,
262 "avb" | "avp" | "avs" => ObjectType::AvidMediaComposer,
263
264 "stl" | "obj" | "fbx" | "glb" | "gltf" | "ply" | "dae" | "abc" | "3ds" | "usd"
267 | "usda" | "usdc" => ObjectType::Model3D,
268 "usdz" => ObjectType::Zip,
269
270 "blend" | "blend1" => ObjectType::Blender,
272 "ma" | "mb" => ObjectType::Maya,
273 "max" => ObjectType::ThreeDsMax,
274 "c4d" => ObjectType::Cinema4D,
275 "hip" | "hipnc" | "hiplc" => ObjectType::Houdini,
276
277 "ptx" | "ptf" => ObjectType::ProTools,
279 "als" => ObjectType::AbletonLive,
280 "flp" => ObjectType::FLStudio,
281 "logic" | "logicx" => ObjectType::LogicPro,
282
283 "dwg" | "dxf" => ObjectType::AutoCad,
285 "skp" => ObjectType::SketchUp,
286 "rvt" | "rfa" | "rte" => ObjectType::Revit,
287
288 "unity" | "prefab" | "asset" | "unity3d" => ObjectType::UnityProject,
290 "uasset" | "umap" | "upk" => ObjectType::UnrealProject,
291 "tscn" | "tres" | "godot" => ObjectType::GodotProject,
292
293 "docx" | "doc" | "docm" | "dot" | "dotx" => ObjectType::WordDocument,
295 "xlsx" | "xls" | "xlsm" | "xlsb" | "xlt" | "xltx" => ObjectType::ExcelSpreadsheet,
296 "pptx" | "ppt" | "pptm" | "pot" | "potx" => ObjectType::PowerpointPresentation,
297 "odt" | "ods" | "odp" | "odg" | "odf" => ObjectType::OpenDocument,
298
299 "sqlite" | "sqlite3" | "db" | "db3" | "s3db" => ObjectType::SqliteDatabase,
301
302 _ => ObjectType::Unknown,
305 }
306 }
307
308 pub fn from_path<P: AsRef<Path>>(path: P) -> Self {
310 path.as_ref()
311 .extension()
312 .and_then(|ext| ext.to_str())
313 .map(Self::from_extension)
314 .unwrap_or(ObjectType::Unknown)
315 }
316
317 pub fn from_magic_bytes(data: &[u8]) -> Self {
319 if data.len() < 4 {
320 return ObjectType::Unknown;
321 }
322
323 if data.len() >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF {
325 return ObjectType::Jpeg;
326 }
327
328 if data.starts_with(&[0x89, 0x50, 0x4E, 0x47]) {
330 return ObjectType::Png;
331 }
332
333 if data.starts_with(b"GIF8") {
335 return ObjectType::Gif;
336 }
337
338 if data.len() >= 12 && data.starts_with(b"RIFF") && &data[8..12] == b"WEBP" {
340 return ObjectType::Webp;
341 }
342
343 if data.starts_with(&[0x49, 0x49, 0x2A, 0x00])
345 || data.starts_with(&[0x4D, 0x4D, 0x00, 0x2A])
346 {
347 return ObjectType::Tiff;
348 }
349
350 if data.starts_with(&[0x42, 0x4D]) {
352 return ObjectType::Bmp;
353 }
354
355 if data.starts_with(b"%PDF") {
357 return ObjectType::Pdf;
358 }
359
360 if data.len() >= 12 && &data[4..8] == b"ftyp" {
362 return ObjectType::Mp4;
363 }
364
365 if data.starts_with(&[0x50, 0x4B, 0x03, 0x04])
367 || data.starts_with(&[0x50, 0x4B, 0x05, 0x06])
368 {
369 return ObjectType::Zip;
370 }
371
372 if data.starts_with(&[0x1F, 0x8B]) {
374 return ObjectType::Gz;
375 }
376
377 ObjectType::Unknown
378 }
379
380 pub fn is_already_compressed(self) -> bool {
382 matches!(
383 self,
384 ObjectType::Jpeg
385 | ObjectType::Png
386 | ObjectType::Gif
387 | ObjectType::Webp
388 | ObjectType::Avif
389 | ObjectType::Heic
390 | ObjectType::GpuTexture
391 | ObjectType::Mp4
392 | ObjectType::Mov
393 | ObjectType::Avi
394 | ObjectType::Mkv
395 | ObjectType::Webm
396 | ObjectType::Flv
397 | ObjectType::Wmv
398 | ObjectType::Mpg
399 | ObjectType::Mp3
400 | ObjectType::Aac
401 | ObjectType::Ogg
402 | ObjectType::Opus
403 | ObjectType::Pdf
404 | ObjectType::Zip
405 | ObjectType::Gz
406 | ObjectType::SevenZ
407 | ObjectType::Rar
408 | ObjectType::Parquet
409 | ObjectType::AdobeIllustrator
411 | ObjectType::AdobeIndesign
412 | ObjectType::WordDocument
414 | ObjectType::ExcelSpreadsheet
415 | ObjectType::PowerpointPresentation
416 | ObjectType::OpenDocument
417 )
418 }
419
420 pub fn category(self) -> ObjectCategory {
422 match self {
423 ObjectType::Jpeg
424 | ObjectType::Png
425 | ObjectType::Gif
426 | ObjectType::Webp
427 | ObjectType::Avif
428 | ObjectType::Heic
429 | ObjectType::GpuTexture
430 | ObjectType::Tiff
431 | ObjectType::Bmp
432 | ObjectType::Raw
433 | ObjectType::Exr
434 | ObjectType::Hdr => ObjectCategory::Image,
435
436 ObjectType::Mp4
437 | ObjectType::Mov
438 | ObjectType::Avi
439 | ObjectType::Mkv
440 | ObjectType::Webm
441 | ObjectType::Flv
442 | ObjectType::Wmv
443 | ObjectType::Mpg => ObjectCategory::Video,
444
445 ObjectType::Mp3
446 | ObjectType::Aac
447 | ObjectType::Ogg
448 | ObjectType::Opus
449 | ObjectType::Flac
450 | ObjectType::Wav
451 | ObjectType::Aiff
452 | ObjectType::Alac => ObjectCategory::Audio,
453
454 ObjectType::Pdf | ObjectType::Svg | ObjectType::Eps => ObjectCategory::Document,
455
456 ObjectType::Text
457 | ObjectType::Json
458 | ObjectType::Xml
459 | ObjectType::Yaml
460 | ObjectType::Toml
461 | ObjectType::Csv => ObjectCategory::Text,
462
463 ObjectType::Zip
464 | ObjectType::Tar
465 | ObjectType::Gz
466 | ObjectType::SevenZ
467 | ObjectType::Rar
468 | ObjectType::CompressedLog => ObjectCategory::Archive,
469
470 ObjectType::Parquet
471 | ObjectType::MlData
472 | ObjectType::MlModel
473 | ObjectType::MlDeployment => ObjectCategory::Archive, ObjectType::MlCheckpoint | ObjectType::MlInference => ObjectCategory::MlSpecialized,
477
478 ObjectType::Model3D => ObjectCategory::CreativeProject,
480
481 ObjectType::AdobePhotoshop
483 | ObjectType::AdobeIllustrator
484 | ObjectType::AdobeIndesign
485 | ObjectType::AdobeAfterEffects
486 | ObjectType::AdobePremiere
487 | ObjectType::DavinciResolve
488 | ObjectType::FinalCutPro
489 | ObjectType::AvidMediaComposer
490 | ObjectType::Blender
491 | ObjectType::Maya
492 | ObjectType::ThreeDsMax
493 | ObjectType::Cinema4D
494 | ObjectType::Houdini
495 | ObjectType::ProTools
496 | ObjectType::AbletonLive
497 | ObjectType::FLStudio
498 | ObjectType::LogicPro
499 | ObjectType::AutoCad
500 | ObjectType::SketchUp
501 | ObjectType::Revit
502 | ObjectType::UnityProject
503 | ObjectType::UnrealProject
504 | ObjectType::GodotProject => ObjectCategory::CreativeProject,
505
506 ObjectType::WordDocument
508 | ObjectType::ExcelSpreadsheet
509 | ObjectType::PowerpointPresentation
510 | ObjectType::OpenDocument => ObjectCategory::Office,
511
512 ObjectType::SqliteDatabase => ObjectCategory::Database,
514
515 ObjectType::GitBlob | ObjectType::GitTree | ObjectType::GitCommit => {
516 ObjectCategory::GitObject
517 }
518
519 ObjectType::Unknown => ObjectCategory::Unknown,
520 }
521 }
522}
523
524#[allow(missing_docs)]
526#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
527pub enum ObjectCategory {
528 Image,
529 Video,
530 Audio,
531 Document,
532 Text,
533 Archive,
534 CreativeProject, Office, MlSpecialized, Database, GitObject,
539 Unknown,
540}
541
542const LARGE_TEXT_THRESHOLD: usize = 500 * 1024 * 1024; #[derive(Debug, Clone, Copy, PartialEq, Eq)]
549pub enum CompressionStrategy {
550 Store,
552
553 Zlib(CompressionLevel),
555
556 Zstd(CompressionLevel),
558
559 Brotli(CompressionLevel),
561
562 Delta,
564}
565
566impl CompressionStrategy {
567 pub fn for_object_type(obj_type: ObjectType) -> Self {
569 match obj_type {
570 ObjectType::Jpeg
572 | ObjectType::Png
573 | ObjectType::Gif
574 | ObjectType::Webp
575 | ObjectType::Avif
576 | ObjectType::Heic
577 | ObjectType::GpuTexture => CompressionStrategy::Store,
578
579 ObjectType::Tiff
581 | ObjectType::Bmp
582 | ObjectType::Raw
583 | ObjectType::Exr
584 | ObjectType::Hdr => CompressionStrategy::Zstd(CompressionLevel::Best),
585
586 ObjectType::Mp4
588 | ObjectType::Mov
589 | ObjectType::Avi
590 | ObjectType::Mkv
591 | ObjectType::Webm
592 | ObjectType::Flv
593 | ObjectType::Wmv
594 | ObjectType::Mpg => CompressionStrategy::Store,
595
596 ObjectType::Mp3 | ObjectType::Aac | ObjectType::Ogg | ObjectType::Opus => {
598 CompressionStrategy::Store
599 }
600
601 ObjectType::Flac | ObjectType::Wav | ObjectType::Aiff | ObjectType::Alac => {
603 CompressionStrategy::Zstd(CompressionLevel::Best)
604 }
605
606 ObjectType::Pdf | ObjectType::Svg | ObjectType::Eps => {
608 CompressionStrategy::Zstd(CompressionLevel::Default)
609 }
610
611 ObjectType::Text
615 | ObjectType::Json
616 | ObjectType::Xml
617 | ObjectType::Yaml
618 | ObjectType::Toml
619 | ObjectType::Csv => CompressionStrategy::Brotli(CompressionLevel::Default),
620
621 ObjectType::Zip
623 | ObjectType::Gz
624 | ObjectType::SevenZ
625 | ObjectType::Rar
626 | ObjectType::Parquet
627 | ObjectType::CompressedLog => CompressionStrategy::Store,
628
629 ObjectType::Tar => CompressionStrategy::Zstd(CompressionLevel::Default),
631
632 ObjectType::MlData | ObjectType::MlModel => {
634 CompressionStrategy::Zstd(CompressionLevel::Fast)
635 }
636
637 ObjectType::MlCheckpoint => CompressionStrategy::Zstd(CompressionLevel::Fast),
639
640 ObjectType::MlInference | ObjectType::MlDeployment => {
642 CompressionStrategy::Zstd(CompressionLevel::Default)
643 }
644
645 ObjectType::Model3D => CompressionStrategy::Zstd(CompressionLevel::Best),
649
650 ObjectType::AdobeIllustrator | ObjectType::AdobeIndesign => CompressionStrategy::Store,
654
655 ObjectType::AdobePhotoshop
658 | ObjectType::AdobeAfterEffects
659 | ObjectType::AdobePremiere
660 | ObjectType::DavinciResolve
661 | ObjectType::FinalCutPro
662 | ObjectType::AvidMediaComposer
663 | ObjectType::Blender
664 | ObjectType::Maya
665 | ObjectType::ThreeDsMax
666 | ObjectType::Cinema4D
667 | ObjectType::Houdini
668 | ObjectType::ProTools
669 | ObjectType::AbletonLive
670 | ObjectType::FLStudio
671 | ObjectType::LogicPro
672 | ObjectType::AutoCad
673 | ObjectType::SketchUp
674 | ObjectType::Revit
675 | ObjectType::UnityProject
676 | ObjectType::UnrealProject
677 | ObjectType::GodotProject => CompressionStrategy::Zstd(CompressionLevel::Default),
678
679 ObjectType::WordDocument
681 | ObjectType::ExcelSpreadsheet
682 | ObjectType::PowerpointPresentation
683 | ObjectType::OpenDocument => CompressionStrategy::Store,
684
685 ObjectType::SqliteDatabase => CompressionStrategy::Zstd(CompressionLevel::Default),
687
688 ObjectType::GitBlob | ObjectType::GitTree | ObjectType::GitCommit => {
690 CompressionStrategy::Zlib(CompressionLevel::Default)
691 }
692
693 ObjectType::Unknown => CompressionStrategy::Zstd(CompressionLevel::Default),
695 }
696 }
697
698 pub fn for_object_type_with_size(obj_type: ObjectType, data_size: usize) -> Self {
703 let base_strategy = Self::for_object_type(obj_type);
705
706 if data_size >= LARGE_TEXT_THRESHOLD {
708 if let CompressionStrategy::Brotli(_) = base_strategy {
709 return CompressionStrategy::Zstd(CompressionLevel::Default);
711 }
712 }
713
714 base_strategy
715 }
716}
717
718pub trait TypeAwareCompressor: Send + Sync {
720 fn compress_typed(&self, data: &[u8], obj_type: ObjectType) -> CompressionResult<Vec<u8>>;
722
723 fn compress_typed_with_size(
726 &self,
727 data: &[u8],
728 obj_type: ObjectType,
729 ) -> CompressionResult<Vec<u8>>;
730
731 fn decompress_typed(&self, data: &[u8]) -> CompressionResult<Vec<u8>>;
733
734 fn strategy_for_type(&self, obj_type: ObjectType) -> CompressionStrategy;
736
737 fn strategy_for_type_with_size(
739 &self,
740 obj_type: ObjectType,
741 data_size: usize,
742 ) -> CompressionStrategy;
743}
744
745#[derive(Clone)]
747pub struct SmartCompressor {
748 zlib: ZlibCompressor,
749 zstd_fast: ZstdCompressor,
750 zstd_default: ZstdCompressor,
751 zstd_best: ZstdCompressor,
752 brotli_best: BrotliCompressor,
753}
754
755impl SmartCompressor {
756 pub fn new() -> Self {
758 Self {
759 zlib: ZlibCompressor::new(CompressionLevel::Default),
760 zstd_fast: ZstdCompressor::new(CompressionLevel::Fast),
761 zstd_default: ZstdCompressor::new(CompressionLevel::Default),
762 zstd_best: ZstdCompressor::new(CompressionLevel::Best),
763 brotli_best: BrotliCompressor::new(CompressionLevel::Best),
764 }
765 }
766
767 fn compress_with_strategy(
772 &self,
773 data: &[u8],
774 strategy: CompressionStrategy,
775 ) -> CompressionResult<Vec<u8>> {
776 if matches!(strategy, CompressionStrategy::Store) {
778 let mut result = Vec::with_capacity(data.len() + 1);
779 result.push(0x00); result.extend_from_slice(data);
781 return Ok(result);
782 }
783
784 let compressed = match strategy {
785 CompressionStrategy::Store => unreachable!(), CompressionStrategy::Zlib(level) => {
788 let compressor = ZlibCompressor::new(level);
789 compressor.compress(data)?
790 }
791
792 CompressionStrategy::Zstd(level) => {
793 let compressor = match level {
794 CompressionLevel::Fast => &self.zstd_fast,
795 CompressionLevel::Default => &self.zstd_default,
796 CompressionLevel::Best => &self.zstd_best,
797 };
798 compressor.compress(data)?
799 }
800
801 CompressionStrategy::Brotli(level) => {
802 let compressor = BrotliCompressor::new(level);
803 compressor.compress(data)?
804 }
805
806 CompressionStrategy::Delta => {
807 self.zstd_default.compress(data)?
810 }
811 };
812
813 if compressed.len() >= data.len() {
817 tracing::debug!(
818 original_size = data.len(),
819 compressed_size = compressed.len(),
820 "Compression expanded data, falling back to Store mode"
821 );
822 let mut result = Vec::with_capacity(data.len() + 1);
823 result.push(0x00); result.extend_from_slice(data);
825 return Ok(result);
826 }
827
828 Ok(compressed)
829 }
830}
831
832impl Default for SmartCompressor {
833 fn default() -> Self {
834 Self::new()
835 }
836}
837
838impl fmt::Debug for SmartCompressor {
839 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
840 f.debug_struct("SmartCompressor")
841 .field("strategies", &"Zlib|Zstd|Brotli|Delta")
842 .finish()
843 }
844}
845
846impl TypeAwareCompressor for SmartCompressor {
847 fn compress_typed(&self, data: &[u8], obj_type: ObjectType) -> CompressionResult<Vec<u8>> {
848 let strategy = self.strategy_for_type(obj_type);
849 self.compress_with_strategy(data, strategy)
850 }
851
852 fn compress_typed_with_size(
853 &self,
854 data: &[u8],
855 obj_type: ObjectType,
856 ) -> CompressionResult<Vec<u8>> {
857 let strategy = self.strategy_for_type_with_size(obj_type, data.len());
858 self.compress_with_strategy(data, strategy)
859 }
860
861 fn decompress_typed(&self, data: &[u8]) -> CompressionResult<Vec<u8>> {
862 use crate::CompressionAlgorithm;
864
865 if !data.is_empty() && data[0] == 0x00 {
868 let remaining = &data[1..];
870 let algo = CompressionAlgorithm::detect(remaining);
871 if algo == CompressionAlgorithm::None {
872 return Ok(remaining.to_vec());
874 }
875 }
876
877 let algo = CompressionAlgorithm::detect(data);
878
879 match algo {
880 CompressionAlgorithm::None => Ok(data.to_vec()),
881 CompressionAlgorithm::Zlib => {
882 Ok(self.zlib.decompress(data).unwrap_or_else(|_| data.to_vec()))
885 }
886 CompressionAlgorithm::Zstd => {
887 Ok(self
890 .zstd_default
891 .decompress(data)
892 .unwrap_or_else(|_| data.to_vec()))
893 }
894 CompressionAlgorithm::Brotli => {
895 Ok(self
898 .brotli_best
899 .decompress(data)
900 .unwrap_or_else(|_| data.to_vec()))
901 }
902 }
903 }
904
905 fn strategy_for_type(&self, obj_type: ObjectType) -> CompressionStrategy {
906 CompressionStrategy::for_object_type(obj_type)
907 }
908
909 fn strategy_for_type_with_size(
910 &self,
911 obj_type: ObjectType,
912 data_size: usize,
913 ) -> CompressionStrategy {
914 CompressionStrategy::for_object_type_with_size(obj_type, data_size)
915 }
916}
917
918impl Compressor for SmartCompressor {
919 fn compress(&self, data: &[u8]) -> CompressionResult<Vec<u8>> {
920 self.zstd_default.compress(data)
922 }
923
924 fn decompress(&self, data: &[u8]) -> CompressionResult<Vec<u8>> {
925 self.decompress_typed(data)
926 }
927}
928
929#[cfg(test)]
930#[allow(clippy::unwrap_used)]
931mod tests {
932 use super::*;
933
934 #[test]
935 fn test_object_type_from_extension() {
936 assert_eq!(ObjectType::from_extension("jpg"), ObjectType::Jpeg);
937 assert_eq!(ObjectType::from_extension("JPEG"), ObjectType::Jpeg);
938 assert_eq!(ObjectType::from_extension("png"), ObjectType::Png);
939 assert_eq!(ObjectType::from_extension("tiff"), ObjectType::Tiff);
940 assert_eq!(ObjectType::from_extension("mp4"), ObjectType::Mp4);
941 assert_eq!(ObjectType::from_extension("pdf"), ObjectType::Pdf);
942 assert_eq!(ObjectType::from_extension("txt"), ObjectType::Text);
943 assert_eq!(ObjectType::from_extension("rs"), ObjectType::Text);
944 assert_eq!(ObjectType::from_extension("json"), ObjectType::Json);
945 assert_eq!(ObjectType::from_extension("unknown"), ObjectType::Unknown);
946 }
947
948 #[test]
949 fn test_object_type_from_path() {
950 assert_eq!(ObjectType::from_path("image.jpg"), ObjectType::Jpeg);
951 assert_eq!(ObjectType::from_path("/path/to/file.png"), ObjectType::Png);
952 assert_eq!(ObjectType::from_path("document.PDF"), ObjectType::Pdf);
953 assert_eq!(ObjectType::from_path("code.rs"), ObjectType::Text);
954 assert_eq!(ObjectType::from_path("noextension"), ObjectType::Unknown);
955 }
956
957 #[test]
958 fn test_object_type_from_magic_bytes() {
959 let jpeg_data = [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10];
961 assert_eq!(ObjectType::from_magic_bytes(&jpeg_data), ObjectType::Jpeg);
962
963 let png_data = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
965 assert_eq!(ObjectType::from_magic_bytes(&png_data), ObjectType::Png);
966
967 let gif_data = b"GIF89a";
969 assert_eq!(ObjectType::from_magic_bytes(gif_data), ObjectType::Gif);
970
971 let pdf_data = b"%PDF-1.4";
973 assert_eq!(ObjectType::from_magic_bytes(pdf_data), ObjectType::Pdf);
974
975 let unknown_data = b"random";
977 assert_eq!(
978 ObjectType::from_magic_bytes(unknown_data),
979 ObjectType::Unknown
980 );
981 }
982
983 #[test]
984 fn test_is_already_compressed() {
985 assert!(ObjectType::Jpeg.is_already_compressed());
986 assert!(ObjectType::Png.is_already_compressed());
987 assert!(ObjectType::Mp4.is_already_compressed());
988 assert!(ObjectType::Zip.is_already_compressed());
989 assert!(ObjectType::AdobeIllustrator.is_already_compressed());
991 assert!(ObjectType::AdobeIndesign.is_already_compressed());
992 assert!(ObjectType::WordDocument.is_already_compressed());
994 assert!(ObjectType::ExcelSpreadsheet.is_already_compressed());
995
996 assert!(!ObjectType::Tiff.is_already_compressed());
997 assert!(!ObjectType::Bmp.is_already_compressed());
998 assert!(!ObjectType::Text.is_already_compressed());
999 assert!(!ObjectType::Raw.is_already_compressed());
1000 assert!(!ObjectType::AdobePhotoshop.is_already_compressed());
1002 }
1003
1004 #[test]
1005 fn test_compression_strategy_selection() {
1006 assert_eq!(
1008 CompressionStrategy::for_object_type(ObjectType::Jpeg),
1009 CompressionStrategy::Store
1010 );
1011 assert_eq!(
1012 CompressionStrategy::for_object_type(ObjectType::Mp4),
1013 CompressionStrategy::Store
1014 );
1015
1016 assert_eq!(
1018 CompressionStrategy::for_object_type(ObjectType::Tiff),
1019 CompressionStrategy::Zstd(CompressionLevel::Best)
1020 );
1021 assert_eq!(
1022 CompressionStrategy::for_object_type(ObjectType::Raw),
1023 CompressionStrategy::Zstd(CompressionLevel::Best)
1024 );
1025
1026 assert_eq!(
1028 CompressionStrategy::for_object_type(ObjectType::Text),
1029 CompressionStrategy::Brotli(CompressionLevel::Default)
1030 );
1031 assert_eq!(
1032 CompressionStrategy::for_object_type(ObjectType::Json),
1033 CompressionStrategy::Brotli(CompressionLevel::Default)
1034 );
1035
1036 assert_eq!(
1038 CompressionStrategy::for_object_type(ObjectType::Pdf),
1039 CompressionStrategy::Zstd(CompressionLevel::Default)
1040 );
1041
1042 assert_eq!(
1044 CompressionStrategy::for_object_type(ObjectType::AdobeIllustrator),
1045 CompressionStrategy::Store
1046 );
1047 assert_eq!(
1048 CompressionStrategy::for_object_type(ObjectType::AdobeIndesign),
1049 CompressionStrategy::Store
1050 );
1051
1052 assert_eq!(
1054 CompressionStrategy::for_object_type(ObjectType::WordDocument),
1055 CompressionStrategy::Store
1056 );
1057
1058 assert_eq!(
1060 CompressionStrategy::for_object_type(ObjectType::AdobePhotoshop),
1061 CompressionStrategy::Zstd(CompressionLevel::Default)
1062 );
1063 }
1064
1065 #[test]
1066 fn test_smart_compressor_jpeg_no_compression() {
1067 let compressor = SmartCompressor::new();
1068 let jpeg_data = vec![0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46];
1069
1070 let compressed = compressor
1071 .compress_typed(&jpeg_data, ObjectType::Jpeg)
1072 .unwrap();
1073
1074 assert_eq!(compressed.len(), jpeg_data.len() + 1);
1076 assert_eq!(compressed[0], 0x00);
1077 assert_eq!(&compressed[1..], &jpeg_data[..]);
1078 }
1079
1080 #[test]
1081 fn test_smart_compressor_text_compression() {
1082 let compressor = SmartCompressor::new();
1083 let text_data = b"Hello, World! ".repeat(100);
1084
1085 let compressed = compressor
1086 .compress_typed(&text_data, ObjectType::Text)
1087 .unwrap();
1088
1089 assert!(compressed.len() < text_data.len());
1091
1092 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1093 assert_eq!(decompressed, text_data);
1094 }
1095
1096 #[test]
1097 fn test_smart_compressor_unknown_type() {
1098 let compressor = SmartCompressor::new();
1099 let data = b"Some binary data...".repeat(50);
1100
1101 let compressed = compressor
1102 .compress_typed(&data, ObjectType::Unknown)
1103 .unwrap();
1104
1105 assert!(compressed.len() < data.len());
1107
1108 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1109 assert_eq!(decompressed, data);
1110 }
1111
1112 #[test]
1113 fn test_smart_compressor_fallback() {
1114 let compressor = SmartCompressor::new();
1115 let data = b"Test data";
1116
1117 let compressed = compressor.compress(data).unwrap();
1119 let decompressed = compressor.decompress(&compressed).unwrap();
1120
1121 assert_eq!(decompressed, data);
1122 }
1123
1124 #[test]
1125 fn test_compression_strategy_for_all_types() {
1126 let all_types = [
1128 ObjectType::Jpeg,
1130 ObjectType::Png,
1131 ObjectType::Gif,
1132 ObjectType::Webp,
1133 ObjectType::Avif,
1134 ObjectType::Heic,
1135 ObjectType::Tiff,
1136 ObjectType::Bmp,
1137 ObjectType::Raw,
1138 ObjectType::Exr,
1139 ObjectType::Hdr,
1140 ObjectType::Mp4,
1142 ObjectType::Mov,
1143 ObjectType::Avi,
1144 ObjectType::Mkv,
1145 ObjectType::Webm,
1146 ObjectType::Flv,
1147 ObjectType::Wmv,
1148 ObjectType::Mpg,
1149 ObjectType::Mp3,
1151 ObjectType::Aac,
1152 ObjectType::Ogg,
1153 ObjectType::Opus,
1154 ObjectType::Flac,
1155 ObjectType::Wav,
1156 ObjectType::Aiff,
1157 ObjectType::Alac,
1158 ObjectType::Pdf,
1160 ObjectType::Svg,
1161 ObjectType::Eps,
1162 ObjectType::Text,
1164 ObjectType::Json,
1165 ObjectType::Xml,
1166 ObjectType::Yaml,
1167 ObjectType::Toml,
1168 ObjectType::Csv,
1169 ObjectType::Zip,
1171 ObjectType::Tar,
1172 ObjectType::Gz,
1173 ObjectType::SevenZ,
1174 ObjectType::Rar,
1175 ObjectType::AdobePhotoshop,
1177 ObjectType::Blender,
1178 ObjectType::WordDocument,
1180 ObjectType::MlCheckpoint,
1182 ObjectType::MlInference,
1183 ObjectType::GitBlob,
1185 ObjectType::GitTree,
1186 ObjectType::GitCommit,
1187 ObjectType::Unknown,
1189 ];
1190
1191 for obj_type in &all_types {
1192 let strategy = CompressionStrategy::for_object_type(*obj_type);
1193 assert!(matches!(
1195 strategy,
1196 CompressionStrategy::Store
1197 | CompressionStrategy::Zlib(_)
1198 | CompressionStrategy::Zstd(_)
1199 | CompressionStrategy::Brotli(_)
1200 | CompressionStrategy::Delta
1201 ));
1202 }
1203 }
1204
1205 #[test]
1206 fn test_object_category() {
1207 assert_eq!(ObjectType::Jpeg.category(), ObjectCategory::Image);
1208 assert_eq!(ObjectType::Tiff.category(), ObjectCategory::Image);
1209 assert_eq!(ObjectType::Mp4.category(), ObjectCategory::Video);
1210 assert_eq!(ObjectType::Mp3.category(), ObjectCategory::Audio);
1211 assert_eq!(ObjectType::Wav.category(), ObjectCategory::Audio);
1212 assert_eq!(ObjectType::Pdf.category(), ObjectCategory::Document);
1213 assert_eq!(ObjectType::Text.category(), ObjectCategory::Text);
1214 assert_eq!(ObjectType::Json.category(), ObjectCategory::Text);
1215 assert_eq!(ObjectType::Zip.category(), ObjectCategory::Archive);
1216 assert_eq!(ObjectType::GitBlob.category(), ObjectCategory::GitObject);
1217 assert_eq!(ObjectType::Unknown.category(), ObjectCategory::Unknown);
1218 }
1219
1220 #[test]
1221 fn test_new_file_extensions() {
1222 assert_eq!(ObjectType::from_extension("avif"), ObjectType::Avif);
1223 assert_eq!(ObjectType::from_extension("heic"), ObjectType::Heic);
1224 assert_eq!(ObjectType::from_extension("exr"), ObjectType::Exr);
1225 assert_eq!(ObjectType::from_extension("hdr"), ObjectType::Hdr);
1226 assert_eq!(ObjectType::from_extension("flv"), ObjectType::Flv);
1227 assert_eq!(ObjectType::from_extension("wmv"), ObjectType::Wmv);
1228 assert_eq!(ObjectType::from_extension("opus"), ObjectType::Opus);
1229 assert_eq!(ObjectType::from_extension("aiff"), ObjectType::Aiff);
1230 assert_eq!(ObjectType::from_extension("toml"), ObjectType::Toml);
1231 assert_eq!(ObjectType::from_extension("csv"), ObjectType::Csv);
1232 assert_eq!(ObjectType::from_extension("7z"), ObjectType::SevenZ);
1233 }
1234
1235 #[test]
1236 fn test_smart_compressor_multiple_types() {
1237 let compressor = SmartCompressor::new();
1238
1239 let content = b"Test content ".repeat(100);
1241
1242 let jpeg_result = compressor
1243 .compress_typed(&content, ObjectType::Jpeg)
1244 .unwrap();
1245 let text_result = compressor
1246 .compress_typed(&content, ObjectType::Text)
1247 .unwrap();
1248 let tiff_result = compressor
1249 .compress_typed(&content, ObjectType::Tiff)
1250 .unwrap();
1251
1252 assert_eq!(jpeg_result.len(), content.len() + 1);
1254
1255 assert!(text_result.len() < content.len());
1257 assert!(tiff_result.len() < content.len());
1258
1259 assert_eq!(compressor.decompress_typed(&jpeg_result).unwrap(), content);
1261 assert_eq!(compressor.decompress_typed(&text_result).unwrap(), content);
1262 assert_eq!(compressor.decompress_typed(&tiff_result).unwrap(), content);
1263 }
1264
1265 #[test]
1266 fn test_smart_compressor_empty_data() {
1267 let compressor = SmartCompressor::new();
1268 let empty: &[u8] = b"";
1269
1270 let compressed = compressor.compress_typed(empty, ObjectType::Text).unwrap();
1271 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1272
1273 assert_eq!(decompressed, empty);
1274 }
1275
1276 #[test]
1277 fn test_debug_format() {
1278 let compressor = SmartCompressor::new();
1279 let debug_str = format!("{:?}", compressor);
1280 assert!(debug_str.contains("SmartCompressor"));
1281 }
1282
1283 #[test]
1288 fn test_creative_project_file_extensions() {
1289 assert_eq!(
1291 ObjectType::from_extension("psd"),
1292 ObjectType::AdobePhotoshop
1293 );
1294 assert_eq!(
1295 ObjectType::from_extension("psb"),
1296 ObjectType::AdobePhotoshop
1297 );
1298 assert_eq!(
1299 ObjectType::from_extension("ai"),
1300 ObjectType::AdobeIllustrator
1301 );
1302 assert_eq!(
1303 ObjectType::from_extension("indd"),
1304 ObjectType::AdobeIndesign
1305 );
1306 assert_eq!(
1307 ObjectType::from_extension("aep"),
1308 ObjectType::AdobeAfterEffects
1309 );
1310 assert_eq!(
1311 ObjectType::from_extension("prproj"),
1312 ObjectType::AdobePremiere
1313 );
1314
1315 assert_eq!(
1317 ObjectType::from_extension("drp"),
1318 ObjectType::DavinciResolve
1319 );
1320 assert_eq!(
1321 ObjectType::from_extension("fcpbundle"),
1322 ObjectType::FinalCutPro
1323 );
1324 assert_eq!(
1325 ObjectType::from_extension("avb"),
1326 ObjectType::AvidMediaComposer
1327 );
1328
1329 assert_eq!(ObjectType::from_extension("blend"), ObjectType::Blender);
1331 assert_eq!(ObjectType::from_extension("ma"), ObjectType::Maya);
1332 assert_eq!(ObjectType::from_extension("max"), ObjectType::ThreeDsMax);
1333 assert_eq!(ObjectType::from_extension("c4d"), ObjectType::Cinema4D);
1334 assert_eq!(ObjectType::from_extension("hip"), ObjectType::Houdini);
1335
1336 assert_eq!(ObjectType::from_extension("ptx"), ObjectType::ProTools);
1338 assert_eq!(ObjectType::from_extension("als"), ObjectType::AbletonLive);
1339 assert_eq!(ObjectType::from_extension("flp"), ObjectType::FLStudio);
1340 assert_eq!(ObjectType::from_extension("logic"), ObjectType::LogicPro);
1341
1342 assert_eq!(ObjectType::from_extension("dwg"), ObjectType::AutoCad);
1344 assert_eq!(ObjectType::from_extension("skp"), ObjectType::SketchUp);
1345 assert_eq!(ObjectType::from_extension("rvt"), ObjectType::Revit);
1346
1347 assert_eq!(
1349 ObjectType::from_extension("unity"),
1350 ObjectType::UnityProject
1351 );
1352 assert_eq!(
1353 ObjectType::from_extension("uasset"),
1354 ObjectType::UnrealProject
1355 );
1356 assert_eq!(ObjectType::from_extension("tscn"), ObjectType::GodotProject);
1357 }
1358
1359 #[test]
1360 fn test_office_document_extensions() {
1361 assert_eq!(ObjectType::from_extension("docx"), ObjectType::WordDocument);
1362 assert_eq!(ObjectType::from_extension("doc"), ObjectType::WordDocument);
1363 assert_eq!(
1364 ObjectType::from_extension("xlsx"),
1365 ObjectType::ExcelSpreadsheet
1366 );
1367 assert_eq!(
1368 ObjectType::from_extension("xls"),
1369 ObjectType::ExcelSpreadsheet
1370 );
1371 assert_eq!(
1372 ObjectType::from_extension("pptx"),
1373 ObjectType::PowerpointPresentation
1374 );
1375 assert_eq!(
1376 ObjectType::from_extension("ppt"),
1377 ObjectType::PowerpointPresentation
1378 );
1379 assert_eq!(ObjectType::from_extension("odt"), ObjectType::OpenDocument);
1380 assert_eq!(ObjectType::from_extension("ods"), ObjectType::OpenDocument);
1381 }
1382
1383 #[test]
1384 fn test_ml_specialized_extensions() {
1385 assert_eq!(ObjectType::from_extension("ckpt"), ObjectType::MlCheckpoint);
1387 assert_eq!(ObjectType::from_extension("pt"), ObjectType::MlCheckpoint);
1388 assert_eq!(ObjectType::from_extension("pth"), ObjectType::MlCheckpoint);
1389
1390 assert_eq!(ObjectType::from_extension("onnx"), ObjectType::MlInference);
1392 assert_eq!(ObjectType::from_extension("gguf"), ObjectType::MlInference);
1393 assert_eq!(
1394 ObjectType::from_extension("tflite"),
1395 ObjectType::MlInference
1396 );
1397 assert_eq!(
1398 ObjectType::from_extension("llamafile"),
1399 ObjectType::MlInference
1400 );
1401 }
1402
1403 #[test]
1404 fn test_database_extensions() {
1405 assert_eq!(
1406 ObjectType::from_extension("sqlite"),
1407 ObjectType::SqliteDatabase
1408 );
1409 assert_eq!(ObjectType::from_extension("db"), ObjectType::SqliteDatabase);
1410 assert_eq!(
1411 ObjectType::from_extension("db3"),
1412 ObjectType::SqliteDatabase
1413 );
1414 }
1415
1416 #[test]
1417 fn test_creative_project_categories() {
1418 assert_eq!(
1419 ObjectType::AdobePhotoshop.category(),
1420 ObjectCategory::CreativeProject
1421 );
1422 assert_eq!(
1423 ObjectType::Blender.category(),
1424 ObjectCategory::CreativeProject
1425 );
1426 assert_eq!(
1427 ObjectType::DavinciResolve.category(),
1428 ObjectCategory::CreativeProject
1429 );
1430 assert_eq!(
1431 ObjectType::ProTools.category(),
1432 ObjectCategory::CreativeProject
1433 );
1434 assert_eq!(
1435 ObjectType::AutoCad.category(),
1436 ObjectCategory::CreativeProject
1437 );
1438 assert_eq!(
1439 ObjectType::UnityProject.category(),
1440 ObjectCategory::CreativeProject
1441 );
1442 }
1443
1444 #[test]
1445 fn test_office_category() {
1446 assert_eq!(ObjectType::WordDocument.category(), ObjectCategory::Office);
1447 assert_eq!(
1448 ObjectType::ExcelSpreadsheet.category(),
1449 ObjectCategory::Office
1450 );
1451 assert_eq!(
1452 ObjectType::PowerpointPresentation.category(),
1453 ObjectCategory::Office
1454 );
1455 assert_eq!(ObjectType::OpenDocument.category(), ObjectCategory::Office);
1456 }
1457
1458 #[test]
1459 fn test_ml_specialized_category() {
1460 assert_eq!(
1461 ObjectType::MlCheckpoint.category(),
1462 ObjectCategory::MlSpecialized
1463 );
1464 assert_eq!(
1465 ObjectType::MlInference.category(),
1466 ObjectCategory::MlSpecialized
1467 );
1468 }
1469
1470 #[test]
1471 fn test_database_category() {
1472 assert_eq!(
1473 ObjectType::SqliteDatabase.category(),
1474 ObjectCategory::Database
1475 );
1476 }
1477
1478 #[test]
1479 fn test_creative_project_compression_strategy() {
1480 assert_eq!(
1482 CompressionStrategy::for_object_type(ObjectType::AdobePhotoshop),
1483 CompressionStrategy::Zstd(CompressionLevel::Default)
1484 );
1485 assert_eq!(
1486 CompressionStrategy::for_object_type(ObjectType::Blender),
1487 CompressionStrategy::Zstd(CompressionLevel::Default)
1488 );
1489 assert_eq!(
1490 CompressionStrategy::for_object_type(ObjectType::DavinciResolve),
1491 CompressionStrategy::Zstd(CompressionLevel::Default)
1492 );
1493 }
1494
1495 #[test]
1496 fn test_ml_specialized_compression_strategy() {
1497 assert_eq!(
1499 CompressionStrategy::for_object_type(ObjectType::MlCheckpoint),
1500 CompressionStrategy::Zstd(CompressionLevel::Fast)
1501 );
1502 assert_eq!(
1504 CompressionStrategy::for_object_type(ObjectType::MlInference),
1505 CompressionStrategy::Zstd(CompressionLevel::Default)
1506 );
1507 }
1508
1509 #[test]
1510 fn test_office_compression_strategy() {
1511 assert_eq!(
1512 CompressionStrategy::for_object_type(ObjectType::WordDocument),
1513 CompressionStrategy::Store
1514 );
1515 assert_eq!(
1516 CompressionStrategy::for_object_type(ObjectType::ExcelSpreadsheet),
1517 CompressionStrategy::Store
1518 );
1519 }
1520
1521 #[test]
1522 fn test_text_uses_brotli() {
1523 assert_eq!(
1525 CompressionStrategy::for_object_type(ObjectType::Text),
1526 CompressionStrategy::Brotli(CompressionLevel::Default)
1527 );
1528 assert_eq!(
1529 CompressionStrategy::for_object_type(ObjectType::Json),
1530 CompressionStrategy::Brotli(CompressionLevel::Default)
1531 );
1532 assert_eq!(
1533 CompressionStrategy::for_object_type(ObjectType::Csv),
1534 CompressionStrategy::Brotli(CompressionLevel::Default)
1535 );
1536 assert_eq!(
1537 CompressionStrategy::for_object_type(ObjectType::Xml),
1538 CompressionStrategy::Brotli(CompressionLevel::Default)
1539 );
1540 }
1541
1542 #[test]
1543 fn test_case_insensitive_extensions() {
1544 assert_eq!(
1546 ObjectType::from_extension("PSD"),
1547 ObjectType::AdobePhotoshop
1548 );
1549 assert_eq!(ObjectType::from_extension("BLEND"), ObjectType::Blender);
1550 assert_eq!(ObjectType::from_extension("ONNX"), ObjectType::MlInference);
1551 assert_eq!(ObjectType::from_extension("DOCX"), ObjectType::WordDocument);
1552 }
1553
1554 #[test]
1555 fn test_psd_no_longer_in_image_uncompressed() {
1556 assert_eq!(
1559 CompressionStrategy::for_object_type(ObjectType::AdobePhotoshop),
1560 CompressionStrategy::Zstd(CompressionLevel::Default)
1561 );
1562
1563 assert_eq!(
1565 CompressionStrategy::for_object_type(ObjectType::Tiff),
1566 CompressionStrategy::Zstd(CompressionLevel::Best)
1567 );
1568 }
1569
1570 #[test]
1575 fn test_integration_brotli_text_roundtrip() {
1576 let compressor = SmartCompressor::new();
1578
1579 let json_data =
1581 r#"{"name": "MediaGit", "version": "1.0", "features": ["compression", "delta"]}"#
1582 .repeat(50);
1583 let compressed = compressor
1584 .compress_typed(json_data.as_bytes(), ObjectType::Json)
1585 .unwrap();
1586 assert!(compressed.len() < json_data.len(), "JSON should compress");
1587 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1588 assert_eq!(
1589 json_data.as_bytes(),
1590 &decompressed[..],
1591 "JSON roundtrip failed"
1592 );
1593
1594 let csv_data = "id,name,value\n1,Alice,100\n2,Bob,200\n".repeat(100);
1596 let compressed = compressor
1597 .compress_typed(csv_data.as_bytes(), ObjectType::Csv)
1598 .unwrap();
1599 assert!(compressed.len() < csv_data.len(), "CSV should compress");
1600 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1601 assert_eq!(
1602 csv_data.as_bytes(),
1603 &decompressed[..],
1604 "CSV roundtrip failed"
1605 );
1606
1607 let xml_data = "<root><item id=\"1\">Value</item></root>".repeat(50);
1609 let compressed = compressor
1610 .compress_typed(xml_data.as_bytes(), ObjectType::Xml)
1611 .unwrap();
1612 assert!(compressed.len() < xml_data.len(), "XML should compress");
1613 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1614 assert_eq!(
1615 xml_data.as_bytes(),
1616 &decompressed[..],
1617 "XML roundtrip failed"
1618 );
1619
1620 let text_data = "The quick brown fox jumps over the lazy dog. ".repeat(100);
1622 let compressed = compressor
1623 .compress_typed(text_data.as_bytes(), ObjectType::Text)
1624 .unwrap();
1625 assert!(compressed.len() < text_data.len(), "Text should compress");
1626 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1627 assert_eq!(
1628 text_data.as_bytes(),
1629 &decompressed[..],
1630 "Text roundtrip failed"
1631 );
1632 }
1633
1634 #[test]
1635 fn test_integration_creative_project_roundtrip() {
1636 let compressor = SmartCompressor::new();
1638
1639 let psd_data = vec![0x38, 0x42, 0x50, 0x53]; let mut data = psd_data.clone();
1642 data.extend_from_slice(&vec![0xAB; 10000]); let compressed = compressor
1645 .compress_typed(&data, ObjectType::AdobePhotoshop)
1646 .unwrap();
1647 assert!(compressed.len() < data.len(), "PSD should compress");
1648
1649 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1650 assert_eq!(data, decompressed, "PSD roundtrip failed");
1651 }
1652
1653 #[test]
1654 fn test_integration_ml_specialized_roundtrip() {
1655 let compressor = SmartCompressor::new();
1657
1658 let model_data = (0..5000).map(|x| (x % 256) as u8).collect::<Vec<_>>();
1660
1661 let checkpoint_compressed = compressor
1663 .compress_typed(&model_data, ObjectType::MlCheckpoint)
1664 .unwrap();
1665 let checkpoint_decompressed = compressor.decompress_typed(&checkpoint_compressed).unwrap();
1666 assert_eq!(
1667 model_data, checkpoint_decompressed,
1668 "Checkpoint roundtrip failed"
1669 );
1670
1671 let inference_compressed = compressor
1673 .compress_typed(&model_data, ObjectType::MlInference)
1674 .unwrap();
1675 let inference_decompressed = compressor.decompress_typed(&inference_compressed).unwrap();
1676 assert_eq!(
1677 model_data, inference_decompressed,
1678 "Inference model roundtrip failed"
1679 );
1680
1681 }
1684
1685 #[test]
1686 fn test_integration_office_document_roundtrip() {
1687 let compressor = SmartCompressor::new();
1689
1690 let docx_data = b"PK\x03\x04...document content...".repeat(100);
1692
1693 let compressed = compressor
1694 .compress_typed(&docx_data, ObjectType::WordDocument)
1695 .unwrap();
1696 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1697 assert_eq!(docx_data, &decompressed[..], "DOCX roundtrip failed");
1698 }
1699
1700 #[test]
1701 fn test_integration_database_roundtrip() {
1702 let compressor = SmartCompressor::new();
1704
1705 let db_data = b"SQLite format 3\x00...table data...".repeat(100);
1707
1708 let compressed = compressor
1709 .compress_typed(&db_data, ObjectType::SqliteDatabase)
1710 .unwrap();
1711 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1712 assert_eq!(db_data, &decompressed[..], "SQLite roundtrip failed");
1713 }
1714
1715 #[test]
1716 fn test_integration_auto_detection_mixed_types() {
1717 let compressor = SmartCompressor::new();
1719
1720 let test_data = b"Test data for compression ".repeat(50);
1721
1722 let types = vec![
1724 ObjectType::Text, ObjectType::Json, ObjectType::AdobePhotoshop, ObjectType::MlCheckpoint, ObjectType::WordDocument, ObjectType::Tiff, ];
1731
1732 for obj_type in types {
1733 let compressed = compressor.compress_typed(&test_data, obj_type).unwrap();
1734 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1735 assert_eq!(
1736 test_data,
1737 &decompressed[..],
1738 "Auto-detection failed for {:?}",
1739 obj_type
1740 );
1741 }
1742 }
1743
1744 #[test]
1745 fn test_integration_already_compressed_types() {
1746 let compressor = SmartCompressor::new();
1748
1749 let jpeg_data = vec![0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46];
1751 let mp4_data = b"....ftypisom....";
1752 let zip_data = vec![0x50, 0x4B, 0x03, 0x04];
1753
1754 let jpeg_compressed = compressor
1756 .compress_typed(&jpeg_data, ObjectType::Jpeg)
1757 .unwrap();
1758 assert_eq!(jpeg_compressed[0], 0x00, "JPEG should have Store prefix");
1759 assert_eq!(
1760 &jpeg_compressed[1..],
1761 &jpeg_data[..],
1762 "JPEG should not be recompressed"
1763 );
1764
1765 let mp4_compressed = compressor
1766 .compress_typed(mp4_data, ObjectType::Mp4)
1767 .unwrap();
1768 assert_eq!(mp4_compressed[0], 0x00, "MP4 should have Store prefix");
1769 assert_eq!(
1770 &mp4_compressed[1..],
1771 mp4_data,
1772 "MP4 should not be recompressed"
1773 );
1774
1775 let zip_compressed = compressor
1776 .compress_typed(&zip_data, ObjectType::Zip)
1777 .unwrap();
1778 assert_eq!(zip_compressed[0], 0x00, "ZIP should have Store prefix");
1779 assert_eq!(
1780 &zip_compressed[1..],
1781 &zip_data[..],
1782 "ZIP should not be recompressed"
1783 );
1784 }
1785
1786 #[test]
1787 fn test_integration_compression_ratio_expectations() {
1788 let compressor = SmartCompressor::new();
1790
1791 let repetitive_text = "AAAAAAAAAA".repeat(1000);
1793 let compressed = compressor
1794 .compress_typed(repetitive_text.as_bytes(), ObjectType::Text)
1795 .unwrap();
1796 let ratio = compressed.len() as f64 / repetitive_text.len() as f64;
1797 assert!(
1798 ratio < 0.1,
1799 "Repetitive text should compress to <10% with Brotli, got {:.2}%",
1800 ratio * 100.0
1801 );
1802
1803 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1805 assert_eq!(repetitive_text.as_bytes(), &decompressed[..]);
1806 }
1807
1808 #[test]
1809 fn test_integration_empty_data_all_types() {
1810 let compressor = SmartCompressor::new();
1812 let empty: &[u8] = b"";
1813
1814 let types = vec![
1815 ObjectType::Text, ObjectType::Json, ObjectType::AdobePhotoshop, ObjectType::MlCheckpoint, ObjectType::Tiff, ObjectType::Jpeg, ];
1822
1823 for obj_type in types {
1824 let compressed = compressor.compress_typed(empty, obj_type).unwrap();
1825 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1826 assert_eq!(
1827 empty,
1828 &decompressed[..],
1829 "Empty data failed for {:?}",
1830 obj_type
1831 );
1832 }
1833 }
1834
1835 #[test]
1836 fn test_integration_large_data_performance() {
1837 let compressor = SmartCompressor::new();
1839
1840 let large_json = format!(
1842 r#"{{"data": [{}]}}"#,
1843 (0..10000)
1844 .map(|i| format!("{}", i))
1845 .collect::<Vec<_>>()
1846 .join(",")
1847 );
1848
1849 let compressed = compressor
1850 .compress_typed(large_json.as_bytes(), ObjectType::Json)
1851 .unwrap();
1852 assert!(
1853 compressed.len() < large_json.len(),
1854 "Large JSON should compress"
1855 );
1856
1857 let decompressed = compressor.decompress_typed(&compressed).unwrap();
1858 assert_eq!(
1859 large_json.as_bytes(),
1860 &decompressed[..],
1861 "Large JSON roundtrip failed"
1862 );
1863
1864 let ratio = compressed.len() as f64 / large_json.len() as f64;
1866 assert!(
1867 ratio < 0.5,
1868 "Large JSON should compress to <50%, got {:.2}%",
1869 ratio * 100.0
1870 );
1871 }
1872
1873 #[test]
1874 fn test_integration_all_new_extensions_mapped() {
1875 let new_extensions = vec![
1877 ("psd", ObjectType::AdobePhotoshop),
1879 ("ai", ObjectType::AdobeIllustrator),
1880 ("indd", ObjectType::AdobeIndesign),
1881 ("aep", ObjectType::AdobeAfterEffects),
1882 ("prproj", ObjectType::AdobePremiere),
1883 ("drp", ObjectType::DavinciResolve),
1884 ("blend", ObjectType::Blender),
1885 ("ma", ObjectType::Maya),
1886 ("als", ObjectType::AbletonLive),
1887 ("dwg", ObjectType::AutoCad),
1888 ("unity", ObjectType::UnityProject),
1889 ("docx", ObjectType::WordDocument),
1891 ("xlsx", ObjectType::ExcelSpreadsheet),
1892 ("pptx", ObjectType::PowerpointPresentation),
1893 ("odt", ObjectType::OpenDocument),
1894 ("ckpt", ObjectType::MlCheckpoint),
1896 ("onnx", ObjectType::MlInference),
1897 ("sqlite", ObjectType::SqliteDatabase),
1899 ];
1900
1901 for (ext, expected_type) in new_extensions {
1902 let detected_type = ObjectType::from_extension(ext);
1903 assert_eq!(
1904 detected_type, expected_type,
1905 "Extension '{}' should map to {:?}, got {:?}",
1906 ext, expected_type, detected_type
1907 );
1908
1909 let strategy = CompressionStrategy::for_object_type(detected_type);
1911 assert!(
1912 matches!(
1913 strategy,
1914 CompressionStrategy::Store
1915 | CompressionStrategy::Zlib(_)
1916 | CompressionStrategy::Zstd(_)
1917 | CompressionStrategy::Brotli(_)
1918 | CompressionStrategy::Delta
1919 ),
1920 "Type {:?} has invalid strategy: {:?}",
1921 detected_type,
1922 strategy
1923 );
1924 }
1925 }
1926
1927 #[test]
1928 fn test_integration_category_coverage() {
1929 let category_samples = vec![
1931 (ObjectType::AdobePhotoshop, ObjectCategory::CreativeProject),
1932 (ObjectType::WordDocument, ObjectCategory::Office),
1933 (ObjectType::MlCheckpoint, ObjectCategory::MlSpecialized),
1934 (ObjectType::SqliteDatabase, ObjectCategory::Database),
1935 ];
1936
1937 for (obj_type, expected_category) in category_samples {
1938 let category = obj_type.category();
1939 assert_eq!(
1940 category, expected_category,
1941 "{:?} should be in {:?} category",
1942 obj_type, expected_category
1943 );
1944 }
1945 }
1946}