omnizip 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +32 -0
- data/.rubocop_todo.yml +754 -0
- data/COPYING +502 -0
- data/Gemfile +17 -0
- data/LICENSE +12 -0
- data/README.adoc +1045 -0
- data/Rakefile +12 -0
- data/benchmark/README.md +260 -0
- data/benchmark/benchmark_suite.rb +125 -0
- data/benchmark/compression_bench.rb +181 -0
- data/benchmark/filter_bench.rb +180 -0
- data/benchmark/models/benchmark_result.rb +59 -0
- data/benchmark/models/comparison_result.rb +69 -0
- data/benchmark/profile_suite.rb +167 -0
- data/benchmark/reporter.rb +150 -0
- data/benchmark/run_benchmarks.rb +66 -0
- data/benchmark/test_data.rb +137 -0
- data/config/formats/rar3_spec.yml +91 -0
- data/config/formats/rar5_spec.yml +102 -0
- data/docs/.github/workflows/docs.yml +142 -0
- data/docs/.gitignore +21 -0
- data/docs/.lychee.toml +67 -0
- data/docs/Gemfile +13 -0
- data/docs/RAR_WRITE_SUPPORT.md +26 -0
- data/docs/README.md +101 -0
- data/docs/_config.yml +112 -0
- data/docs/assets/logo.svg +1 -0
- data/docs/assets/omnizip-logo.pdf +1540 -11
- data/docs/comparison/feature-matrix.adoc +694 -0
- data/docs/comparison/index.adoc +113 -0
- data/docs/comparison/vs-7zip.adoc +309 -0
- data/docs/comparison/vs-peazip.adoc +77 -0
- data/docs/comparison/vs-rubyzip.adoc +342 -0
- data/docs/comparison/vs-winrar.adoc +100 -0
- data/docs/compatibility.adoc +579 -0
- data/docs/concepts/index.adoc +129 -0
- data/docs/developer/architecture.adoc +256 -0
- data/docs/developer/contributing.adoc +158 -0
- data/docs/developer/index.adoc +25 -0
- data/docs/developer/testing.adoc +212 -0
- data/docs/getting-started/basic-usage.adoc +271 -0
- data/docs/getting-started/index.adoc +42 -0
- data/docs/getting-started/installation.adoc +138 -0
- data/docs/getting-started/quick-start.adoc +185 -0
- data/docs/getting-started/your-first-archive.adoc +218 -0
- data/docs/guides/advanced-features/encryption.adoc +300 -0
- data/docs/guides/advanced-features/index.adoc +49 -0
- data/docs/guides/advanced-features/parallel-processing.adoc +246 -0
- data/docs/guides/advanced-features/progress-tracking.adoc +320 -0
- data/docs/guides/advanced-features/streaming.adoc +212 -0
- data/docs/guides/archive-formats/gzip-format.adoc +107 -0
- data/docs/guides/archive-formats/index.adoc +130 -0
- data/docs/guides/archive-formats/rar-format.adoc +104 -0
- data/docs/guides/archive-formats/rar5.adoc +521 -0
- data/docs/guides/archive-formats/seven-zip-format.adoc +35 -0
- data/docs/guides/archive-formats/tar-format.adoc +106 -0
- data/docs/guides/archive-formats/xz-format.adoc +118 -0
- data/docs/guides/archive-formats/zip-format.adoc +35 -0
- data/docs/guides/compression-algorithms/bzip2.adoc +113 -0
- data/docs/guides/compression-algorithms/deflate.adoc +319 -0
- data/docs/guides/compression-algorithms/index.adoc +190 -0
- data/docs/guides/compression-algorithms/lzma.adoc +398 -0
- data/docs/guides/compression-algorithms/lzma2.adoc +327 -0
- data/docs/guides/compression-algorithms/ppmd.adoc +316 -0
- data/docs/guides/compression-algorithms/zstandard.adoc +361 -0
- data/docs/guides/creating-archives.adoc +354 -0
- data/docs/guides/extracting-archives.adoc +53 -0
- data/docs/guides/format-conversion.adoc +64 -0
- data/docs/guides/index.adoc +49 -0
- data/docs/guides/migration-rubyzip.adoc +217 -0
- data/docs/guides/parity-archives.adoc +605 -0
- data/docs/guides/performance-tuning.adoc +88 -0
- data/docs/index.adoc +218 -0
- data/docs/lychee.toml +67 -0
- data/docs/reference/api/overview.adoc +188 -0
- data/docs/reference/cli/compress-command.adoc +114 -0
- data/docs/reference/cli/overview.adoc +140 -0
- data/docs/reference/index.adoc +26 -0
- data/docs/resources/faq.adoc +185 -0
- data/docs/resources/quick-reference.adoc +222 -0
- data/docs/troubleshooting/index.adoc +208 -0
- data/examples/api_comparison.rb +205 -0
- data/examples/deflate64_example.rb +96 -0
- data/examples/par2_demo.rb +121 -0
- data/examples/quick_start_native.rb +150 -0
- data/examples/quick_start_rubyzip.rb +115 -0
- data/examples/rubyzip_compatibility_demo.rb +194 -0
- data/exe/omnizip +27 -0
- data/lib/omnizip/algorithm.rb +130 -0
- data/lib/omnizip/algorithm_registry.rb +86 -0
- data/lib/omnizip/algorithms/.keep +0 -0
- data/lib/omnizip/algorithms/bzip2/bwt.rb +225 -0
- data/lib/omnizip/algorithms/bzip2/decoder.rb +193 -0
- data/lib/omnizip/algorithms/bzip2/encoder.rb +237 -0
- data/lib/omnizip/algorithms/bzip2/huffman.rb +206 -0
- data/lib/omnizip/algorithms/bzip2/mtf.rb +101 -0
- data/lib/omnizip/algorithms/bzip2/rle.rb +151 -0
- data/lib/omnizip/algorithms/bzip2.rb +130 -0
- data/lib/omnizip/algorithms/deflate/constants.rb +28 -0
- data/lib/omnizip/algorithms/deflate/decoder.rb +38 -0
- data/lib/omnizip/algorithms/deflate/encoder.rb +46 -0
- data/lib/omnizip/algorithms/deflate.rb +128 -0
- data/lib/omnizip/algorithms/deflate64/constants.rb +45 -0
- data/lib/omnizip/algorithms/deflate64/decoder.rb +153 -0
- data/lib/omnizip/algorithms/deflate64/encoder.rb +98 -0
- data/lib/omnizip/algorithms/deflate64/huffman_coder.rb +354 -0
- data/lib/omnizip/algorithms/deflate64/lz77_encoder.rb +142 -0
- data/lib/omnizip/algorithms/deflate64.rb +109 -0
- data/lib/omnizip/algorithms/lzma/bit_model.rb +120 -0
- data/lib/omnizip/algorithms/lzma/constants.rb +112 -0
- data/lib/omnizip/algorithms/lzma/decoder.rb +148 -0
- data/lib/omnizip/algorithms/lzma/dictionary.rb +69 -0
- data/lib/omnizip/algorithms/lzma/distance_coder.rb +415 -0
- data/lib/omnizip/algorithms/lzma/encoder.rb +142 -0
- data/lib/omnizip/algorithms/lzma/length_coder.rb +260 -0
- data/lib/omnizip/algorithms/lzma/literal_decoder.rb +320 -0
- data/lib/omnizip/algorithms/lzma/literal_encoder.rb +210 -0
- data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +341 -0
- data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +192 -0
- data/lib/omnizip/algorithms/lzma/lzma_state.rb +128 -0
- data/lib/omnizip/algorithms/lzma/match.rb +32 -0
- data/lib/omnizip/algorithms/lzma/match_finder.rb +205 -0
- data/lib/omnizip/algorithms/lzma/match_finder_config.rb +142 -0
- data/lib/omnizip/algorithms/lzma/match_finder_factory.rb +88 -0
- data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +130 -0
- data/lib/omnizip/algorithms/lzma/probability_models.rb +72 -0
- data/lib/omnizip/algorithms/lzma/range_coder.rb +85 -0
- data/lib/omnizip/algorithms/lzma/range_decoder.rb +434 -0
- data/lib/omnizip/algorithms/lzma/range_encoder.rb +194 -0
- data/lib/omnizip/algorithms/lzma/state.rb +127 -0
- data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +325 -0
- data/lib/omnizip/algorithms/lzma/xz_encoder.rb +426 -0
- data/lib/omnizip/algorithms/lzma/xz_encoder_fast.rb +645 -0
- data/lib/omnizip/algorithms/lzma/xz_match_finder_adapter.rb +227 -0
- data/lib/omnizip/algorithms/lzma/xz_price_calculator.rb +169 -0
- data/lib/omnizip/algorithms/lzma/xz_probability_models.rb +261 -0
- data/lib/omnizip/algorithms/lzma/xz_range_encoder.rb +223 -0
- data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +331 -0
- data/lib/omnizip/algorithms/lzma/xz_state.rb +116 -0
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +2055 -0
- data/lib/omnizip/algorithms/lzma.rb +238 -0
- data/lib/omnizip/algorithms/lzma2/chunk_manager.rb +182 -0
- data/lib/omnizip/algorithms/lzma2/constants.rb +41 -0
- data/lib/omnizip/algorithms/lzma2/encoder.rb +147 -0
- data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +161 -0
- data/lib/omnizip/algorithms/lzma2/properties.rb +179 -0
- data/lib/omnizip/algorithms/lzma2/simple_lzma2_encoder.rb +127 -0
- data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +85 -0
- data/lib/omnizip/algorithms/lzma2.rb +141 -0
- data/lib/omnizip/algorithms/ppmd7/constants.rb +74 -0
- data/lib/omnizip/algorithms/ppmd7/context.rb +154 -0
- data/lib/omnizip/algorithms/ppmd7/decoder.rb +126 -0
- data/lib/omnizip/algorithms/ppmd7/encoder.rb +163 -0
- data/lib/omnizip/algorithms/ppmd7/model.rb +248 -0
- data/lib/omnizip/algorithms/ppmd7/symbol_state.rb +57 -0
- data/lib/omnizip/algorithms/ppmd7.rb +116 -0
- data/lib/omnizip/algorithms/ppmd8/constants.rb +61 -0
- data/lib/omnizip/algorithms/ppmd8/context.rb +34 -0
- data/lib/omnizip/algorithms/ppmd8/decoder.rb +107 -0
- data/lib/omnizip/algorithms/ppmd8/encoder.rb +138 -0
- data/lib/omnizip/algorithms/ppmd8/model.rb +250 -0
- data/lib/omnizip/algorithms/ppmd8/restoration_method.rb +78 -0
- data/lib/omnizip/algorithms/ppmd8.rb +82 -0
- data/lib/omnizip/algorithms/ppmd_base.rb +138 -0
- data/lib/omnizip/algorithms/sevenzip_lzma2.rb +123 -0
- data/lib/omnizip/algorithms/xz_lzma2.rb +118 -0
- data/lib/omnizip/algorithms/zstandard/constants.rb +25 -0
- data/lib/omnizip/algorithms/zstandard/decoder.rb +46 -0
- data/lib/omnizip/algorithms/zstandard/encoder.rb +51 -0
- data/lib/omnizip/algorithms/zstandard.rb +138 -0
- data/lib/omnizip/buffer/memory_archive.rb +251 -0
- data/lib/omnizip/buffer/memory_extractor.rb +224 -0
- data/lib/omnizip/buffer.rb +176 -0
- data/lib/omnizip/checksum_registry.rb +114 -0
- data/lib/omnizip/checksums/crc32.rb +100 -0
- data/lib/omnizip/checksums/crc64.rb +101 -0
- data/lib/omnizip/checksums/crc_base.rb +158 -0
- data/lib/omnizip/checksums/verifier.rb +131 -0
- data/lib/omnizip/chunked/memory_manager.rb +194 -0
- data/lib/omnizip/chunked/reader.rb +78 -0
- data/lib/omnizip/chunked/writer.rb +120 -0
- data/lib/omnizip/chunked.rb +129 -0
- data/lib/omnizip/cli/output_formatter.rb +104 -0
- data/lib/omnizip/cli.rb +572 -0
- data/lib/omnizip/commands/.keep +0 -0
- data/lib/omnizip/commands/archive_create_command.rb +427 -0
- data/lib/omnizip/commands/archive_extract_command.rb +272 -0
- data/lib/omnizip/commands/archive_list_command.rb +218 -0
- data/lib/omnizip/commands/archive_repair_command.rb +131 -0
- data/lib/omnizip/commands/archive_verify_command.rb +117 -0
- data/lib/omnizip/commands/compress_command.rb +117 -0
- data/lib/omnizip/commands/decompress_command.rb +120 -0
- data/lib/omnizip/commands/list_command.rb +53 -0
- data/lib/omnizip/commands/metadata_command.rb +153 -0
- data/lib/omnizip/commands/parity_create_command.rb +122 -0
- data/lib/omnizip/commands/parity_repair_command.rb +122 -0
- data/lib/omnizip/commands/parity_verify_command.rb +124 -0
- data/lib/omnizip/commands/profile_list_command.rb +56 -0
- data/lib/omnizip/commands/profile_show_command.rb +44 -0
- data/lib/omnizip/convenience.rb +359 -0
- data/lib/omnizip/converter/conversion_registry.rb +49 -0
- data/lib/omnizip/converter/conversion_strategy.rb +121 -0
- data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +97 -0
- data/lib/omnizip/converter/zip_to_seven_zip_strategy.rb +112 -0
- data/lib/omnizip/converter.rb +105 -0
- data/lib/omnizip/crypto/aes256/cipher.rb +100 -0
- data/lib/omnizip/crypto/aes256/constants.rb +28 -0
- data/lib/omnizip/crypto/aes256/key_derivation.rb +101 -0
- data/lib/omnizip/crypto/aes256.rb +102 -0
- data/lib/omnizip/error.rb +106 -0
- data/lib/omnizip/eta/exponential_smoothing_estimator.rb +98 -0
- data/lib/omnizip/eta/moving_average_estimator.rb +99 -0
- data/lib/omnizip/eta/rate_calculator.rb +104 -0
- data/lib/omnizip/eta/sample_history.rb +143 -0
- data/lib/omnizip/eta/time_estimator.rb +106 -0
- data/lib/omnizip/eta.rb +63 -0
- data/lib/omnizip/extraction/filter_chain.rb +177 -0
- data/lib/omnizip/extraction/glob_pattern.rb +140 -0
- data/lib/omnizip/extraction/pattern_matcher.rb +70 -0
- data/lib/omnizip/extraction/predicate_pattern.rb +52 -0
- data/lib/omnizip/extraction/regex_pattern.rb +50 -0
- data/lib/omnizip/extraction/selective_extractor.rb +240 -0
- data/lib/omnizip/extraction.rb +111 -0
- data/lib/omnizip/file_type/mime_classifier.rb +144 -0
- data/lib/omnizip/file_type.rb +113 -0
- data/lib/omnizip/filter.rb +139 -0
- data/lib/omnizip/filter_pipeline.rb +108 -0
- data/lib/omnizip/filter_registry.rb +166 -0
- data/lib/omnizip/filters/bcj.rb +279 -0
- data/lib/omnizip/filters/bcj2/constants.rb +53 -0
- data/lib/omnizip/filters/bcj2/decoder.rb +200 -0
- data/lib/omnizip/filters/bcj2/encoder.rb +61 -0
- data/lib/omnizip/filters/bcj2/stream_data.rb +93 -0
- data/lib/omnizip/filters/bcj2.rb +99 -0
- data/lib/omnizip/filters/bcj_arm.rb +176 -0
- data/lib/omnizip/filters/bcj_arm64.rb +244 -0
- data/lib/omnizip/filters/bcj_ia64.rb +196 -0
- data/lib/omnizip/filters/bcj_ppc.rb +190 -0
- data/lib/omnizip/filters/bcj_sparc.rb +176 -0
- data/lib/omnizip/filters/bcj_x86.rb +193 -0
- data/lib/omnizip/filters/delta.rb +196 -0
- data/lib/omnizip/filters/filter_base.rb +72 -0
- data/lib/omnizip/filters/registry.rb +123 -0
- data/lib/omnizip/filters/xz_delta.rb +258 -0
- data/lib/omnizip/format_detector.rb +162 -0
- data/lib/omnizip/format_registry.rb +59 -0
- data/lib/omnizip/formats/.keep +0 -0
- data/lib/omnizip/formats/bzip2_file.rb +172 -0
- data/lib/omnizip/formats/cpio/constants.rb +55 -0
- data/lib/omnizip/formats/cpio/entry.rb +385 -0
- data/lib/omnizip/formats/cpio/reader.rb +196 -0
- data/lib/omnizip/formats/cpio/writer.rb +234 -0
- data/lib/omnizip/formats/cpio.rb +140 -0
- data/lib/omnizip/formats/format_spec_loader.rb +230 -0
- data/lib/omnizip/formats/gzip.rb +238 -0
- data/lib/omnizip/formats/iso/directory_builder.rb +297 -0
- data/lib/omnizip/formats/iso/directory_record.rb +152 -0
- data/lib/omnizip/formats/iso/joliet.rb +204 -0
- data/lib/omnizip/formats/iso/path_table.rb +125 -0
- data/lib/omnizip/formats/iso/reader.rb +197 -0
- data/lib/omnizip/formats/iso/rock_ridge.rb +349 -0
- data/lib/omnizip/formats/iso/volume_builder.rb +320 -0
- data/lib/omnizip/formats/iso/volume_descriptor.rb +168 -0
- data/lib/omnizip/formats/iso/writer.rb +530 -0
- data/lib/omnizip/formats/iso.rb +140 -0
- data/lib/omnizip/formats/lzip.rb +175 -0
- data/lib/omnizip/formats/lzma_alone.rb +171 -0
- data/lib/omnizip/formats/rar/archive_repairer.rb +243 -0
- data/lib/omnizip/formats/rar/archive_verifier.rb +195 -0
- data/lib/omnizip/formats/rar/block_parser.rb +243 -0
- data/lib/omnizip/formats/rar/compression/bit_stream.rb +180 -0
- data/lib/omnizip/formats/rar/compression/dispatcher.rb +217 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/decoder.rb +216 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/encoder.rb +158 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_builder.rb +217 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_coder.rb +189 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/match_finder.rb +135 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/sliding_window.rb +165 -0
- data/lib/omnizip/formats/rar/compression/ppmd/context.rb +105 -0
- data/lib/omnizip/formats/rar/compression/ppmd/decoder.rb +219 -0
- data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +262 -0
- data/lib/omnizip/formats/rar/compression_method_registry.rb +106 -0
- data/lib/omnizip/formats/rar/constants.rb +82 -0
- data/lib/omnizip/formats/rar/decompressor.rb +238 -0
- data/lib/omnizip/formats/rar/external_writer.rb +312 -0
- data/lib/omnizip/formats/rar/header.rb +192 -0
- data/lib/omnizip/formats/rar/license_validator.rb +109 -0
- data/lib/omnizip/formats/rar/models/rar_archive.rb +77 -0
- data/lib/omnizip/formats/rar/models/rar_entry.rb +65 -0
- data/lib/omnizip/formats/rar/models/rar_volume.rb +56 -0
- data/lib/omnizip/formats/rar/parity_handler.rb +292 -0
- data/lib/omnizip/formats/rar/rar5/compression/lzma.rb +202 -0
- data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +578 -0
- data/lib/omnizip/formats/rar/rar5/compression/store.rb +60 -0
- data/lib/omnizip/formats/rar/rar5/crc32.rb +39 -0
- data/lib/omnizip/formats/rar/rar5/encryption/aes256_cbc.rb +97 -0
- data/lib/omnizip/formats/rar/rar5/encryption/encryption_header.rb +114 -0
- data/lib/omnizip/formats/rar/rar5/encryption/encryption_manager.rb +166 -0
- data/lib/omnizip/formats/rar/rar5/encryption/key_derivation.rb +97 -0
- data/lib/omnizip/formats/rar/rar5/header.rb +187 -0
- data/lib/omnizip/formats/rar/rar5/models/encryption_options.rb +74 -0
- data/lib/omnizip/formats/rar/rar5/models/recovery_options.rb +63 -0
- data/lib/omnizip/formats/rar/rar5/models/solid_options.rb +63 -0
- data/lib/omnizip/formats/rar/rar5/models/volume_options.rb +74 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/ARCHITECTURE.md +290 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_manager.rb +264 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_splitter.rb +155 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +194 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_encoder.rb +109 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_manager.rb +142 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_stream.rb +121 -0
- data/lib/omnizip/formats/rar/rar5/vint.rb +65 -0
- data/lib/omnizip/formats/rar/rar5/writer.rb +466 -0
- data/lib/omnizip/formats/rar/rar_format_base.rb +241 -0
- data/lib/omnizip/formats/rar/reader.rb +366 -0
- data/lib/omnizip/formats/rar/recovery_record.rb +245 -0
- data/lib/omnizip/formats/rar/volume_manager.rb +168 -0
- data/lib/omnizip/formats/rar/writer.rb +431 -0
- data/lib/omnizip/formats/rar.rb +205 -0
- data/lib/omnizip/formats/rar3/compressor.rb +73 -0
- data/lib/omnizip/formats/rar3/decompressor.rb +66 -0
- data/lib/omnizip/formats/rar3/reader.rb +386 -0
- data/lib/omnizip/formats/rar3/writer.rb +219 -0
- data/lib/omnizip/formats/rar5/compressor.rb +73 -0
- data/lib/omnizip/formats/rar5/decompressor.rb +66 -0
- data/lib/omnizip/formats/rar5/reader.rb +342 -0
- data/lib/omnizip/formats/rar5/writer.rb +214 -0
- data/lib/omnizip/formats/seven_zip/coder_chain.rb +150 -0
- data/lib/omnizip/formats/seven_zip/constants.rb +126 -0
- data/lib/omnizip/formats/seven_zip/encoded_header.rb +114 -0
- data/lib/omnizip/formats/seven_zip/encrypted_header.rb +142 -0
- data/lib/omnizip/formats/seven_zip/file_collector.rb +144 -0
- data/lib/omnizip/formats/seven_zip/header.rb +106 -0
- data/lib/omnizip/formats/seven_zip/header_encryptor.rb +134 -0
- data/lib/omnizip/formats/seven_zip/header_writer.rb +466 -0
- data/lib/omnizip/formats/seven_zip/models/coder_info.rb +30 -0
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +58 -0
- data/lib/omnizip/formats/seven_zip/models/folder.rb +69 -0
- data/lib/omnizip/formats/seven_zip/models/stream_info.rb +42 -0
- data/lib/omnizip/formats/seven_zip/parser.rb +660 -0
- data/lib/omnizip/formats/seven_zip/reader.rb +458 -0
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +632 -0
- data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +315 -0
- data/lib/omnizip/formats/seven_zip/stream_compressor.rb +151 -0
- data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +162 -0
- data/lib/omnizip/formats/seven_zip/writer.rb +740 -0
- data/lib/omnizip/formats/seven_zip.rb +93 -0
- data/lib/omnizip/formats/tar/constants.rb +73 -0
- data/lib/omnizip/formats/tar/entry.rb +94 -0
- data/lib/omnizip/formats/tar/header.rb +168 -0
- data/lib/omnizip/formats/tar/reader.rb +121 -0
- data/lib/omnizip/formats/tar/writer.rb +216 -0
- data/lib/omnizip/formats/tar.rb +84 -0
- data/lib/omnizip/formats/xz/reader.rb +116 -0
- data/lib/omnizip/formats/xz.rb +237 -0
- data/lib/omnizip/formats/xz_impl/block_decoder.rb +754 -0
- data/lib/omnizip/formats/xz_impl/block_encoder.rb +306 -0
- data/lib/omnizip/formats/xz_impl/block_header.rb +210 -0
- data/lib/omnizip/formats/xz_impl/block_header_parser.rb +186 -0
- data/lib/omnizip/formats/xz_impl/constants.rb +49 -0
- data/lib/omnizip/formats/xz_impl/index_decoder.rb +174 -0
- data/lib/omnizip/formats/xz_impl/index_encoder.rb +122 -0
- data/lib/omnizip/formats/xz_impl/stream_decoder.rb +468 -0
- data/lib/omnizip/formats/xz_impl/stream_encoder.rb +99 -0
- data/lib/omnizip/formats/xz_impl/stream_footer.rb +81 -0
- data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +117 -0
- data/lib/omnizip/formats/xz_impl/stream_header.rb +55 -0
- data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +108 -0
- data/lib/omnizip/formats/xz_impl/vli.rb +128 -0
- data/lib/omnizip/formats/xz_impl/writer.rb +421 -0
- data/lib/omnizip/formats/zip/central_directory_header.rb +195 -0
- data/lib/omnizip/formats/zip/constants.rb +69 -0
- data/lib/omnizip/formats/zip/end_of_central_directory.rb +133 -0
- data/lib/omnizip/formats/zip/local_file_header.rb +138 -0
- data/lib/omnizip/formats/zip/reader.rb +250 -0
- data/lib/omnizip/formats/zip/unix_extra_field.rb +153 -0
- data/lib/omnizip/formats/zip/writer.rb +375 -0
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +104 -0
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +66 -0
- data/lib/omnizip/formats/zip/zip64_extra_field.rb +114 -0
- data/lib/omnizip/formats/zip.rb +50 -0
- data/lib/omnizip/implementations/base/lzma2_decoder_base.rb +75 -0
- data/lib/omnizip/implementations/base/lzma2_encoder_base.rb +128 -0
- data/lib/omnizip/implementations/base/lzma_decoder_base.rb +83 -0
- data/lib/omnizip/implementations/base/lzma_encoder_base.rb +108 -0
- data/lib/omnizip/implementations/base/state_machine_base.rb +182 -0
- data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +421 -0
- data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +465 -0
- data/lib/omnizip/implementations/seven_zip/lzma/match_finder.rb +288 -0
- data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +200 -0
- data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +197 -0
- data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +141 -0
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +519 -0
- data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +723 -0
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +750 -0
- data/lib/omnizip/io/buffered_input.rb +146 -0
- data/lib/omnizip/io/buffered_output.rb +105 -0
- data/lib/omnizip/io/stream_manager.rb +115 -0
- data/lib/omnizip/link_handler/hard_link.rb +79 -0
- data/lib/omnizip/link_handler/symbolic_link.rb +74 -0
- data/lib/omnizip/link_handler.rb +124 -0
- data/lib/omnizip/metadata/archive_metadata.rb +114 -0
- data/lib/omnizip/metadata/entry_metadata.rb +146 -0
- data/lib/omnizip/metadata/metadata_editor.rb +171 -0
- data/lib/omnizip/metadata/metadata_registry.rb +64 -0
- data/lib/omnizip/metadata/metadata_validator.rb +99 -0
- data/lib/omnizip/metadata.rb +57 -0
- data/lib/omnizip/models/.keep +0 -0
- data/lib/omnizip/models/algorithm_metadata.rb +73 -0
- data/lib/omnizip/models/compression_options.rb +71 -0
- data/lib/omnizip/models/conversion_options.rb +87 -0
- data/lib/omnizip/models/conversion_result.rb +135 -0
- data/lib/omnizip/models/eta_result.rb +46 -0
- data/lib/omnizip/models/extraction_rule.rb +115 -0
- data/lib/omnizip/models/filter_chain.rb +144 -0
- data/lib/omnizip/models/filter_config.rb +183 -0
- data/lib/omnizip/models/match_result.rb +124 -0
- data/lib/omnizip/models/optimization_suggestion.rb +91 -0
- data/lib/omnizip/models/parallel_options.rb +104 -0
- data/lib/omnizip/models/performance_result.rb +79 -0
- data/lib/omnizip/models/profile_report.rb +82 -0
- data/lib/omnizip/models/progress_options.rb +38 -0
- data/lib/omnizip/models/split_options.rb +116 -0
- data/lib/omnizip/optimization_registry.rb +81 -0
- data/lib/omnizip/parallel/job_queue.rb +209 -0
- data/lib/omnizip/parallel/job_scheduler.rb +203 -0
- data/lib/omnizip/parallel/parallel_compressor.rb +347 -0
- data/lib/omnizip/parallel/parallel_extractor.rb +329 -0
- data/lib/omnizip/parallel/worker_pool.rb +223 -0
- data/lib/omnizip/parallel.rb +149 -0
- data/lib/omnizip/parity/chunked_block_processor.rb +196 -0
- data/lib/omnizip/parity/galois16.rb +145 -0
- data/lib/omnizip/parity/models/creator_packet.rb +73 -0
- data/lib/omnizip/parity/models/file_description_packet.rb +133 -0
- data/lib/omnizip/parity/models/ifsc_packet.rb +123 -0
- data/lib/omnizip/parity/models/main_packet.rb +128 -0
- data/lib/omnizip/parity/models/packet.rb +156 -0
- data/lib/omnizip/parity/models/packet_registry.rb +109 -0
- data/lib/omnizip/parity/models/recovery_slice_packet.rb +78 -0
- data/lib/omnizip/parity/par2_creator.rb +531 -0
- data/lib/omnizip/parity/par2_repairer.rb +407 -0
- data/lib/omnizip/parity/par2_verifier.rb +364 -0
- data/lib/omnizip/parity/par2cmdline_algorithm.rb +110 -0
- data/lib/omnizip/parity/par2cmdline_coefficients.rb +78 -0
- data/lib/omnizip/parity/reed_solomon_decoder.rb +266 -0
- data/lib/omnizip/parity/reed_solomon_encoder.rb +111 -0
- data/lib/omnizip/parity/reed_solomon_matrix.rb +342 -0
- data/lib/omnizip/parity.rb +186 -0
- data/lib/omnizip/password/encryption_registry.rb +65 -0
- data/lib/omnizip/password/encryption_strategy.rb +96 -0
- data/lib/omnizip/password/password_validator.rb +129 -0
- data/lib/omnizip/password/winzip_aes_strategy.rb +192 -0
- data/lib/omnizip/password/zip_crypto_strategy.rb +141 -0
- data/lib/omnizip/password.rb +87 -0
- data/lib/omnizip/pipe/stream_compressor.rb +124 -0
- data/lib/omnizip/pipe/stream_decompressor.rb +174 -0
- data/lib/omnizip/pipe.rb +121 -0
- data/lib/omnizip/platform/ntfs_streams.rb +201 -0
- data/lib/omnizip/platform.rb +189 -0
- data/lib/omnizip/profile/archive_profile.rb +39 -0
- data/lib/omnizip/profile/balanced_profile.rb +33 -0
- data/lib/omnizip/profile/binary_profile.rb +36 -0
- data/lib/omnizip/profile/compression_profile.rb +158 -0
- data/lib/omnizip/profile/custom_profile.rb +157 -0
- data/lib/omnizip/profile/fast_profile.rb +33 -0
- data/lib/omnizip/profile/maximum_profile.rb +33 -0
- data/lib/omnizip/profile/profile_detector.rb +110 -0
- data/lib/omnizip/profile/profile_registry.rb +161 -0
- data/lib/omnizip/profile/text_profile.rb +36 -0
- data/lib/omnizip/profile.rb +190 -0
- data/lib/omnizip/profiler/memory_profiler.rb +66 -0
- data/lib/omnizip/profiler/method_profiler.rb +49 -0
- data/lib/omnizip/profiler/report_generator.rb +169 -0
- data/lib/omnizip/profiler.rb +204 -0
- data/lib/omnizip/progress/callback_reporter.rb +36 -0
- data/lib/omnizip/progress/console_reporter.rb +62 -0
- data/lib/omnizip/progress/log_reporter.rb +91 -0
- data/lib/omnizip/progress/operation_progress.rb +118 -0
- data/lib/omnizip/progress/progress_bar.rb +156 -0
- data/lib/omnizip/progress/progress_reporter.rb +40 -0
- data/lib/omnizip/progress/progress_tracker.rb +190 -0
- data/lib/omnizip/progress/silent_reporter.rb +24 -0
- data/lib/omnizip/progress.rb +127 -0
- data/lib/omnizip/rubyzip_compat.rb +63 -0
- data/lib/omnizip/temp/safe_extract.rb +168 -0
- data/lib/omnizip/temp/temp_file.rb +124 -0
- data/lib/omnizip/temp/temp_file_pool.rb +109 -0
- data/lib/omnizip/temp.rb +181 -0
- data/lib/omnizip/version.rb +5 -0
- data/lib/omnizip/zip/entry.rb +156 -0
- data/lib/omnizip/zip/file.rb +485 -0
- data/lib/omnizip/zip/input_stream.rb +273 -0
- data/lib/omnizip/zip/output_stream.rb +324 -0
- data/lib/omnizip.rb +156 -0
- data/readme-docs/advanced-features.adoc +515 -0
- data/readme-docs/api-usage.adoc +444 -0
- data/readme-docs/architecture.adoc +449 -0
- data/readme-docs/archive-formats.adoc +479 -0
- data/readme-docs/cli-usage.adoc +222 -0
- data/readme-docs/compression-algorithms.adoc +442 -0
- data/readme-docs/compression-profiles.adoc +247 -0
- data/readme-docs/encryption-checksums.adoc +328 -0
- data/readme-docs/format-converter.adoc +325 -0
- data/readme-docs/installation.adoc +228 -0
- data/readme-docs/par2-archives.adoc +608 -0
- data/readme-docs/performance-profiler.adoc +389 -0
- data/readme-docs/preprocessing-filters.adoc +280 -0
- data/xz-file-format-1.2.1.txt +1174 -0
- metadata +617 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "entry"
|
|
4
|
+
require_relative "../formats/zip/constants"
|
|
5
|
+
require_relative "../formats/zip/local_file_header"
|
|
6
|
+
|
|
7
|
+
module Omnizip
|
|
8
|
+
module Zip
|
|
9
|
+
# Rubyzip-compatible InputStream class
|
|
10
|
+
# Provides streaming read API for ZIP archives
|
|
11
|
+
class InputStream
|
|
12
|
+
include Omnizip::Formats::Zip::Constants
|
|
13
|
+
|
|
14
|
+
# Open an input stream
|
|
15
|
+
# @param file_path_or_io [String, IO] File path or IO object
|
|
16
|
+
# @yield [stream] Block to read from the stream
|
|
17
|
+
# @return [InputStream] The stream (if no block given)
|
|
18
|
+
def self.open(file_path_or_io, &block)
|
|
19
|
+
stream = new(file_path_or_io)
|
|
20
|
+
|
|
21
|
+
if block
|
|
22
|
+
begin
|
|
23
|
+
yield(stream)
|
|
24
|
+
ensure
|
|
25
|
+
stream.close
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
stream
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Initialize input stream
|
|
33
|
+
# @param file_path_or_io [String, IO] File path or IO object
|
|
34
|
+
def initialize(file_path_or_io)
|
|
35
|
+
if file_path_or_io.is_a?(String)
|
|
36
|
+
@file_path = file_path_or_io
|
|
37
|
+
@io = ::File.open(file_path_or_io, "rb")
|
|
38
|
+
@owns_io = true
|
|
39
|
+
else
|
|
40
|
+
@io = file_path_or_io
|
|
41
|
+
@owns_io = false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@current_entry = nil
|
|
45
|
+
@current_entry_io = nil
|
|
46
|
+
@current_index = 0
|
|
47
|
+
@closed = false
|
|
48
|
+
@hit_nil = false
|
|
49
|
+
|
|
50
|
+
# Find and parse central directory for efficient access
|
|
51
|
+
parse_central_directory
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get next entry in the archive
|
|
55
|
+
# @return [Entry, nil] Next entry or nil if no more entries
|
|
56
|
+
def get_next_entry
|
|
57
|
+
# Check if we can fetch another entry
|
|
58
|
+
if @current_index >= @all_entries.size
|
|
59
|
+
# Mark that get_next_entry returned nil
|
|
60
|
+
@hit_nil = true
|
|
61
|
+
return nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Close previous entry data if open
|
|
65
|
+
@current_entry_io = nil
|
|
66
|
+
|
|
67
|
+
# Get next entry from our parsed list
|
|
68
|
+
header = @all_entries[@current_index]
|
|
69
|
+
@current_index += 1
|
|
70
|
+
|
|
71
|
+
# Successfully got an entry - clear the nil flag
|
|
72
|
+
@hit_nil = false
|
|
73
|
+
|
|
74
|
+
# Position at entry data
|
|
75
|
+
position_at_entry_data(header)
|
|
76
|
+
|
|
77
|
+
@current_entry = Entry.new(header)
|
|
78
|
+
@current_entry
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Read from current entry
|
|
82
|
+
# @param size [Integer, nil] Number of bytes to read (nil for all)
|
|
83
|
+
# @return [String, nil] Data read or nil if no current entry
|
|
84
|
+
def read(size = nil)
|
|
85
|
+
return nil unless @current_entry
|
|
86
|
+
|
|
87
|
+
# Initialize entry IO if needed
|
|
88
|
+
unless @current_entry_io
|
|
89
|
+
compressed_data = @io.read(@current_entry.compressed_size)
|
|
90
|
+
decompressed = decompress_data(
|
|
91
|
+
compressed_data,
|
|
92
|
+
@current_entry.compression_method,
|
|
93
|
+
@current_entry.size,
|
|
94
|
+
)
|
|
95
|
+
require "stringio"
|
|
96
|
+
@current_entry_io = StringIO.new(decompressed, "rb")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
@current_entry_io.read(size)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Rewind the stream
|
|
103
|
+
def rewind
|
|
104
|
+
@current_index = 0
|
|
105
|
+
@current_entry = nil
|
|
106
|
+
@current_entry_io = nil
|
|
107
|
+
@hit_nil = false
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Close the stream
|
|
111
|
+
def close
|
|
112
|
+
return if @closed
|
|
113
|
+
|
|
114
|
+
@current_entry_io = nil
|
|
115
|
+
@io.close if @owns_io
|
|
116
|
+
@closed = true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Check if stream is closed
|
|
120
|
+
def closed?
|
|
121
|
+
@closed
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Check if at end of file
|
|
125
|
+
# Returns true if the NEXT call to get_next_entry would return nil
|
|
126
|
+
def eof?
|
|
127
|
+
@current_index >= @all_entries.size
|
|
128
|
+
end
|
|
129
|
+
alias_method :eof, :eof?
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
# Parse central directory to get all entries
|
|
134
|
+
def parse_central_directory
|
|
135
|
+
# Find End of Central Directory
|
|
136
|
+
eocd = find_eocd
|
|
137
|
+
|
|
138
|
+
# Read central directory entries
|
|
139
|
+
@io.seek(eocd[:central_directory_offset], ::IO::SEEK_SET)
|
|
140
|
+
@all_entries = []
|
|
141
|
+
|
|
142
|
+
eocd[:total_entries].times do
|
|
143
|
+
header_data = @io.read(46)
|
|
144
|
+
break unless header_data && header_data.size == 46
|
|
145
|
+
|
|
146
|
+
# Get dynamic field lengths
|
|
147
|
+
_, _, _, _, _, _, _, _, _, _,
|
|
148
|
+
filename_length, extra_field_length, comment_length = header_data.unpack("VvvvvvvVVVvvv")
|
|
149
|
+
|
|
150
|
+
# Read complete header
|
|
151
|
+
complete_data = header_data + @io.read(filename_length + extra_field_length + comment_length)
|
|
152
|
+
entry = Omnizip::Formats::Zip::CentralDirectoryHeader.from_binary(complete_data)
|
|
153
|
+
|
|
154
|
+
@all_entries << entry
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Find End of Central Directory record
|
|
159
|
+
def find_eocd
|
|
160
|
+
# Start from end of file
|
|
161
|
+
@io.seek(0, ::IO::SEEK_END)
|
|
162
|
+
file_size = @io.pos
|
|
163
|
+
|
|
164
|
+
# EOCD is at least 22 bytes
|
|
165
|
+
return nil if file_size < 22
|
|
166
|
+
|
|
167
|
+
# Search backwards for EOCD signature
|
|
168
|
+
# Maximum comment length is 65535, so search up to 65557 bytes from end
|
|
169
|
+
search_start = [file_size - 65557, 0].max
|
|
170
|
+
@io.seek(search_start, ::IO::SEEK_SET)
|
|
171
|
+
buffer = @io.read
|
|
172
|
+
|
|
173
|
+
# Find signature
|
|
174
|
+
signature = [END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack("V")
|
|
175
|
+
pos = buffer.rindex(signature)
|
|
176
|
+
|
|
177
|
+
unless pos
|
|
178
|
+
raise Omnizip::FormatError,
|
|
179
|
+
"End of Central Directory not found"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Parse EOCD
|
|
183
|
+
eocd_data = buffer[pos..]
|
|
184
|
+
|
|
185
|
+
sig, disk_num, disk_with_cd, entries_this_disk, total_entries,
|
|
186
|
+
cd_size, cd_offset, comment_length = eocd_data.unpack("VvvvvVVv")
|
|
187
|
+
|
|
188
|
+
{
|
|
189
|
+
signature: sig,
|
|
190
|
+
disk_number: disk_num,
|
|
191
|
+
disk_number_with_cd: disk_with_cd,
|
|
192
|
+
total_entries_this_disk: entries_this_disk,
|
|
193
|
+
total_entries: total_entries,
|
|
194
|
+
central_directory_size: cd_size,
|
|
195
|
+
central_directory_offset: cd_offset,
|
|
196
|
+
comment_length: comment_length,
|
|
197
|
+
comment: eocd_data[22, comment_length] || "",
|
|
198
|
+
}
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Position IO at entry's data (after local file header)
|
|
202
|
+
def position_at_entry_data(header)
|
|
203
|
+
# Seek to local file header
|
|
204
|
+
@io.seek(header.local_header_offset, ::IO::SEEK_SET)
|
|
205
|
+
|
|
206
|
+
# Read fixed part of local file header
|
|
207
|
+
fixed_header = @io.read(30)
|
|
208
|
+
|
|
209
|
+
# Extract variable lengths
|
|
210
|
+
_, _, _, _, _, _, _, _, _, filename_length, extra_length =
|
|
211
|
+
fixed_header.unpack("VvvvvvVVVvv")
|
|
212
|
+
|
|
213
|
+
# Skip variable parts to get to data
|
|
214
|
+
@io.read(filename_length + extra_length)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Decompress data based on compression method
|
|
218
|
+
def decompress_data(compressed_data, method, uncompressed_size)
|
|
219
|
+
case method
|
|
220
|
+
when COMPRESSION_STORE
|
|
221
|
+
compressed_data
|
|
222
|
+
when COMPRESSION_DEFLATE
|
|
223
|
+
decompress_deflate(compressed_data)
|
|
224
|
+
when COMPRESSION_BZIP2
|
|
225
|
+
decompress_bzip2(compressed_data)
|
|
226
|
+
when COMPRESSION_LZMA
|
|
227
|
+
decompress_lzma(compressed_data, uncompressed_size)
|
|
228
|
+
when COMPRESSION_ZSTANDARD
|
|
229
|
+
decompress_zstandard(compressed_data)
|
|
230
|
+
else
|
|
231
|
+
raise Omnizip::UnsupportedFormatError,
|
|
232
|
+
"Unsupported compression method: #{method}"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Decompress using Deflate
|
|
237
|
+
def decompress_deflate(data)
|
|
238
|
+
require "zlib"
|
|
239
|
+
Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(data)
|
|
240
|
+
rescue StandardError => e
|
|
241
|
+
raise Omnizip::DecompressionError,
|
|
242
|
+
"Deflate decompression failed: #{e.message}"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Decompress using BZip2
|
|
246
|
+
def decompress_bzip2(data)
|
|
247
|
+
algorithm = Omnizip::AlgorithmRegistry.get(:bzip2)
|
|
248
|
+
algorithm.decompress(data)
|
|
249
|
+
rescue StandardError => e
|
|
250
|
+
raise Omnizip::DecompressionError,
|
|
251
|
+
"BZip2 decompression failed: #{e.message}"
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Decompress using LZMA
|
|
255
|
+
def decompress_lzma(data, uncompressed_size)
|
|
256
|
+
algorithm = Omnizip::AlgorithmRegistry.get(:lzma)
|
|
257
|
+
algorithm.decompress(data, uncompressed_size: uncompressed_size)
|
|
258
|
+
rescue StandardError => e
|
|
259
|
+
raise Omnizip::DecompressionError,
|
|
260
|
+
"LZMA decompression failed: #{e.message}"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Decompress using Zstandard
|
|
264
|
+
def decompress_zstandard(data)
|
|
265
|
+
algorithm = Omnizip::AlgorithmRegistry.get(:zstandard)
|
|
266
|
+
algorithm.decompress(data)
|
|
267
|
+
rescue StandardError => e
|
|
268
|
+
raise Omnizip::DecompressionError,
|
|
269
|
+
"Zstandard decompression failed: #{e.message}"
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "../formats/zip/constants"
|
|
5
|
+
require_relative "../formats/zip/local_file_header"
|
|
6
|
+
require_relative "../formats/zip/central_directory_header"
|
|
7
|
+
require_relative "../formats/zip/end_of_central_directory"
|
|
8
|
+
|
|
9
|
+
module Omnizip
|
|
10
|
+
module Zip
|
|
11
|
+
# Rubyzip-compatible OutputStream class
|
|
12
|
+
# Provides streaming write API for ZIP archives
|
|
13
|
+
class OutputStream
|
|
14
|
+
include Omnizip::Formats::Zip::Constants
|
|
15
|
+
|
|
16
|
+
# Open a new output stream
|
|
17
|
+
# @param file_path [String] Path to ZIP file
|
|
18
|
+
# @yield [stream] Block to write to the stream
|
|
19
|
+
# @return [OutputStream] The stream (if no block given)
|
|
20
|
+
def self.open(file_path, &block)
|
|
21
|
+
stream = new(file_path)
|
|
22
|
+
|
|
23
|
+
if block
|
|
24
|
+
begin
|
|
25
|
+
yield(stream)
|
|
26
|
+
ensure
|
|
27
|
+
stream.close
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
stream
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Initialize output stream
|
|
35
|
+
# @param file_path_or_io [String, IO] File path or IO object
|
|
36
|
+
def initialize(file_path_or_io)
|
|
37
|
+
if file_path_or_io.is_a?(String)
|
|
38
|
+
@file_path = file_path_or_io
|
|
39
|
+
@io = ::File.open(file_path_or_io, "wb")
|
|
40
|
+
@owns_io = true
|
|
41
|
+
else
|
|
42
|
+
@io = file_path_or_io
|
|
43
|
+
@owns_io = false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@entries = []
|
|
47
|
+
@current_entry = nil
|
|
48
|
+
@current_entry_data = nil
|
|
49
|
+
@closed = false
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Start a new entry
|
|
53
|
+
# @param name [String] Entry name
|
|
54
|
+
# @param time [Time] Modification time
|
|
55
|
+
# @param comment [String] Entry comment
|
|
56
|
+
# @param compression [Symbol] Compression method (:store or :deflate)
|
|
57
|
+
# @param level [Integer] Compression level (1-9)
|
|
58
|
+
def put_next_entry(name, time: Time.now, comment: "",
|
|
59
|
+
compression: :deflate, level: 6)
|
|
60
|
+
close_entry if @current_entry
|
|
61
|
+
|
|
62
|
+
@current_entry = {
|
|
63
|
+
name: name,
|
|
64
|
+
time: time,
|
|
65
|
+
comment: comment,
|
|
66
|
+
compression: compression_method_for(compression),
|
|
67
|
+
level: level,
|
|
68
|
+
offset: @io.pos,
|
|
69
|
+
directory: name.end_with?("/"),
|
|
70
|
+
}
|
|
71
|
+
@current_entry_data = String.new(encoding: Encoding::BINARY)
|
|
72
|
+
|
|
73
|
+
# Write placeholder local file header (will update later)
|
|
74
|
+
write_local_file_header_placeholder
|
|
75
|
+
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Write data to current entry
|
|
80
|
+
# @param data [String] Data to write
|
|
81
|
+
def write(data)
|
|
82
|
+
raise "No entry started. Call put_next_entry first" unless @current_entry
|
|
83
|
+
|
|
84
|
+
@current_entry_data << data.b
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
alias_method :<<, :write
|
|
88
|
+
|
|
89
|
+
# Print data to current entry
|
|
90
|
+
def print(*args)
|
|
91
|
+
write(args.join)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Put data to current entry
|
|
95
|
+
def puts(*args)
|
|
96
|
+
args.each { |arg| write("#{arg}\n") }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Close current entry
|
|
100
|
+
def close_entry
|
|
101
|
+
return unless @current_entry
|
|
102
|
+
|
|
103
|
+
# Compress data if needed
|
|
104
|
+
compressed_data = compress_entry_data
|
|
105
|
+
|
|
106
|
+
# Calculate CRC32
|
|
107
|
+
crc32 = @current_entry[:directory] ? 0 : calculate_crc32(@current_entry_data)
|
|
108
|
+
|
|
109
|
+
# Write compressed data
|
|
110
|
+
@io.write(compressed_data) unless @current_entry[:directory]
|
|
111
|
+
|
|
112
|
+
# Update entry info
|
|
113
|
+
@current_entry.merge!({
|
|
114
|
+
crc32: crc32,
|
|
115
|
+
compressed_size: compressed_data.bytesize,
|
|
116
|
+
uncompressed_size: @current_entry_data.bytesize,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
# Update local file header with correct sizes
|
|
120
|
+
current_pos = @io.pos
|
|
121
|
+
@io.seek(@current_entry[:offset], ::IO::SEEK_SET)
|
|
122
|
+
write_local_file_header
|
|
123
|
+
@io.seek(current_pos, ::IO::SEEK_SET)
|
|
124
|
+
|
|
125
|
+
# Save entry for central directory
|
|
126
|
+
@entries << @current_entry
|
|
127
|
+
|
|
128
|
+
@current_entry = nil
|
|
129
|
+
@current_entry_data = nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Set archive comment
|
|
133
|
+
def comment=(comment)
|
|
134
|
+
@comment = comment
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Get archive comment
|
|
138
|
+
def comment
|
|
139
|
+
@comment || ""
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Close the stream
|
|
143
|
+
def close
|
|
144
|
+
return if @closed
|
|
145
|
+
|
|
146
|
+
close_entry if @current_entry
|
|
147
|
+
|
|
148
|
+
# Write central directory
|
|
149
|
+
central_directory_offset = @io.pos
|
|
150
|
+
write_central_directory
|
|
151
|
+
central_directory_size = @io.pos - central_directory_offset
|
|
152
|
+
|
|
153
|
+
# Write end of central directory
|
|
154
|
+
write_end_of_central_directory(central_directory_offset,
|
|
155
|
+
central_directory_size)
|
|
156
|
+
|
|
157
|
+
@io.close if @owns_io
|
|
158
|
+
@closed = true
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Check if stream is closed
|
|
162
|
+
def closed?
|
|
163
|
+
@closed
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
# Get compression method code
|
|
169
|
+
def compression_method_for(symbol)
|
|
170
|
+
case symbol
|
|
171
|
+
when :store, :none then COMPRESSION_STORE
|
|
172
|
+
when :deflate then COMPRESSION_DEFLATE
|
|
173
|
+
when :bzip2 then COMPRESSION_BZIP2
|
|
174
|
+
when :lzma then COMPRESSION_LZMA
|
|
175
|
+
when :zstandard then COMPRESSION_ZSTANDARD
|
|
176
|
+
else COMPRESSION_DEFLATE
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Write local file header placeholder
|
|
181
|
+
def write_local_file_header_placeholder
|
|
182
|
+
# Write zeros for now, will update after compression
|
|
183
|
+
@io.write("\x00" * 30) # Fixed part
|
|
184
|
+
@io.write(@current_entry[:name].b) # Filename
|
|
185
|
+
@current_entry[:filename_length] = @current_entry[:name].bytesize
|
|
186
|
+
@current_entry[:extra_field_length] = 0
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Write actual local file header
|
|
190
|
+
def write_local_file_header
|
|
191
|
+
header = Omnizip::Formats::Zip::LocalFileHeader.new(
|
|
192
|
+
version_needed: version_for_method(@current_entry[:compression]),
|
|
193
|
+
flags: FLAG_UTF8,
|
|
194
|
+
compression_method: @current_entry[:directory] ? COMPRESSION_STORE : @current_entry[:compression],
|
|
195
|
+
last_mod_time: dos_time(@current_entry[:time]),
|
|
196
|
+
last_mod_date: dos_date(@current_entry[:time]),
|
|
197
|
+
crc32: @current_entry[:crc32],
|
|
198
|
+
compressed_size: @current_entry[:compressed_size],
|
|
199
|
+
uncompressed_size: @current_entry[:uncompressed_size],
|
|
200
|
+
filename: @current_entry[:name],
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
@io.write(header.to_binary)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Compress current entry data
|
|
207
|
+
def compress_entry_data
|
|
208
|
+
return "" if @current_entry[:directory]
|
|
209
|
+
|
|
210
|
+
case @current_entry[:compression]
|
|
211
|
+
when COMPRESSION_STORE
|
|
212
|
+
@current_entry_data
|
|
213
|
+
when COMPRESSION_DEFLATE
|
|
214
|
+
compress_deflate(@current_entry_data, @current_entry[:level])
|
|
215
|
+
when COMPRESSION_BZIP2
|
|
216
|
+
compress_bzip2(@current_entry_data, @current_entry[:level])
|
|
217
|
+
when COMPRESSION_LZMA
|
|
218
|
+
compress_lzma(@current_entry_data, @current_entry[:level])
|
|
219
|
+
when COMPRESSION_ZSTANDARD
|
|
220
|
+
compress_zstandard(@current_entry_data, @current_entry[:level])
|
|
221
|
+
else
|
|
222
|
+
@current_entry_data
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Write central directory
|
|
227
|
+
def write_central_directory
|
|
228
|
+
@entries.each do |entry|
|
|
229
|
+
external_attrs = if entry[:directory]
|
|
230
|
+
UNIX_DIR_PERMISSIONS | ATTR_DIRECTORY
|
|
231
|
+
else
|
|
232
|
+
UNIX_FILE_PERMISSIONS
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
header = Omnizip::Formats::Zip::CentralDirectoryHeader.new(
|
|
236
|
+
version_made_by: VERSION_MADE_BY_UNIX | version_for_method(entry[:compression]),
|
|
237
|
+
version_needed: version_for_method(entry[:compression]),
|
|
238
|
+
flags: FLAG_UTF8,
|
|
239
|
+
compression_method: entry[:directory] ? COMPRESSION_STORE : entry[:compression],
|
|
240
|
+
last_mod_time: dos_time(entry[:time]),
|
|
241
|
+
last_mod_date: dos_date(entry[:time]),
|
|
242
|
+
crc32: entry[:crc32],
|
|
243
|
+
compressed_size: entry[:compressed_size],
|
|
244
|
+
uncompressed_size: entry[:uncompressed_size],
|
|
245
|
+
disk_number_start: 0,
|
|
246
|
+
internal_attributes: 0,
|
|
247
|
+
external_attributes: external_attrs,
|
|
248
|
+
local_header_offset: entry[:offset],
|
|
249
|
+
filename: entry[:name],
|
|
250
|
+
extra_field: "",
|
|
251
|
+
comment: entry[:comment] || "",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
@io.write(header.to_binary)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Write end of central directory
|
|
259
|
+
def write_end_of_central_directory(offset, size)
|
|
260
|
+
eocd = Omnizip::Formats::Zip::EndOfCentralDirectory.new(
|
|
261
|
+
disk_number: 0,
|
|
262
|
+
disk_number_with_cd: 0,
|
|
263
|
+
total_entries_this_disk: @entries.size,
|
|
264
|
+
total_entries: @entries.size,
|
|
265
|
+
central_directory_size: size,
|
|
266
|
+
central_directory_offset: offset,
|
|
267
|
+
comment: comment,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
@io.write(eocd.to_binary)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Compress using Deflate
|
|
274
|
+
def compress_deflate(data, level)
|
|
275
|
+
require "zlib"
|
|
276
|
+
Zlib::Deflate.new(level, -Zlib::MAX_WBITS).deflate(data, Zlib::FINISH)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Compress using BZip2
|
|
280
|
+
def compress_bzip2(data, level)
|
|
281
|
+
algorithm = Omnizip::AlgorithmRegistry.get(:bzip2)
|
|
282
|
+
algorithm.compress(data, level: level)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Compress using LZMA
|
|
286
|
+
def compress_lzma(data, level)
|
|
287
|
+
algorithm = Omnizip::AlgorithmRegistry.get(:lzma)
|
|
288
|
+
algorithm.compress(data, level: level)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Compress using Zstandard
|
|
292
|
+
def compress_zstandard(data, level)
|
|
293
|
+
algorithm = Omnizip::AlgorithmRegistry.get(:zstandard)
|
|
294
|
+
algorithm.compress(data, level: level)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Calculate CRC32
|
|
298
|
+
def calculate_crc32(data)
|
|
299
|
+
Omnizip::Checksums::Crc32.new.tap { |c| c.update(data) }.finalize
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Get version needed for compression method
|
|
303
|
+
def version_for_method(method)
|
|
304
|
+
case method
|
|
305
|
+
when COMPRESSION_STORE then VERSION_DEFAULT
|
|
306
|
+
when COMPRESSION_DEFLATE then VERSION_DEFLATE
|
|
307
|
+
when COMPRESSION_BZIP2 then VERSION_BZIP2
|
|
308
|
+
when COMPRESSION_LZMA then VERSION_LZMA
|
|
309
|
+
else VERSION_DEFAULT
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Convert Time to DOS time
|
|
314
|
+
def dos_time(time)
|
|
315
|
+
((time.hour << 11) | (time.min << 5) | (time.sec / 2)) & 0xFFFF
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Convert Time to DOS date
|
|
319
|
+
def dos_date(time)
|
|
320
|
+
(((time.year - 1980) << 9) | (time.month << 5) | time.day) & 0xFFFF
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|