1use std::fs::File;
42use std::io::{Read, Write};
43use std::path::Path;
44
45use oxiarc_archive::{bzip2, gzip, lz4, zstd};
47
48pub mod advanced;
50pub mod ndarray;
51
52use crate::error::{IoError, Result};
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum CompressionAlgorithm {
57 Gzip,
59 Zstd,
61 Lz4,
63 Bzip2,
65 Brotli,
67 Snappy,
69 FpZip,
71 DeltaLz4,
73}
74
75impl CompressionAlgorithm {
76 pub fn extension(&self) -> &'static str {
78 match self {
79 CompressionAlgorithm::Gzip => "gz",
80 CompressionAlgorithm::Zstd => "zst",
81 CompressionAlgorithm::Lz4 => "lz4",
82 CompressionAlgorithm::Bzip2 => "bz2",
83 CompressionAlgorithm::Brotli => "br",
84 CompressionAlgorithm::Snappy => "snappy",
85 CompressionAlgorithm::FpZip => "fpz",
86 CompressionAlgorithm::DeltaLz4 => "dlz4",
87 }
88 }
89
90 pub fn from_extension(ext: &str) -> Option<Self> {
92 match ext.to_lowercase().as_str() {
93 "gz" | "gzip" => Some(CompressionAlgorithm::Gzip),
94 "zst" | "zstd" => Some(CompressionAlgorithm::Zstd),
95 "lz4" => Some(CompressionAlgorithm::Lz4),
96 "bz2" | "bzip2" => Some(CompressionAlgorithm::Bzip2),
97 "br" | "brotli" => Some(CompressionAlgorithm::Brotli),
98 "snappy" | "snp" => Some(CompressionAlgorithm::Snappy),
99 "fpz" | "fpzip" => Some(CompressionAlgorithm::FpZip),
100 "dlz4" | "delta-lz4" => Some(CompressionAlgorithm::DeltaLz4),
101 _ => None,
102 }
103 }
104}
105
106#[allow(dead_code)]
108fn normalize_level(level: Option<u32>, algorithm: CompressionAlgorithm) -> Result<u32> {
109 let _level = level.unwrap_or(6); if _level > 9 {
112 return Err(IoError::CompressionError(format!(
113 "Compression level must be between 0 and 9, got {_level}"
114 )));
115 }
116
117 match algorithm {
119 CompressionAlgorithm::Gzip => Ok(_level),
120 CompressionAlgorithm::Zstd => {
121 Ok(1 + (_level * 21) / 9)
123 }
124 CompressionAlgorithm::Lz4 => Ok(_level),
125 CompressionAlgorithm::Bzip2 => Ok(_level),
126 CompressionAlgorithm::Brotli => {
127 Ok((_level * 11) / 9)
129 }
130 CompressionAlgorithm::Snappy => Ok(0), CompressionAlgorithm::FpZip => Ok(_level), CompressionAlgorithm::DeltaLz4 => Ok(_level), }
134}
135
136#[allow(dead_code)]
148pub fn compress_data(
149 mut data: &[u8],
150 algorithm: CompressionAlgorithm,
151 level: Option<u32>,
152) -> Result<Vec<u8>> {
153 let normalized_level = normalize_level(level, algorithm)?;
154
155 match algorithm {
156 CompressionAlgorithm::Gzip => {
157 gzip::compress(data, normalized_level as u8)
159 .map_err(|e| IoError::CompressionError(e.to_string()))
160 }
161 CompressionAlgorithm::Zstd => {
162 let writer = zstd::ZstdWriter::new();
164 writer
165 .compress(data)
166 .map_err(|e| IoError::CompressionError(e.to_string()))
167 }
168 CompressionAlgorithm::Lz4 => {
169 use std::io::Write;
171 let mut writer = lz4::Lz4Writer::new(Vec::new());
172 writer
173 .write_compressed(data)
174 .map_err(|e| IoError::CompressionError(e.to_string()))?;
175 Ok(writer.into_inner())
176 }
177 CompressionAlgorithm::Bzip2 => {
178 let writer = bzip2::Bzip2Writer::with_level(normalized_level as u8);
180 writer
181 .compress(data)
182 .map_err(|e| IoError::CompressionError(e.to_string()))
183 }
184 CompressionAlgorithm::Brotli => oxiarc_brotli::compress(data, normalized_level)
185 .map_err(|e| IoError::CompressionError(format!("Brotli compression failed: {e}"))),
186 CompressionAlgorithm::Snappy => Ok(oxiarc_snappy::compress(data)),
187 CompressionAlgorithm::FpZip => {
188 compress_fpzip(data, normalized_level)
190 }
191 CompressionAlgorithm::DeltaLz4 => {
192 compress_delta_lz4(data, normalized_level)
194 }
195 }
196}
197
198#[allow(dead_code)]
209pub fn decompress_data(data: &[u8], algorithm: CompressionAlgorithm) -> Result<Vec<u8>> {
210 match algorithm {
211 CompressionAlgorithm::Gzip => {
212 use std::io::Cursor;
214 let mut cursor = Cursor::new(data);
215 gzip::decompress(&mut cursor).map_err(|e| IoError::DecompressionError(e.to_string()))
216 }
217 CompressionAlgorithm::Zstd => {
218 use std::io::Cursor;
220 let cursor = Cursor::new(data);
221 let mut reader = zstd::ZstdReader::new(cursor)
222 .map_err(|e| IoError::DecompressionError(e.to_string()))?;
223 reader
224 .decompress()
225 .map_err(|e| IoError::DecompressionError(e.to_string()))
226 }
227 CompressionAlgorithm::Lz4 => {
228 use std::io::Cursor;
230 let cursor = Cursor::new(data);
231 let mut reader = lz4::Lz4Reader::new(cursor)
232 .map_err(|e| IoError::DecompressionError(e.to_string()))?;
233 reader
234 .decompress()
235 .map_err(|e| IoError::DecompressionError(e.to_string()))
236 }
237 CompressionAlgorithm::Bzip2 => {
238 bzip2::decompress(data).map_err(|e| IoError::DecompressionError(e.to_string()))
240 }
241 CompressionAlgorithm::Brotli => oxiarc_brotli::decompress(data)
242 .map_err(|e| IoError::DecompressionError(format!("Brotli decompression failed: {e}"))),
243 CompressionAlgorithm::Snappy => oxiarc_snappy::decompress(data)
244 .map_err(|e| IoError::DecompressionError(format!("Snappy decompression failed: {e}"))),
245 CompressionAlgorithm::FpZip => {
246 decompress_fpzip(data)
248 }
249 CompressionAlgorithm::DeltaLz4 => {
250 decompress_delta_lz4(data)
252 }
253 }
254}
255
256#[allow(dead_code)]
269pub fn compress_file<P: AsRef<Path>>(
270 input_path: P,
271 output_path: Option<P>,
272 algorithm: CompressionAlgorithm,
273 level: Option<u32>,
274) -> Result<String> {
275 let mut input_data = Vec::new();
277 File::open(input_path.as_ref())
278 .map_err(|e| IoError::FileError(format!("Failed to open input file: {e}")))?
279 .read_to_end(&mut input_data)
280 .map_err(|e| IoError::FileError(format!("Failed to read input file: {e}")))?;
281
282 let compressed_data = compress_data(&input_data, algorithm, level)?;
284
285 let output_path_string = match output_path {
287 Some(path) => path.as_ref().to_string_lossy().to_string(),
288 None => {
289 let mut path_buf = input_path.as_ref().to_path_buf();
291 let ext = algorithm.extension();
292
293 let file_name = path_buf
295 .file_name()
296 .ok_or_else(|| IoError::FileError("Invalid input file _path".to_string()))?
297 .to_string_lossy()
298 .to_string();
299
300 let new_file_name = format!("{file_name}.{ext}");
302 path_buf.set_file_name(new_file_name);
303
304 path_buf.to_string_lossy().to_string()
305 }
306 };
307
308 File::create(&output_path_string)
310 .map_err(|e| IoError::FileError(format!("Failed to create output file: {e}")))?
311 .write_all(&compressed_data)
312 .map_err(|e| IoError::FileError(format!("Failed to write to output file: {e}")))?;
313
314 Ok(output_path_string)
315}
316
317#[allow(dead_code)]
329pub fn decompress_file<P: AsRef<Path>>(
330 input_path: P,
331 output_path: Option<P>,
332 algorithm: Option<CompressionAlgorithm>,
333) -> Result<String> {
334 let algorithm = match algorithm {
336 Some(algo) => algo,
337 None => {
338 let ext = input_path
340 .as_ref()
341 .extension()
342 .ok_or_else(|| {
343 IoError::DecompressionError("Unable to determine file extension".to_string())
344 })?
345 .to_string_lossy()
346 .to_string();
347
348 CompressionAlgorithm::from_extension(&ext)
349 .ok_or(IoError::UnsupportedCompressionAlgorithm(ext))?
350 }
351 };
352
353 let mut input_data = Vec::new();
355 File::open(input_path.as_ref())
356 .map_err(|e| IoError::FileError(format!("Failed to open input file: {e}")))?
357 .read_to_end(&mut input_data)
358 .map_err(|e| IoError::FileError(format!("Failed to read input file: {e}")))?;
359
360 let decompressed_data = decompress_data(&input_data, algorithm)?;
362
363 let output_path_string = match output_path {
365 Some(path) => path.as_ref().to_string_lossy().to_string(),
366 None => {
367 let path_str = input_path.as_ref().to_string_lossy().to_string();
369 let ext = algorithm.extension();
370
371 if path_str.ends_with(&format!(".{ext}")) {
372 path_str[0..path_str.len() - ext.len() - 1].to_string()
374 } else {
375 format!("{path_str}.decompressed")
377 }
378 }
379 };
380
381 File::create(&output_path_string)
383 .map_err(|e| IoError::FileError(format!("Failed to create output file: {e}")))?
384 .write_all(&decompressed_data)
385 .map_err(|e| IoError::FileError(format!("Failed to write to output file: {e}")))?;
386
387 Ok(output_path_string)
388}
389
390#[allow(dead_code)]
402pub fn compression_ratio(
403 data: &[u8],
404 algorithm: CompressionAlgorithm,
405 level: Option<u32>,
406) -> Result<f64> {
407 let compressed = compress_data(data, algorithm, level)?;
408 let original_size = data.len() as f64;
409 let compressed_size = compressed.len() as f64;
410
411 if compressed_size == 0.0 {
413 return Err(IoError::CompressionError(
414 "Compressed data has zero size".to_string(),
415 ));
416 }
417
418 Ok(original_size / compressed_size)
419}
420
421pub struct CompressionInfo {
423 pub name: String,
425 pub description: String,
427 pub typical_compression_ratio: f64,
429 pub compression_speed: u8,
431 pub decompression_speed: u8,
433 pub file_extension: String,
435}
436
437#[allow(dead_code)]
439pub fn algorithm_info(algorithm: CompressionAlgorithm) -> CompressionInfo {
440 match algorithm {
441 CompressionAlgorithm::Gzip => CompressionInfo {
442 name: "GZIP".to_string(),
443 description: "General-purpose compression _algorithm with good balance of speed and compression ratio".to_string(),
444 typical_compression_ratio: 2.5,
445 compression_speed: 6,
446 decompression_speed: 7,
447 file_extension: "gz".to_string(),
448 },
449 CompressionAlgorithm::Zstd => CompressionInfo {
450 name: "Zstandard".to_string(),
451 description: "Modern compression _algorithm with excellent compression ratio and fast decompression".to_string(),
452 typical_compression_ratio: 3.2,
453 compression_speed: 7,
454 decompression_speed: 9,
455 file_extension: "zst".to_string(),
456 },
457 CompressionAlgorithm::Lz4 => CompressionInfo {
458 name: "LZ4".to_string(),
459 description: "Extremely fast compression _algorithm with moderate compression ratio".to_string(),
460 typical_compression_ratio: 1.8,
461 compression_speed: 10,
462 decompression_speed: 10,
463 file_extension: "lz4".to_string(),
464 },
465 CompressionAlgorithm::Bzip2 => CompressionInfo {
466 name: "BZIP2".to_string(),
467 description: "High compression ratio but slower speed, good for archival storage".to_string(),
468 typical_compression_ratio: 3.5,
469 compression_speed: 3,
470 decompression_speed: 4,
471 file_extension: "bz2".to_string(),
472 },
473 CompressionAlgorithm::Brotli => CompressionInfo {
474 name: "Brotli".to_string(),
475 description: "Web-optimized compression _algorithm with excellent compression ratio".to_string(),
476 typical_compression_ratio: 3.8,
477 compression_speed: 5,
478 decompression_speed: 8,
479 file_extension: "br".to_string(),
480 },
481 CompressionAlgorithm::Snappy => CompressionInfo {
482 name: "Snappy".to_string(),
483 description: "Very fast compression _algorithm developed by Google".to_string(),
484 typical_compression_ratio: 1.5,
485 compression_speed: 10,
486 decompression_speed: 10,
487 file_extension: "snappy".to_string(),
488 },
489 CompressionAlgorithm::FpZip => CompressionInfo {
490 name: "FPZip".to_string(),
491 description: "Floating-point specific compression optimized for scientific data".to_string(),
492 typical_compression_ratio: 4.0,
493 compression_speed: 7,
494 decompression_speed: 8,
495 file_extension: "fpz".to_string(),
496 },
497 CompressionAlgorithm::DeltaLz4 => CompressionInfo {
498 name: "Delta+LZ4".to_string(),
499 description: "Delta encoding followed by LZ4 compression, optimized for time series data".to_string(),
500 typical_compression_ratio: 5.0,
501 compression_speed: 8,
502 decompression_speed: 9,
503 file_extension: "dlz4".to_string(),
504 },
505 }
506}
507
508const GZIP_MAGIC: &[u8] = &[0x1f, 0x8b];
510const ZSTD_MAGIC: &[u8] = &[0x28, 0xb5, 0x2f, 0xfd];
511const LZ4_MAGIC: &[u8] = &[0x04, 0x22, 0x4d, 0x18];
512const BZIP2_MAGIC: &[u8] = &[0x42, 0x5a, 0x68];
513const BROTLI_MAGIC: &[u8] = &[0xce, 0xb2, 0xcf, 0x81]; const SNAPPY_MAGIC: &[u8] = &[0x73, 0x4e, 0x61, 0x50]; const FPZIP_MAGIC: &[u8] = &[0x46, 0x50, 0x5a, 0x49]; const DELTA_LZ4_MAGIC: &[u8] = &[0x44, 0x4c, 0x5a, 0x34]; #[allow(dead_code)]
520pub fn detect_compression_from_bytes(data: &[u8]) -> Option<CompressionAlgorithm> {
521 if data.starts_with(GZIP_MAGIC) {
522 Some(CompressionAlgorithm::Gzip)
523 } else if data.starts_with(ZSTD_MAGIC) {
524 Some(CompressionAlgorithm::Zstd)
525 } else if data.starts_with(LZ4_MAGIC) {
526 Some(CompressionAlgorithm::Lz4)
527 } else if data.starts_with(BZIP2_MAGIC) {
528 Some(CompressionAlgorithm::Bzip2)
529 } else if data.starts_with(BROTLI_MAGIC) {
530 Some(CompressionAlgorithm::Brotli)
531 } else if data.starts_with(SNAPPY_MAGIC) {
532 Some(CompressionAlgorithm::Snappy)
533 } else if data.starts_with(FPZIP_MAGIC) {
534 Some(CompressionAlgorithm::FpZip)
535 } else if data.starts_with(DELTA_LZ4_MAGIC) {
536 Some(CompressionAlgorithm::DeltaLz4)
537 } else {
538 None
539 }
540}
541
542pub struct TransparentFileHandler {
544 pub auto_detect_extension: bool,
546 pub auto_detect_content: bool,
548 pub default_algorithm: CompressionAlgorithm,
550 pub default_level: Option<u32>,
552}
553
554impl Default for TransparentFileHandler {
555 fn default() -> Self {
556 Self {
557 auto_detect_extension: true,
558 auto_detect_content: true,
559 default_algorithm: CompressionAlgorithm::Zstd,
560 default_level: Some(6),
561 }
562 }
563}
564
565impl TransparentFileHandler {
566 pub fn new(
568 auto_detect_extension: bool,
569 auto_detect_content: bool,
570 default_algorithm: CompressionAlgorithm,
571 default_level: Option<u32>,
572 ) -> Self {
573 Self {
574 auto_detect_extension,
575 auto_detect_content,
576 default_algorithm,
577 default_level,
578 }
579 }
580
581 pub fn read_file<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>> {
583 let mut file_data = Vec::new();
584 File::open(path.as_ref())
585 .map_err(|e| IoError::FileError(format!("Failed to open file: {e}")))?
586 .read_to_end(&mut file_data)
587 .map_err(|e| IoError::FileError(format!("Failed to read file: {e}")))?;
588
589 let mut algorithm = None;
591
592 if self.auto_detect_extension {
594 if let Some(ext) = path.as_ref().extension() {
595 algorithm = CompressionAlgorithm::from_extension(&ext.to_string_lossy());
596 }
597 }
598
599 if algorithm.is_none() && self.auto_detect_content {
601 algorithm = detect_compression_from_bytes(&file_data);
602 }
603
604 match algorithm {
606 Some(algo) => decompress_data(&file_data, algo),
607 None => Ok(file_data), }
609 }
610
611 pub fn write_file<P: AsRef<Path>>(&self, path: P, data: &[u8]) -> Result<()> {
613 let mut algorithm = None;
614 let level = self.default_level;
615
616 if self.auto_detect_extension {
618 if let Some(ext) = path.as_ref().extension() {
619 algorithm = CompressionAlgorithm::from_extension(&ext.to_string_lossy());
620 }
621 }
622
623 if algorithm.is_none() && self.should_compress_by_default(&path) {
625 algorithm = Some(self.default_algorithm);
626 }
627
628 let output_data = match algorithm {
630 Some(algo) => compress_data(data, algo, level)?,
631 None => data.to_vec(),
632 };
633
634 File::create(path.as_ref())
636 .map_err(|e| IoError::FileError(format!("Failed to create file: {e}")))?
637 .write_all(&output_data)
638 .map_err(|e| IoError::FileError(format!("Failed to write file: {e}")))?;
639
640 Ok(())
641 }
642
643 fn should_compress_by_default<P: AsRef<Path>>(&self, path: P) -> bool {
645 if let Some(ext) = path.as_ref().extension() {
647 let ext_str = ext.to_string_lossy().to_lowercase();
648 matches!(
649 ext_str.as_str(),
650 "gz" | "gzip" | "zst" | "zstd" | "lz4" | "bz2" | "bzip2"
651 )
652 } else {
653 false
654 }
655 }
656
657 pub fn copy_file<P: AsRef<Path>, Q: AsRef<Path>>(
659 &self,
660 source: P,
661 destination: Q,
662 ) -> Result<()> {
663 let data = self.read_file(source)?;
664 self.write_file(destination, &data)?;
665 Ok(())
666 }
667
668 pub fn file_info<P: AsRef<Path>>(&self, path: P) -> Result<FileCompressionInfo> {
670 let mut file_data = Vec::new();
671 File::open(path.as_ref())
672 .map_err(|e| IoError::FileError(format!("Failed to open file: {e}")))?
673 .read_to_end(&mut file_data)
674 .map_err(|e| IoError::FileError(format!("Failed to read file: {e}")))?;
675
676 let original_size = file_data.len();
677
678 let detected_algorithm = if self.auto_detect_content {
680 detect_compression_from_bytes(&file_data)
681 } else {
682 None
683 };
684
685 let extension_algorithm = if self.auto_detect_extension {
686 path.as_ref()
687 .extension()
688 .and_then(|ext| CompressionAlgorithm::from_extension(&ext.to_string_lossy()))
689 } else {
690 None
691 };
692
693 let is_compressed = detected_algorithm.is_some() || extension_algorithm.is_some();
694 let algorithm = detected_algorithm.or(extension_algorithm);
695
696 let uncompressed_size = if let Some(algo) = algorithm {
697 match decompress_data(&file_data, algo) {
698 Ok(decompressed) => Some(decompressed.len()),
699 Err(_) => None,
700 }
701 } else {
702 Some(original_size)
703 };
704
705 Ok(FileCompressionInfo {
706 path: path.as_ref().to_path_buf(),
707 is_compressed,
708 algorithm,
709 compressed_size: if is_compressed {
710 Some(original_size)
711 } else {
712 None
713 },
714 uncompressed_size,
715 compression_ratio: if let (Some(compressed), Some(uncompressed)) = (
716 if is_compressed {
717 Some(original_size)
718 } else {
719 None
720 },
721 uncompressed_size,
722 ) {
723 Some(uncompressed as f64 / compressed as f64)
724 } else {
725 None
726 },
727 })
728 }
729}
730
731#[derive(Debug, Clone)]
733pub struct FileCompressionInfo {
734 pub path: std::path::PathBuf,
736 pub is_compressed: bool,
738 pub algorithm: Option<CompressionAlgorithm>,
740 pub compressed_size: Option<usize>,
742 pub uncompressed_size: Option<usize>,
744 pub compression_ratio: Option<f64>,
746}
747
748static GLOBAL_HANDLER: std::sync::OnceLock<TransparentFileHandler> = std::sync::OnceLock::new();
750
751#[allow(dead_code)]
753pub fn init_global_handler(handler: TransparentFileHandler) {
754 let _ = GLOBAL_HANDLER.set(handler);
755}
756
757#[allow(dead_code)]
759pub fn global_handler() -> &'static TransparentFileHandler {
760 GLOBAL_HANDLER.get_or_init(TransparentFileHandler::default)
761}
762
763#[allow(dead_code)]
765pub fn read_file_transparent<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
766 global_handler().read_file(path)
767}
768
769#[allow(dead_code)]
771pub fn write_file_transparent<P: AsRef<Path>>(path: P, data: &[u8]) -> Result<()> {
772 global_handler().write_file(path, data)
773}
774
775#[allow(dead_code)]
777pub fn copy_file_transparent<P: AsRef<Path>, Q: AsRef<Path>>(
778 source: P,
779 destination: Q,
780) -> Result<()> {
781 global_handler().copy_file(source, destination)
782}
783
784#[allow(dead_code)]
786pub fn file_info_transparent<P: AsRef<Path>>(path: P) -> Result<FileCompressionInfo> {
787 global_handler().file_info(path)
788}
789
790#[allow(dead_code)]
796fn compress_fpzip(data: &[u8], level: u32) -> Result<Vec<u8>> {
797 let mut result = Vec::with_capacity(FPZIP_MAGIC.len() + data.len());
799
800 result.extend_from_slice(FPZIP_MAGIC);
802
803 if data.len().is_multiple_of(8) {
806 let float_data =
808 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f64, data.len() / 8) };
809
810 let mut encoded_data = Vec::with_capacity(data.len());
812 if !float_data.is_empty() {
813 encoded_data.extend_from_slice(&float_data[0].to_le_bytes());
815
816 for i in 1..float_data.len() {
818 let current_bits = float_data[i].to_bits();
819 let prev_bits = float_data[i - 1].to_bits();
820 let xor_result = current_bits ^ prev_bits;
821 encoded_data.extend_from_slice(&xor_result.to_le_bytes());
822 }
823 }
824
825 let compressed = compress_data(&encoded_data, CompressionAlgorithm::Lz4, Some(level))?;
827 result.extend_from_slice(&compressed);
828 } else if data.len().is_multiple_of(4) {
829 let float_data =
831 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, data.len() / 4) };
832
833 let mut encoded_data = Vec::with_capacity(data.len());
834 if !float_data.is_empty() {
835 encoded_data.extend_from_slice(&float_data[0].to_le_bytes());
836
837 for i in 1..float_data.len() {
838 let current_bits = float_data[i].to_bits();
839 let prev_bits = float_data[i - 1].to_bits();
840 let xor_result = current_bits ^ prev_bits;
841 encoded_data.extend_from_slice(&xor_result.to_le_bytes());
842 }
843 }
844
845 let compressed = compress_data(&encoded_data, CompressionAlgorithm::Lz4, Some(level))?;
846 result.extend_from_slice(&compressed);
847 } else {
848 let compressed = compress_data(data, CompressionAlgorithm::Zstd, Some(level))?;
850 result.extend_from_slice(&compressed);
851 }
852
853 Ok(result)
854}
855
856#[allow(dead_code)]
858fn decompress_fpzip(data: &[u8]) -> Result<Vec<u8>> {
859 if !data.starts_with(FPZIP_MAGIC) {
860 return Err(IoError::DecompressionError(
861 "Invalid FPZip magic bytes".to_string(),
862 ));
863 }
864
865 let compressed_data = &data[FPZIP_MAGIC.len()..];
866
867 let decompressed = decompress_data(compressed_data, CompressionAlgorithm::Lz4)?;
869
870 if decompressed.len() % 8 == 0 {
872 let mut float_data = unsafe {
874 std::slice::from_raw_parts(decompressed.as_ptr() as *const u64, decompressed.len() / 8)
875 }
876 .to_vec();
877
878 for i in 1..float_data.len() {
880 float_data[i] ^= float_data[i - 1];
881 }
882
883 let result = unsafe {
885 std::slice::from_raw_parts(float_data.as_ptr() as *const u8, float_data.len() * 8)
886 }
887 .to_vec();
888 Ok(result)
889 } else if decompressed.len() % 4 == 0 {
890 let mut float_data = unsafe {
892 std::slice::from_raw_parts(decompressed.as_ptr() as *const u32, decompressed.len() / 4)
893 }
894 .to_vec();
895
896 for i in 1..float_data.len() {
897 float_data[i] ^= float_data[i - 1];
898 }
899
900 let result = unsafe {
901 std::slice::from_raw_parts(float_data.as_ptr() as *const u8, float_data.len() * 4)
902 }
903 .to_vec();
904 Ok(result)
905 } else {
906 decompress_data(compressed_data, CompressionAlgorithm::Zstd)
908 }
909}
910
911#[allow(dead_code)]
913fn compress_delta_lz4(data: &[u8], level: u32) -> Result<Vec<u8>> {
914 let mut result = Vec::with_capacity(DELTA_LZ4_MAGIC.len() + data.len());
915
916 result.extend_from_slice(DELTA_LZ4_MAGIC);
918
919 if data.len() < 8 {
920 let compressed = compress_data(data, CompressionAlgorithm::Lz4, Some(level))?;
922 result.extend_from_slice(&compressed);
923 return Ok(result);
924 }
925
926 let mut delta_encoded = Vec::with_capacity(data.len());
928
929 if data.len().is_multiple_of(8) {
930 let values =
932 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const i64, data.len() / 8) };
933
934 delta_encoded.extend_from_slice(&values[0].to_le_bytes());
936
937 for i in 1..values.len() {
939 let delta = values[i].wrapping_sub(values[i - 1]);
940 delta_encoded.extend_from_slice(&delta.to_le_bytes());
941 }
942 } else if data.len().is_multiple_of(4) {
943 let values =
945 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const i32, data.len() / 4) };
946
947 delta_encoded.extend_from_slice(&values[0].to_le_bytes());
948
949 for i in 1..values.len() {
950 let delta = values[i].wrapping_sub(values[i - 1]);
951 delta_encoded.extend_from_slice(&delta.to_le_bytes());
952 }
953 } else {
954 delta_encoded.push(data[0]);
956 for i in 1..data.len() {
957 let delta = data[i].wrapping_sub(data[i - 1]);
958 delta_encoded.push(delta);
959 }
960 }
961
962 let compressed = compress_data(&delta_encoded, CompressionAlgorithm::Lz4, Some(level))?;
964 result.extend_from_slice(&compressed);
965
966 Ok(result)
967}
968
969#[allow(dead_code)]
971fn decompress_delta_lz4(data: &[u8]) -> Result<Vec<u8>> {
972 if !data.starts_with(DELTA_LZ4_MAGIC) {
973 return Err(IoError::DecompressionError(
974 "Invalid Delta-LZ4 magic bytes".to_string(),
975 ));
976 }
977
978 let compressed_data = &data[DELTA_LZ4_MAGIC.len()..];
979
980 let delta_data = decompress_data(compressed_data, CompressionAlgorithm::Lz4)?;
982
983 if delta_data.len() < 8 {
984 return Ok(delta_data);
985 }
986
987 if delta_data.len() % 8 == 0 {
989 let mut values = unsafe {
991 std::slice::from_raw_parts(delta_data.as_ptr() as *const i64, delta_data.len() / 8)
992 }
993 .to_vec();
994
995 for i in 1..values.len() {
997 values[i] = values[i - 1].wrapping_add(values[i]);
998 }
999
1000 let result =
1001 unsafe { std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len() * 8) }
1002 .to_vec();
1003 Ok(result)
1004 } else if delta_data.len() % 4 == 0 {
1005 let mut values = unsafe {
1007 std::slice::from_raw_parts(delta_data.as_ptr() as *const i32, delta_data.len() / 4)
1008 }
1009 .to_vec();
1010
1011 for i in 1..values.len() {
1012 values[i] = values[i - 1].wrapping_add(values[i]);
1013 }
1014
1015 let result =
1016 unsafe { std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len() * 4) }
1017 .to_vec();
1018 Ok(result)
1019 } else {
1020 let mut result = delta_data.clone();
1022 for i in 1..result.len() {
1023 result[i] = result[i - 1].wrapping_add(result[i]);
1024 }
1025 Ok(result)
1026 }
1027}
1028
1029use std::sync::atomic::{AtomicUsize, Ordering};
1034use std::sync::Arc;
1035use std::time::Instant;
1036
1037#[allow(unused_imports)]
1039use scirs2_core::parallel_ops::{IntoParallelIterator, ParallelIterator};
1040
1041#[derive(Debug, Clone)]
1043pub struct ParallelCompressionConfig {
1044 pub num_threads: usize,
1046 pub chunk_size: usize,
1048 pub buffer_size: usize,
1050 pub enable_memory_mapping: bool,
1052}
1053
1054impl Default for ParallelCompressionConfig {
1055 fn default() -> Self {
1056 Self {
1057 num_threads: 0, chunk_size: 1024 * 1024, buffer_size: 64 * 1024, enable_memory_mapping: true,
1061 }
1062 }
1063}
1064
1065#[derive(Debug, Clone)]
1067pub struct ParallelCompressionStats {
1068 pub chunks_processed: usize,
1070 pub bytes_processed: usize,
1072 pub bytes_output: usize,
1074 pub operation_time_ms: f64,
1076 pub throughput_bps: f64,
1078 pub compression_ratio: f64,
1080 pub threads_used: usize,
1082}
1083
1084#[allow(dead_code)]
1086pub fn compress_data_parallel(
1087 data: &[u8],
1088 algorithm: CompressionAlgorithm,
1089 level: Option<u32>,
1090 config: ParallelCompressionConfig,
1091) -> Result<(Vec<u8>, ParallelCompressionStats)> {
1092 let start_time = Instant::now();
1093 let input_size = data.len();
1094
1095 let num_threads = if config.num_threads == 0 {
1097 std::thread::available_parallelism()
1098 .map(|p| p.get())
1099 .unwrap_or(1)
1100 } else {
1101 config.num_threads
1102 };
1103
1104 if input_size <= config.chunk_size {
1106 let compressed = compress_data(data, algorithm, level)?;
1107 let operation_time = start_time.elapsed().as_secs_f64() * 1000.0;
1108
1109 let stats = ParallelCompressionStats {
1110 chunks_processed: 1,
1111 bytes_processed: input_size,
1112 bytes_output: compressed.len(),
1113 operation_time_ms: operation_time,
1114 throughput_bps: input_size as f64 / (operation_time / 1000.0),
1115 compression_ratio: input_size as f64 / compressed.len() as f64,
1116 threads_used: 1,
1117 };
1118
1119 return Ok((compressed, stats));
1120 }
1121
1122 let chunk_size = config.chunk_size;
1124 let chunks: Vec<&[u8]> = data.chunks(chunk_size).collect();
1125 let chunk_count = chunks.len();
1126
1127 let processed_count = Arc::new(AtomicUsize::new(0));
1129 let compressed_chunks: Result<Vec<Vec<u8>>> = chunks
1130 .into_par_iter()
1131 .map(|chunk| {
1132 let result = compress_data(chunk, algorithm, level);
1133 processed_count.fetch_add(1, Ordering::Relaxed);
1134 result
1135 })
1136 .collect();
1137
1138 let compressed_chunks = compressed_chunks?;
1139
1140 let total_compressed_size: usize = compressed_chunks.iter().map(|chunk| chunk.len()).sum();
1142 let mut result = Vec::with_capacity(total_compressed_size + (chunk_count * 8)); result.extend_from_slice(&(chunk_count as u64).to_le_bytes());
1146 for chunk in &compressed_chunks {
1147 result.extend_from_slice(&(chunk.len() as u64).to_le_bytes());
1148 }
1149
1150 for chunk in compressed_chunks {
1152 result.extend_from_slice(&chunk);
1153 }
1154
1155 let operation_time = start_time.elapsed().as_secs_f64() * 1000.0;
1156
1157 let stats = ParallelCompressionStats {
1158 chunks_processed: chunk_count,
1159 bytes_processed: input_size,
1160 bytes_output: result.len(),
1161 operation_time_ms: operation_time,
1162 throughput_bps: input_size as f64 / (operation_time / 1000.0),
1163 compression_ratio: input_size as f64 / result.len() as f64,
1164 threads_used: num_threads,
1165 };
1166
1167 Ok((result, stats))
1168}
1169
1170#[allow(dead_code)]
1172pub fn decompress_data_parallel(
1173 data: &[u8],
1174 algorithm: CompressionAlgorithm,
1175 config: ParallelCompressionConfig,
1176) -> Result<(Vec<u8>, ParallelCompressionStats)> {
1177 let start_time = Instant::now();
1178 let input_size = data.len();
1179
1180 let num_threads = if config.num_threads == 0 {
1182 std::thread::available_parallelism()
1183 .map(|p| p.get())
1184 .unwrap_or(1)
1185 } else {
1186 config.num_threads
1187 };
1188
1189 if data.len() < 8 {
1191 let decompressed = decompress_data(data, algorithm)?;
1193 let operation_time = start_time.elapsed().as_secs_f64() * 1000.0;
1194
1195 let stats = ParallelCompressionStats {
1196 chunks_processed: 1,
1197 bytes_processed: input_size,
1198 bytes_output: decompressed.len(),
1199 operation_time_ms: operation_time,
1200 throughput_bps: decompressed.len() as f64 / (operation_time / 1000.0),
1201 compression_ratio: decompressed.len() as f64 / input_size as f64,
1202 threads_used: 1,
1203 };
1204
1205 return Ok((decompressed, stats));
1206 }
1207
1208 let chunk_count = u64::from_le_bytes(
1210 data[0..8]
1211 .try_into()
1212 .map_err(|_| IoError::DecompressionError("Invalid chunk header".to_string()))?,
1213 ) as usize;
1214
1215 if chunk_count == 0 || chunk_count > data.len() / 8 {
1216 let decompressed = decompress_data(data, algorithm)?;
1218 let operation_time = start_time.elapsed().as_secs_f64() * 1000.0;
1219
1220 let stats = ParallelCompressionStats {
1221 chunks_processed: 1,
1222 bytes_processed: input_size,
1223 bytes_output: decompressed.len(),
1224 operation_time_ms: operation_time,
1225 throughput_bps: decompressed.len() as f64 / (operation_time / 1000.0),
1226 compression_ratio: decompressed.len() as f64 / input_size as f64,
1227 threads_used: 1,
1228 };
1229
1230 return Ok((decompressed, stats));
1231 }
1232
1233 let header_size = 8 + (chunk_count * 8);
1235 if data.len() < header_size {
1236 return Err(IoError::DecompressionError(
1237 "Truncated chunk headers".to_string(),
1238 ));
1239 }
1240
1241 let mut chunk_sizes = Vec::with_capacity(chunk_count);
1242 for i in 0..chunk_count {
1243 let start_idx = 8 + (i * 8);
1244 let size = u64::from_le_bytes(
1245 data[start_idx..start_idx + 8]
1246 .try_into()
1247 .map_err(|_| IoError::DecompressionError("Invalid chunk size".to_string()))?,
1248 ) as usize;
1249 chunk_sizes.push(size);
1250 }
1251
1252 let mut chunks = Vec::with_capacity(chunk_count);
1254 let mut offset = header_size;
1255
1256 for &size in &chunk_sizes {
1257 if offset + size > data.len() {
1258 return Err(IoError::DecompressionError(
1259 "Truncated chunk data".to_string(),
1260 ));
1261 }
1262 chunks.push(&data[offset..offset + size]);
1263 offset += size;
1264 }
1265
1266 let processed_count = Arc::new(AtomicUsize::new(0));
1268 let decompressed_chunks: Result<Vec<Vec<u8>>> = chunks
1269 .into_par_iter()
1270 .map(|chunk| {
1271 let result = decompress_data(chunk, algorithm);
1272 processed_count.fetch_add(1, Ordering::Relaxed);
1273 result
1274 })
1275 .collect();
1276
1277 let decompressed_chunks = decompressed_chunks?;
1278
1279 let total_size: usize = decompressed_chunks.iter().map(|chunk| chunk.len()).sum();
1281 let mut result = Vec::with_capacity(total_size);
1282
1283 for chunk in decompressed_chunks {
1284 result.extend_from_slice(&chunk);
1285 }
1286
1287 let operation_time = start_time.elapsed().as_secs_f64() * 1000.0;
1288
1289 let stats = ParallelCompressionStats {
1290 chunks_processed: chunk_count,
1291 bytes_processed: input_size,
1292 bytes_output: result.len(),
1293 operation_time_ms: operation_time,
1294 throughput_bps: result.len() as f64 / (operation_time / 1000.0),
1295 compression_ratio: result.len() as f64 / input_size as f64,
1296 threads_used: num_threads,
1297 };
1298
1299 Ok((result, stats))
1300}
1301
1302#[allow(dead_code)]
1304pub fn compress_file_parallel<P: AsRef<Path>>(
1305 input_path: P,
1306 output_path: Option<P>,
1307 algorithm: CompressionAlgorithm,
1308 level: Option<u32>,
1309 config: ParallelCompressionConfig,
1310) -> Result<(String, ParallelCompressionStats)> {
1311 let mut input_data = Vec::new();
1313 File::open(input_path.as_ref())
1314 .map_err(|e| IoError::FileError(format!("Failed to open input file: {e}")))?
1315 .read_to_end(&mut input_data)
1316 .map_err(|e| IoError::FileError(format!("Failed to read input file: {e}")))?;
1317
1318 let (compressed_data, stats) = compress_data_parallel(&input_data, algorithm, level, config)?;
1320
1321 let output_path_string = match output_path {
1323 Some(path) => path.as_ref().to_string_lossy().to_string(),
1324 None => {
1325 let mut path_buf = input_path.as_ref().to_path_buf();
1326 let ext = algorithm.extension();
1327 let file_name = path_buf
1328 .file_name()
1329 .ok_or_else(|| IoError::FileError("Invalid input file _path".to_string()))?
1330 .to_string_lossy()
1331 .to_string();
1332 let new_file_name = format!("{file_name}.{ext}");
1333 path_buf.set_file_name(new_file_name);
1334 path_buf.to_string_lossy().to_string()
1335 }
1336 };
1337
1338 File::create(&output_path_string)
1340 .map_err(|e| IoError::FileError(format!("Failed to create output file: {e}")))?
1341 .write_all(&compressed_data)
1342 .map_err(|e| IoError::FileError(format!("Failed to write to output file: {e}")))?;
1343
1344 Ok((output_path_string, stats))
1345}
1346
1347#[allow(dead_code)]
1349pub fn decompress_file_parallel<P: AsRef<Path>>(
1350 input_path: P,
1351 output_path: Option<P>,
1352 algorithm: Option<CompressionAlgorithm>,
1353 config: ParallelCompressionConfig,
1354) -> Result<(String, ParallelCompressionStats)> {
1355 let algorithm = match algorithm {
1357 Some(algo) => algo,
1358 None => {
1359 let ext = input_path
1360 .as_ref()
1361 .extension()
1362 .ok_or_else(|| {
1363 IoError::DecompressionError("Unable to determine file extension".to_string())
1364 })?
1365 .to_string_lossy()
1366 .to_string();
1367
1368 CompressionAlgorithm::from_extension(&ext)
1369 .ok_or(IoError::UnsupportedCompressionAlgorithm(ext))?
1370 }
1371 };
1372
1373 let mut input_data = Vec::new();
1375 File::open(input_path.as_ref())
1376 .map_err(|e| IoError::FileError(format!("Failed to open input file: {e}")))?
1377 .read_to_end(&mut input_data)
1378 .map_err(|e| IoError::FileError(format!("Failed to read input file: {e}")))?;
1379
1380 let (decompressed_data, stats) = decompress_data_parallel(&input_data, algorithm, config)?;
1382
1383 let output_path_string = match output_path {
1385 Some(path) => path.as_ref().to_string_lossy().to_string(),
1386 None => {
1387 let path_str = input_path.as_ref().to_string_lossy().to_string();
1388 let ext = algorithm.extension();
1389
1390 if path_str.ends_with(&format!(".{ext}")) {
1391 path_str[0..path_str.len() - ext.len() - 1].to_string()
1392 } else {
1393 format!("{path_str}.decompressed")
1394 }
1395 }
1396 };
1397
1398 File::create(&output_path_string)
1400 .map_err(|e| IoError::FileError(format!("Failed to create output file: {e}")))?
1401 .write_all(&decompressed_data)
1402 .map_err(|e| IoError::FileError(format!("Failed to write to output file: {e}")))?;
1403
1404 Ok((output_path_string, stats))
1405}
1406
1407#[allow(dead_code)]
1409pub fn benchmark_compression_algorithms(
1410 data: &[u8],
1411 algorithms: &[CompressionAlgorithm],
1412 levels: &[u32],
1413 parallel_configs: &[ParallelCompressionConfig],
1414) -> Result<Vec<CompressionBenchmarkResult>> {
1415 let mut results = Vec::new();
1416
1417 for &algorithm in algorithms {
1418 for &level in levels {
1419 let start_time = Instant::now();
1421 let compressed = compress_data(data, algorithm, Some(level))?;
1422 let sequential_time = start_time.elapsed().as_secs_f64() * 1000.0;
1423
1424 let decompressed = decompress_data(&compressed, algorithm)?;
1425 let sequential_decomp_time =
1426 start_time.elapsed().as_secs_f64() * 1000.0 - sequential_time;
1427
1428 assert_eq!(data, &decompressed, "Round-trip failed for {algorithm:?}");
1429
1430 for config in parallel_configs {
1432 let (par_compressed, par_comp_stats) =
1433 compress_data_parallel(data, algorithm, Some(level), config.clone())?;
1434 let (par_decompressed, par_decomp_stats) =
1435 decompress_data_parallel(&par_compressed, algorithm, config.clone())?;
1436
1437 assert_eq!(
1438 data, &par_decompressed,
1439 "Parallel round-trip failed for {algorithm:?}"
1440 );
1441
1442 results.push(CompressionBenchmarkResult {
1443 algorithm,
1444 level,
1445 config: config.clone(),
1446 input_size: data.len(),
1447 compressed_size: compressed.len(),
1448 parallel_compressed_size: par_compressed.len(),
1449 sequential_compression_time_ms: sequential_time,
1450 sequential_decompression_time_ms: sequential_decomp_time,
1451 parallel_compression_stats: par_comp_stats,
1452 parallel_decompression_stats: par_decomp_stats,
1453 compression_ratio: data.len() as f64 / compressed.len() as f64,
1454 parallel_compression_ratio: data.len() as f64 / par_compressed.len() as f64,
1455 });
1456 }
1457 }
1458 }
1459
1460 Ok(results)
1461}
1462
1463#[derive(Debug, Clone)]
1465pub struct CompressionBenchmarkResult {
1466 pub algorithm: CompressionAlgorithm,
1468 pub level: u32,
1470 pub config: ParallelCompressionConfig,
1472 pub input_size: usize,
1474 pub compressed_size: usize,
1476 pub parallel_compressed_size: usize,
1478 pub sequential_compression_time_ms: f64,
1480 pub sequential_decompression_time_ms: f64,
1482 pub parallel_compression_stats: ParallelCompressionStats,
1484 pub parallel_decompression_stats: ParallelCompressionStats,
1486 pub compression_ratio: f64,
1488 pub parallel_compression_ratio: f64,
1490}
1491
1492impl CompressionBenchmarkResult {
1493 pub fn compression_speedup(&self) -> f64 {
1495 self.sequential_compression_time_ms / self.parallel_compression_stats.operation_time_ms
1496 }
1497
1498 pub fn decompression_speedup(&self) -> f64 {
1500 self.sequential_decompression_time_ms / self.parallel_decompression_stats.operation_time_ms
1501 }
1502
1503 pub fn compression_overhead(&self) -> f64 {
1505 self.parallel_compressed_size as f64 / self.compressed_size as f64
1506 }
1507}