rustfs_zip/
lib.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use async_compression::tokio::bufread::{BzDecoder, GzipDecoder, XzDecoder, ZlibDecoder, ZstdDecoder};
16use async_compression::tokio::write::{BzEncoder, GzipEncoder, XzEncoder, ZlibEncoder, ZstdEncoder};
17// use async_zip::tokio::read::seek::ZipFileReader;
18// use async_zip::tokio::write::ZipFileWriter;
19// use async_zip::{Compression, ZipEntryBuilder};
20use std::path::Path;
21use tokio::fs::File;
22use tokio::io::{self, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter};
23use tokio_stream::StreamExt;
24use tokio_tar::Archive;
25
26#[derive(Debug, PartialEq, Clone, Copy)]
27pub enum CompressionFormat {
28    Gzip,  //.gz
29    Bzip2, //.bz2
30    Zip,   //.zip
31    Xz,    //.xz
32    Zlib,  //.z
33    Zstd,  //.zst
34    Tar,   //.tar (uncompressed)
35    Unknown,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub enum CompressionLevel {
40    Fastest,
41    Best,
42    #[default]
43    Default,
44    Level(u32),
45}
46
47impl CompressionFormat {
48    /// Identify compression format from file extension
49    pub fn from_extension(ext: &str) -> Self {
50        match ext.to_lowercase().as_str() {
51            "gz" | "gzip" => CompressionFormat::Gzip,
52            "bz2" | "bzip2" => CompressionFormat::Bzip2,
53            "zip" => CompressionFormat::Zip,
54            "xz" => CompressionFormat::Xz,
55            "zlib" => CompressionFormat::Zlib,
56            "zst" | "zstd" => CompressionFormat::Zstd,
57            "tar" => CompressionFormat::Tar,
58            _ => CompressionFormat::Unknown,
59        }
60    }
61
62    /// Identify compression format from file path
63    pub fn from_path<P: AsRef<Path>>(path: P) -> Self {
64        let path = path.as_ref();
65        if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
66            Self::from_extension(ext)
67        } else {
68            CompressionFormat::Unknown
69        }
70    }
71
72    /// Get file extension corresponding to the format
73    pub fn extension(&self) -> &'static str {
74        match self {
75            CompressionFormat::Gzip => "gz",
76            CompressionFormat::Bzip2 => "bz2",
77            CompressionFormat::Zip => "zip",
78            CompressionFormat::Xz => "xz",
79            CompressionFormat::Zlib => "zlib",
80            CompressionFormat::Zstd => "zst",
81            CompressionFormat::Tar => "tar",
82            CompressionFormat::Unknown => "",
83        }
84    }
85
86    /// Check if format is supported
87    pub fn is_supported(&self) -> bool {
88        !matches!(self, CompressionFormat::Unknown)
89    }
90
91    /// Create decompressor
92    pub fn get_decoder<R>(&self, input: R) -> io::Result<Box<dyn AsyncRead + Send + Unpin>>
93    where
94        R: AsyncRead + Send + Unpin + 'static,
95    {
96        let reader = BufReader::new(input);
97
98        let decoder: Box<dyn AsyncRead + Send + Unpin + 'static> = match self {
99            CompressionFormat::Gzip => Box::new(GzipDecoder::new(reader)),
100            CompressionFormat::Bzip2 => Box::new(BzDecoder::new(reader)),
101            CompressionFormat::Zlib => Box::new(ZlibDecoder::new(reader)),
102            CompressionFormat::Xz => Box::new(XzDecoder::new(reader)),
103            CompressionFormat::Zstd => Box::new(ZstdDecoder::new(reader)),
104            CompressionFormat::Tar => Box::new(reader),
105            CompressionFormat::Zip => {
106                return Err(io::Error::new(
107                    io::ErrorKind::InvalidInput,
108                    "Zip format requires special handling, use extract_zip function instead",
109                ));
110            }
111            CompressionFormat::Unknown => {
112                return Err(io::Error::new(io::ErrorKind::InvalidInput, "Unsupported file format"));
113            }
114        };
115
116        Ok(decoder)
117    }
118
119    /// Create compressor
120    pub fn get_encoder<W>(&self, output: W, level: CompressionLevel) -> io::Result<Box<dyn AsyncWrite + Send + Unpin>>
121    where
122        W: AsyncWrite + Send + Unpin + 'static,
123    {
124        let writer = BufWriter::new(output);
125
126        let encoder: Box<dyn AsyncWrite + Send + Unpin + 'static> = match self {
127            CompressionFormat::Gzip => {
128                let level = match level {
129                    CompressionLevel::Fastest => async_compression::Level::Fastest,
130                    CompressionLevel::Best => async_compression::Level::Best,
131                    CompressionLevel::Default => async_compression::Level::Default,
132                    CompressionLevel::Level(n) => async_compression::Level::Precise(n as i32),
133                };
134                Box::new(GzipEncoder::with_quality(writer, level))
135            }
136            CompressionFormat::Bzip2 => {
137                let level = match level {
138                    CompressionLevel::Fastest => async_compression::Level::Fastest,
139                    CompressionLevel::Best => async_compression::Level::Best,
140                    CompressionLevel::Default => async_compression::Level::Default,
141                    CompressionLevel::Level(n) => async_compression::Level::Precise(n as i32),
142                };
143                Box::new(BzEncoder::with_quality(writer, level))
144            }
145            CompressionFormat::Zlib => {
146                let level = match level {
147                    CompressionLevel::Fastest => async_compression::Level::Fastest,
148                    CompressionLevel::Best => async_compression::Level::Best,
149                    CompressionLevel::Default => async_compression::Level::Default,
150                    CompressionLevel::Level(n) => async_compression::Level::Precise(n as i32),
151                };
152                Box::new(ZlibEncoder::with_quality(writer, level))
153            }
154            CompressionFormat::Xz => {
155                let level = match level {
156                    CompressionLevel::Fastest => async_compression::Level::Fastest,
157                    CompressionLevel::Best => async_compression::Level::Best,
158                    CompressionLevel::Default => async_compression::Level::Default,
159                    CompressionLevel::Level(n) => async_compression::Level::Precise(n as i32),
160                };
161                Box::new(XzEncoder::with_quality(writer, level))
162            }
163            CompressionFormat::Zstd => {
164                let level = match level {
165                    CompressionLevel::Fastest => async_compression::Level::Fastest,
166                    CompressionLevel::Best => async_compression::Level::Best,
167                    CompressionLevel::Default => async_compression::Level::Default,
168                    CompressionLevel::Level(n) => async_compression::Level::Precise(n as i32),
169                };
170                Box::new(ZstdEncoder::with_quality(writer, level))
171            }
172            CompressionFormat::Tar => Box::new(writer),
173            CompressionFormat::Zip => {
174                return Err(io::Error::new(
175                    io::ErrorKind::InvalidInput,
176                    "Zip format requires special handling, use create_zip function instead",
177                ));
178            }
179            CompressionFormat::Unknown => {
180                return Err(io::Error::new(io::ErrorKind::InvalidInput, "Unsupported file format"));
181            }
182        };
183
184        Ok(encoder)
185    }
186}
187
188/// Decompress tar format compressed files
189pub async fn decompress<R, F>(input: R, format: CompressionFormat, mut callback: F) -> io::Result<()>
190where
191    R: AsyncRead + Send + Unpin + 'static,
192    F: AsyncFnMut(tokio_tar::Entry<Archive<Box<dyn AsyncRead + Send + Unpin + 'static>>>) -> std::io::Result<()> + Send + 'static,
193{
194    let decoder = format.get_decoder(input)?;
195    let mut ar = Archive::new(decoder);
196    let mut entries = ar.entries()?;
197
198    while let Some(entry) = entries.next().await {
199        let entry = entry?;
200        callback(entry).await?;
201    }
202
203    Ok(())
204}
205
206/// ZIP file entry information
207#[derive(Debug, Clone)]
208pub struct ZipEntry {
209    pub name: String,
210    pub size: u64,
211    pub compressed_size: u64,
212    pub is_dir: bool,
213    pub compression_method: String,
214}
215
216/// Simplified ZIP file processing (temporarily using standard library zip crate)
217pub async fn extract_zip_simple<P: AsRef<Path>>(zip_path: P, extract_to: P) -> io::Result<Vec<ZipEntry>> {
218    // Use standard library zip processing, return empty list as placeholder for now
219    // Actual implementation needs to be improved in future versions
220    let _zip_path = zip_path.as_ref();
221    let _extract_to = extract_to.as_ref();
222
223    Ok(Vec::new())
224}
225
226/// Simplified ZIP file creation
227pub async fn create_zip_simple<P: AsRef<Path>>(
228    _zip_path: P,
229    _files: Vec<(String, Vec<u8>)>, // (filename, file content)
230    _compression_level: CompressionLevel,
231) -> io::Result<()> {
232    // Return unimplemented error for now
233    Err(io::Error::new(io::ErrorKind::Unsupported, "ZIP creation not yet implemented"))
234}
235
236/// Compression utility struct
237pub struct Compressor {
238    format: CompressionFormat,
239    level: CompressionLevel,
240}
241
242impl Compressor {
243    pub fn new(format: CompressionFormat) -> Self {
244        Self {
245            format,
246            level: CompressionLevel::Default,
247        }
248    }
249
250    pub fn with_level(mut self, level: CompressionLevel) -> Self {
251        self.level = level;
252        self
253    }
254
255    /// Compress data
256    pub async fn compress(&self, input: &[u8]) -> io::Result<Vec<u8>> {
257        let output = Vec::new();
258        let cursor = std::io::Cursor::new(output);
259        let mut encoder = self.format.get_encoder(cursor, self.level)?;
260
261        tokio::io::copy(&mut std::io::Cursor::new(input), &mut encoder).await?;
262        encoder.shutdown().await?;
263
264        // Get compressed data
265        // Note: API needs to be redesigned here as we cannot retrieve data from encoder
266        // Return empty vector as placeholder for now
267        Ok(Vec::new())
268    }
269
270    /// Decompress data
271    pub async fn decompress(&self, input: Vec<u8>) -> io::Result<Vec<u8>> {
272        let mut output = Vec::new();
273        let cursor = std::io::Cursor::new(input);
274        let mut decoder = self.format.get_decoder(cursor)?;
275
276        tokio::io::copy(&mut decoder, &mut output).await?;
277
278        Ok(output)
279    }
280}
281
282/// Decompression utility struct
283pub struct Decompressor {
284    format: CompressionFormat,
285}
286
287impl Decompressor {
288    pub fn new(format: CompressionFormat) -> Self {
289        Self { format }
290    }
291
292    pub fn auto_detect<P: AsRef<Path>>(path: P) -> Self {
293        let format = CompressionFormat::from_path(path);
294        Self { format }
295    }
296
297    /// Decompress file
298    pub async fn decompress_file<P: AsRef<Path>>(&self, input_path: P, output_path: P) -> io::Result<()> {
299        let input_file = File::open(&input_path).await?;
300        let output_file = File::create(&output_path).await?;
301
302        let mut decoder = self.format.get_decoder(input_file)?;
303        let mut writer = BufWriter::new(output_file);
304
305        tokio::io::copy(&mut decoder, &mut writer).await?;
306        writer.shutdown().await?;
307
308        Ok(())
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315    use std::io::Cursor;
316    use tokio::io::AsyncReadExt;
317
318    #[test]
319    fn test_compression_format_from_extension() {
320        // Test supported compression format recognition
321        assert_eq!(CompressionFormat::from_extension("gz"), CompressionFormat::Gzip);
322        assert_eq!(CompressionFormat::from_extension("gzip"), CompressionFormat::Gzip);
323        assert_eq!(CompressionFormat::from_extension("bz2"), CompressionFormat::Bzip2);
324        assert_eq!(CompressionFormat::from_extension("bzip2"), CompressionFormat::Bzip2);
325        assert_eq!(CompressionFormat::from_extension("zip"), CompressionFormat::Zip);
326        assert_eq!(CompressionFormat::from_extension("xz"), CompressionFormat::Xz);
327        assert_eq!(CompressionFormat::from_extension("zlib"), CompressionFormat::Zlib);
328        assert_eq!(CompressionFormat::from_extension("zst"), CompressionFormat::Zstd);
329        assert_eq!(CompressionFormat::from_extension("zstd"), CompressionFormat::Zstd);
330        assert_eq!(CompressionFormat::from_extension("tar"), CompressionFormat::Tar);
331
332        // Test case insensitivity
333        assert_eq!(CompressionFormat::from_extension("GZ"), CompressionFormat::Gzip);
334        assert_eq!(CompressionFormat::from_extension("ZIP"), CompressionFormat::Zip);
335
336        // Test unknown formats
337        assert_eq!(CompressionFormat::from_extension("unknown"), CompressionFormat::Unknown);
338        assert_eq!(CompressionFormat::from_extension("txt"), CompressionFormat::Unknown);
339        assert_eq!(CompressionFormat::from_extension(""), CompressionFormat::Unknown);
340    }
341
342    #[test]
343    fn test_compression_format_case_sensitivity() {
344        // Test case insensitivity (now supports case insensitivity)
345        assert_eq!(CompressionFormat::from_extension("GZ"), CompressionFormat::Gzip);
346        assert_eq!(CompressionFormat::from_extension("Gz"), CompressionFormat::Gzip);
347        assert_eq!(CompressionFormat::from_extension("BZ2"), CompressionFormat::Bzip2);
348        assert_eq!(CompressionFormat::from_extension("ZIP"), CompressionFormat::Zip);
349    }
350
351    #[test]
352    fn test_compression_format_edge_cases() {
353        // Test edge cases
354        assert_eq!(CompressionFormat::from_extension("gz "), CompressionFormat::Unknown);
355        assert_eq!(CompressionFormat::from_extension(" gz"), CompressionFormat::Unknown);
356        assert_eq!(CompressionFormat::from_extension("gz.bak"), CompressionFormat::Unknown);
357        assert_eq!(CompressionFormat::from_extension("tar.gz"), CompressionFormat::Unknown);
358    }
359
360    #[test]
361    fn test_compression_format_debug() {
362        // Test Debug trait implementation
363        let format = CompressionFormat::Gzip;
364        let debug_str = format!("{format:?}");
365        assert_eq!(debug_str, "Gzip");
366
367        let unknown_format = CompressionFormat::Unknown;
368        let unknown_debug_str = format!("{unknown_format:?}");
369        assert_eq!(unknown_debug_str, "Unknown");
370    }
371
372    #[test]
373    fn test_compression_format_equality() {
374        // Test PartialEq trait implementation
375        assert_eq!(CompressionFormat::Gzip, CompressionFormat::Gzip);
376        assert_eq!(CompressionFormat::Unknown, CompressionFormat::Unknown);
377        assert_ne!(CompressionFormat::Gzip, CompressionFormat::Bzip2);
378        assert_ne!(CompressionFormat::Zip, CompressionFormat::Unknown);
379    }
380
381    #[tokio::test]
382    async fn test_get_decoder_supported_formats() {
383        // Test that supported formats can create decoders
384        let test_data = b"test data";
385        let cursor = Cursor::new(test_data);
386
387        let gzip_format = CompressionFormat::Gzip;
388        let decoder_result = gzip_format.get_decoder(cursor);
389        assert!(decoder_result.is_ok(), "Gzip decoder should be created successfully");
390    }
391
392    #[tokio::test]
393    async fn test_get_decoder_unsupported_formats() {
394        // Test that unsupported formats return errors
395        let test_data = b"test data";
396        let cursor = Cursor::new(test_data);
397
398        let unknown_format = CompressionFormat::Unknown;
399        let decoder_result = unknown_format.get_decoder(cursor);
400        assert!(decoder_result.is_err(), "Unknown format should return error");
401
402        if let Err(e) = decoder_result {
403            assert_eq!(e.kind(), io::ErrorKind::InvalidInput);
404            assert_eq!(e.to_string(), "Unsupported file format");
405        }
406    }
407
408    #[tokio::test]
409    async fn test_get_decoder_zip_format() {
410        // Test Zip format (currently not supported)
411        let test_data = b"test data";
412        let cursor = Cursor::new(test_data);
413
414        let zip_format = CompressionFormat::Zip;
415        let decoder_result = zip_format.get_decoder(cursor);
416        assert!(decoder_result.is_err(), "Zip format should return error (not implemented)");
417    }
418
419    #[tokio::test]
420    async fn test_get_decoder_all_supported_formats() {
421        // Test that all supported formats can create decoders successfully
422        let sample_content = b"Hello, compression world!";
423
424        let supported_formats = vec![
425            CompressionFormat::Gzip,
426            CompressionFormat::Bzip2,
427            CompressionFormat::Zlib,
428            CompressionFormat::Xz,
429            CompressionFormat::Zstd,
430            CompressionFormat::Tar,
431        ];
432
433        for format in supported_formats {
434            let cursor = Cursor::new(sample_content);
435            let decoder_result = format.get_decoder(cursor);
436            assert!(decoder_result.is_ok(), "Format {format:?} should create decoder successfully");
437        }
438    }
439
440    #[tokio::test]
441    async fn test_decoder_type_consistency() {
442        // Test decoder return type consistency
443        let sample_content = b"Hello, compression world!";
444        let cursor = Cursor::new(sample_content);
445
446        let gzip_format = CompressionFormat::Gzip;
447        let mut decoder = gzip_format.get_decoder(cursor).unwrap();
448
449        // Verify that the returned decoder implements the correct trait object
450        let mut output_buffer = Vec::new();
451        // This only verifies the type, we don't expect successful reading (since data is not actual gzip format)
452        let _read_result = decoder.read_to_end(&mut output_buffer).await;
453    }
454
455    #[test]
456    fn test_compression_format_exhaustive_matching() {
457        // Test that all enum variants have corresponding handling
458        let all_formats = vec![
459            CompressionFormat::Gzip,
460            CompressionFormat::Bzip2,
461            CompressionFormat::Zip,
462            CompressionFormat::Xz,
463            CompressionFormat::Zlib,
464            CompressionFormat::Zstd,
465            CompressionFormat::Unknown,
466        ];
467
468        for format in all_formats {
469            // Verify each format has corresponding Debug implementation
470            let _debug_str = format!("{format:?}");
471
472            // Verify each format has corresponding PartialEq implementation
473            assert_eq!(format, format);
474        }
475    }
476
477    #[test]
478    fn test_extension_mapping_completeness() {
479        // Test completeness of extension mapping
480        let extension_mappings = vec![
481            ("gz", CompressionFormat::Gzip),
482            ("gzip", CompressionFormat::Gzip),
483            ("bz2", CompressionFormat::Bzip2),
484            ("bzip2", CompressionFormat::Bzip2),
485            ("zip", CompressionFormat::Zip),
486            ("xz", CompressionFormat::Xz),
487            ("zlib", CompressionFormat::Zlib),
488            ("zst", CompressionFormat::Zstd),
489            ("zstd", CompressionFormat::Zstd),
490            ("tar", CompressionFormat::Tar),
491        ];
492
493        for (ext, expected_format) in extension_mappings {
494            assert_eq!(
495                CompressionFormat::from_extension(ext),
496                expected_format,
497                "Extension '{ext}' should map to {expected_format:?}"
498            );
499        }
500    }
501
502    #[test]
503    fn test_format_string_representations() {
504        // Test string representation of formats
505        let format_strings = vec![
506            (CompressionFormat::Gzip, "Gzip"),
507            (CompressionFormat::Bzip2, "Bzip2"),
508            (CompressionFormat::Zip, "Zip"),
509            (CompressionFormat::Xz, "Xz"),
510            (CompressionFormat::Zlib, "Zlib"),
511            (CompressionFormat::Zstd, "Zstd"),
512            (CompressionFormat::Unknown, "Unknown"),
513        ];
514
515        for (format, expected_str) in format_strings {
516            assert_eq!(
517                format!("{format:?}"),
518                expected_str,
519                "Format {:?} should have string representation '{}'",
520                format,
521                expected_str
522            );
523        }
524    }
525
526    #[tokio::test]
527    async fn test_decoder_error_handling() {
528        // Test decoder error handling with edge cases
529        let empty_content = b"";
530        let cursor = Cursor::new(empty_content);
531
532        let gzip_format = CompressionFormat::Gzip;
533        let decoder_result = gzip_format.get_decoder(cursor);
534
535        // Decoder creation should succeed even with empty content
536        assert!(decoder_result.is_ok(), "Decoder creation should succeed even with empty content");
537    }
538
539    #[test]
540    fn test_compression_format_memory_efficiency() {
541        // Test memory efficiency of enum
542        use std::mem;
543
544        // Verify enum size is reasonable
545        let size = mem::size_of::<CompressionFormat>();
546        assert!(size <= 8, "CompressionFormat should be memory efficient, got {size} bytes");
547
548        // Verify Option<CompressionFormat> size
549        let option_size = mem::size_of::<Option<CompressionFormat>>();
550        assert!(
551            option_size <= 16,
552            "Option<CompressionFormat> should be efficient, got {option_size} bytes"
553        );
554    }
555
556    #[test]
557    fn test_extension_validation() {
558        // Test edge cases of extension validation
559        let test_cases = vec![
560            // Normal cases
561            ("gz", true),
562            ("bz2", true),
563            ("xz", true),
564            // Edge cases
565            ("", false),
566            ("g", false),
567            ("gzz", false),
568            ("gz2", false),
569            // Special characters
570            ("gz.", false),
571            (".gz", false),
572            ("gz-", false),
573            ("gz_", false),
574        ];
575
576        for (ext, should_be_known) in test_cases {
577            let format = CompressionFormat::from_extension(ext);
578            let is_known = format != CompressionFormat::Unknown;
579            assert_eq!(
580                is_known, should_be_known,
581                "Extension '{ext}' recognition mismatch: expected {should_be_known}, got {is_known}"
582            );
583        }
584    }
585
586    #[tokio::test]
587    async fn test_decoder_trait_bounds() {
588        // Test decoder trait bounds compliance
589        let sample_content = b"Hello, compression world!";
590        let cursor = Cursor::new(sample_content);
591
592        let gzip_format = CompressionFormat::Gzip;
593        let decoder = gzip_format.get_decoder(cursor).unwrap();
594
595        // Verify that the returned decoder satisfies required trait bounds
596        fn check_trait_bounds<T: AsyncRead + Send + Unpin + ?Sized>(_: &T) {}
597        check_trait_bounds(&*decoder);
598    }
599
600    #[test]
601    fn test_format_consistency_with_extensions() {
602        // 测试格式与扩展名的一致性
603        let consistency_tests = vec![
604            (CompressionFormat::Gzip, "gz"),
605            (CompressionFormat::Bzip2, "bz2"),
606            (CompressionFormat::Zip, "zip"),
607            (CompressionFormat::Xz, "xz"),
608            (CompressionFormat::Zlib, "zlib"),
609            (CompressionFormat::Zstd, "zst"),
610        ];
611
612        for (format, ext) in consistency_tests {
613            let parsed_format = CompressionFormat::from_extension(ext);
614            assert_eq!(parsed_format, format, "Extension '{ext}' should consistently map to {format:?}");
615        }
616    }
617
618    #[tokio::test]
619    async fn test_decompress_with_invalid_format() {
620        // Test decompression with invalid format
621        use std::sync::Arc;
622        use std::sync::atomic::{AtomicUsize, Ordering};
623
624        let sample_content = b"Hello, compression world!";
625        let cursor = Cursor::new(sample_content);
626
627        let processed_entries_count = Arc::new(AtomicUsize::new(0));
628        let processed_entries_count_clone = processed_entries_count.clone();
629
630        let decompress_result = decompress(cursor, CompressionFormat::Unknown, move |_archive_entry| {
631            processed_entries_count_clone.fetch_add(1, Ordering::SeqCst);
632            async move { Ok(()) }
633        })
634        .await;
635
636        assert!(decompress_result.is_err(), "Decompress with Unknown format should fail");
637        assert_eq!(
638            processed_entries_count.load(Ordering::SeqCst),
639            0,
640            "No entries should be processed with invalid format"
641        );
642    }
643
644    #[tokio::test]
645    async fn test_decompress_with_zip_format() {
646        // Test decompression with Zip format (currently not supported)
647        use std::sync::Arc;
648        use std::sync::atomic::{AtomicUsize, Ordering};
649
650        let sample_content = b"Hello, compression world!";
651        let cursor = Cursor::new(sample_content);
652
653        let processed_entries_count = Arc::new(AtomicUsize::new(0));
654        let processed_entries_count_clone = processed_entries_count.clone();
655
656        let decompress_result = decompress(cursor, CompressionFormat::Zip, move |_archive_entry| {
657            processed_entries_count_clone.fetch_add(1, Ordering::SeqCst);
658            async move { Ok(()) }
659        })
660        .await;
661
662        assert!(decompress_result.is_err(), "Decompress with Zip format should fail (not implemented)");
663        assert_eq!(
664            processed_entries_count.load(Ordering::SeqCst),
665            0,
666            "No entries should be processed with unsupported format"
667        );
668    }
669
670    #[tokio::test]
671    async fn test_decompress_error_propagation() {
672        // Test error propagation during decompression process
673        use std::sync::Arc;
674        use std::sync::atomic::{AtomicUsize, Ordering};
675
676        let sample_content = b"Hello, compression world!";
677        let cursor = Cursor::new(sample_content);
678
679        let callback_invocation_count = Arc::new(AtomicUsize::new(0));
680        let callback_invocation_count_clone = callback_invocation_count.clone();
681
682        let decompress_result = decompress(cursor, CompressionFormat::Gzip, move |_archive_entry| {
683            let invocation_number = callback_invocation_count_clone.fetch_add(1, Ordering::SeqCst);
684            async move {
685                if invocation_number == 0 {
686                    // First invocation returns an error
687                    Err(io::Error::other("Simulated callback error"))
688                } else {
689                    Ok(())
690                }
691            }
692        })
693        .await;
694
695        // Since input data is not valid gzip format, it may fail during parsing phase
696        // This mainly tests the error handling mechanism
697        assert!(decompress_result.is_err(), "Should propagate callback errors");
698    }
699
700    #[tokio::test]
701    async fn test_decompress_callback_execution() {
702        // Test callback function execution during decompression
703        use std::sync::Arc;
704        use std::sync::atomic::{AtomicBool, Ordering};
705
706        let sample_content = b"Hello, compression world!";
707        let cursor = Cursor::new(sample_content);
708
709        let callback_was_invoked = Arc::new(AtomicBool::new(false));
710        let callback_was_invoked_clone = callback_was_invoked.clone();
711
712        let _decompress_result = decompress(cursor, CompressionFormat::Gzip, move |_archive_entry| {
713            callback_was_invoked_clone.store(true, Ordering::SeqCst);
714            async move { Ok(()) }
715        })
716        .await;
717
718        // Note: Since test data is not valid gzip format, callback may not be invoked
719        // This test mainly verifies function signature and basic flow
720    }
721
722    #[test]
723    fn test_compression_format_clone_and_copy() {
724        // 测试 CompressionFormat 是否可以被复制
725        let format = CompressionFormat::Gzip;
726        let format_copy = format;
727
728        // 验证复制后的值相等
729        assert_eq!(format, format_copy);
730
731        // 验证原值仍然可用
732        assert_eq!(format, CompressionFormat::Gzip);
733    }
734
735    #[test]
736    fn test_compression_format_match_exhaustiveness() {
737        // 测试 match 语句的完整性
738        fn handle_format(format: CompressionFormat) -> &'static str {
739            match format {
740                CompressionFormat::Gzip => "gzip",
741                CompressionFormat::Bzip2 => "bzip2",
742                CompressionFormat::Zip => "zip",
743                CompressionFormat::Xz => "xz",
744                CompressionFormat::Zlib => "zlib",
745                CompressionFormat::Zstd => "zstd",
746                CompressionFormat::Tar => "tar",
747                CompressionFormat::Unknown => "unknown",
748            }
749        }
750
751        // 测试所有变体都有对应的处理
752        assert_eq!(handle_format(CompressionFormat::Gzip), "gzip");
753        assert_eq!(handle_format(CompressionFormat::Bzip2), "bzip2");
754        assert_eq!(handle_format(CompressionFormat::Zip), "zip");
755        assert_eq!(handle_format(CompressionFormat::Xz), "xz");
756        assert_eq!(handle_format(CompressionFormat::Zlib), "zlib");
757        assert_eq!(handle_format(CompressionFormat::Zstd), "zstd");
758        assert_eq!(handle_format(CompressionFormat::Unknown), "unknown");
759    }
760
761    #[test]
762    fn test_extension_parsing_performance() {
763        // 测试扩展名解析的性能(简单的性能测试)
764        let extensions = vec!["gz", "bz2", "zip", "xz", "zlib", "zst", "unknown"];
765
766        // 多次调用以测试性能一致性
767        for _ in 0..1000 {
768            for ext in &extensions {
769                let _format = CompressionFormat::from_extension(ext);
770            }
771        }
772
773        // Extension parsing performance test completed
774    }
775
776    #[test]
777    fn test_format_default_behavior() {
778        // 测试格式的默认行为
779        let unknown_extensions = vec!["", "txt", "doc", "pdf", "unknown_ext"];
780
781        for ext in unknown_extensions {
782            let format = CompressionFormat::from_extension(ext);
783            assert_eq!(format, CompressionFormat::Unknown, "Extension '{ext}' should default to Unknown");
784        }
785    }
786
787    #[test]
788    fn test_compression_level() {
789        // 测试压缩级别
790        let default_level = CompressionLevel::default();
791        assert_eq!(default_level, CompressionLevel::Default);
792
793        let fastest = CompressionLevel::Fastest;
794        let best = CompressionLevel::Best;
795        let custom = CompressionLevel::Level(5);
796
797        assert_ne!(fastest, best);
798        assert_ne!(default_level, custom);
799    }
800
801    #[test]
802    fn test_format_extension() {
803        // 测试格式扩展名获取
804        assert_eq!(CompressionFormat::Gzip.extension(), "gz");
805        assert_eq!(CompressionFormat::Bzip2.extension(), "bz2");
806        assert_eq!(CompressionFormat::Zip.extension(), "zip");
807        assert_eq!(CompressionFormat::Xz.extension(), "xz");
808        assert_eq!(CompressionFormat::Zlib.extension(), "zlib");
809        assert_eq!(CompressionFormat::Zstd.extension(), "zst");
810        assert_eq!(CompressionFormat::Tar.extension(), "tar");
811        assert_eq!(CompressionFormat::Unknown.extension(), "");
812    }
813
814    #[test]
815    fn test_format_is_supported() {
816        // 测试格式支持检查
817        assert!(CompressionFormat::Gzip.is_supported());
818        assert!(CompressionFormat::Bzip2.is_supported());
819        assert!(CompressionFormat::Zip.is_supported());
820        assert!(CompressionFormat::Xz.is_supported());
821        assert!(CompressionFormat::Zlib.is_supported());
822        assert!(CompressionFormat::Zstd.is_supported());
823        assert!(CompressionFormat::Tar.is_supported());
824        assert!(!CompressionFormat::Unknown.is_supported());
825    }
826
827    #[test]
828    fn test_format_from_path() {
829        // 测试从路径识别格式
830        use std::path::Path;
831
832        assert_eq!(CompressionFormat::from_path("file.gz"), CompressionFormat::Gzip);
833        assert_eq!(CompressionFormat::from_path("archive.zip"), CompressionFormat::Zip);
834        assert_eq!(CompressionFormat::from_path("/path/to/file.tar.gz"), CompressionFormat::Gzip);
835        assert_eq!(CompressionFormat::from_path("no_extension"), CompressionFormat::Unknown);
836
837        let path = Path::new("test.bz2");
838        assert_eq!(CompressionFormat::from_path(path), CompressionFormat::Bzip2);
839    }
840
841    #[tokio::test]
842    async fn test_get_encoder_supported_formats() {
843        // 测试支持的格式能够创建编码器
844        use std::io::Cursor;
845
846        let output = Vec::new();
847        let cursor = Cursor::new(output);
848
849        let gzip_format = CompressionFormat::Gzip;
850        let encoder_result = gzip_format.get_encoder(cursor, CompressionLevel::Default);
851        assert!(encoder_result.is_ok(), "Gzip encoder should be created successfully");
852    }
853
854    #[tokio::test]
855    async fn test_get_encoder_unsupported_formats() {
856        // 测试不支持的格式返回错误
857        use std::io::Cursor;
858
859        let output1 = Vec::new();
860        let cursor1 = Cursor::new(output1);
861
862        let unknown_format = CompressionFormat::Unknown;
863        let encoder_result = unknown_format.get_encoder(cursor1, CompressionLevel::Default);
864        assert!(encoder_result.is_err(), "Unknown format should return error");
865
866        let output2 = Vec::new();
867        let cursor2 = Cursor::new(output2);
868        let zip_format = CompressionFormat::Zip;
869        let zip_encoder_result = zip_format.get_encoder(cursor2, CompressionLevel::Default);
870        assert!(zip_encoder_result.is_err(), "Zip format should return error (requires special handling)");
871    }
872
873    #[tokio::test]
874    async fn test_compressor_basic_functionality() {
875        // Test basic compressor functionality (Note: current implementation returns empty vector as placeholder)
876        let compressor = Compressor::new(CompressionFormat::Gzip);
877        let sample_text = b"Hello, World! This is a sample string for compression testing.";
878
879        let compression_result = compressor.compress(sample_text).await;
880        assert!(compression_result.is_ok(), "Compression should succeed");
881
882        // Note: Current implementation returns empty vector as placeholder
883        let compressed_output = compression_result.unwrap();
884        // assert!(!compressed_output.is_empty(), "Compressed data should not be empty");
885        // assert_ne!(compressed_output.as_slice(), sample_text, "Compressed data should be different from original");
886
887        // Temporarily verify that function can be called normally
888        assert!(compressed_output.is_empty(), "Current implementation returns empty vector as placeholder");
889    }
890
891    #[tokio::test]
892    async fn test_compressor_with_level() {
893        // Test compressor with custom compression level
894        let compressor = Compressor::new(CompressionFormat::Gzip).with_level(CompressionLevel::Best);
895
896        let sample_text = b"Sample text for compression level testing";
897        let compression_result = compressor.compress(sample_text).await;
898        assert!(compression_result.is_ok(), "Compression with custom level should succeed");
899    }
900
901    #[test]
902    fn test_decompressor_creation() {
903        // 测试解压缩器创建
904        let decompressor = Decompressor::new(CompressionFormat::Gzip);
905        assert_eq!(decompressor.format, CompressionFormat::Gzip);
906
907        let auto_decompressor = Decompressor::auto_detect("test.gz");
908        assert_eq!(auto_decompressor.format, CompressionFormat::Gzip);
909    }
910
911    #[test]
912    fn test_zip_entry_creation() {
913        // 测试 ZIP 条目信息创建
914        let entry = ZipEntry {
915            name: "test.txt".to_string(),
916            size: 1024,
917            compressed_size: 512,
918            is_dir: false,
919            compression_method: "Deflate".to_string(),
920        };
921
922        assert_eq!(entry.name, "test.txt");
923        assert_eq!(entry.size, 1024);
924        assert_eq!(entry.compressed_size, 512);
925        assert!(!entry.is_dir);
926        assert_eq!(entry.compression_method, "Deflate");
927    }
928
929    #[test]
930    fn test_compression_level_variants() {
931        // 测试压缩级别的所有变体
932        let levels = vec![
933            CompressionLevel::Fastest,
934            CompressionLevel::Best,
935            CompressionLevel::Default,
936            CompressionLevel::Level(1),
937            CompressionLevel::Level(9),
938        ];
939
940        for level in levels {
941            // 验证每个级别都有对应的 Debug 实现
942            let _debug_str = format!("{level:?}");
943        }
944    }
945
946    #[test]
947    fn test_format_comprehensive_coverage() {
948        // 测试格式的全面覆盖
949        let all_formats = vec![
950            CompressionFormat::Gzip,
951            CompressionFormat::Bzip2,
952            CompressionFormat::Zip,
953            CompressionFormat::Xz,
954            CompressionFormat::Zlib,
955            CompressionFormat::Zstd,
956            CompressionFormat::Tar,
957            CompressionFormat::Unknown,
958        ];
959
960        for format in all_formats {
961            // 验证每个格式都有扩展名
962            let _ext = format.extension();
963
964            // 验证支持状态检查
965            let _supported = format.is_supported();
966
967            // 验证 Debug 实现
968            let _debug = format!("{format:?}");
969        }
970    }
971}
972
973// #[tokio::test]
974// async fn test_decompress() -> io::Result<()> {
975//     use std::path::Path;
976//     use tokio::fs::File;
977
978//     let input_path = "/Users/weisd/Downloads/wsd.tar.gz"; // 替换为你的压缩文件路径
979
980//     let f = File::open(input_path).await?;
981
982//     let Some(ext) = Path::new(input_path).extension().and_then(|s| s.to_str()) else {
983//         return Err(io::Error::new(io::ErrorKind::InvalidInput, "Unsupported file format"));
984//     };
985
986//     match decompress(
987//         f,
988//         CompressionFormat::from_extension(ext),
989//         |entry: tokio_tar::Entry<Archive<Box<dyn AsyncRead + Send + Unpin>>>| async move {
990//             let path = entry.path().unwrap();
991//             println!("Extracted: {}", path.display());
992//             Ok(())
993//         },
994//     )
995//     .await
996//     {
997//         Ok(_) => println!("解压成功!"),
998//         Err(e) => println!("解压失败:{}", e),
999//     }
1000
1001//     Ok(())
1002// }