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,366 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
require_relative "header"
|
|
5
|
+
require_relative "block_parser"
|
|
6
|
+
require_relative "decompressor"
|
|
7
|
+
require_relative "volume_manager"
|
|
8
|
+
require_relative "recovery_record"
|
|
9
|
+
require_relative "models/rar_entry"
|
|
10
|
+
require_relative "models/rar_archive"
|
|
11
|
+
require_relative "compression/dispatcher"
|
|
12
|
+
require "fileutils"
|
|
13
|
+
|
|
14
|
+
module Omnizip
|
|
15
|
+
module Formats
|
|
16
|
+
module Rar
|
|
17
|
+
# RAR archive reader
|
|
18
|
+
# Provides read-only access to RAR archives (single and multi-volume)
|
|
19
|
+
class Reader
|
|
20
|
+
include Constants
|
|
21
|
+
|
|
22
|
+
attr_reader :file_path, :header, :entries, :archive_info,
|
|
23
|
+
:volume_manager
|
|
24
|
+
|
|
25
|
+
# Initialize reader with file path
|
|
26
|
+
#
|
|
27
|
+
# @param file_path [String] Path to RAR file
|
|
28
|
+
def initialize(file_path)
|
|
29
|
+
@file_path = file_path
|
|
30
|
+
@header = nil
|
|
31
|
+
@entries = []
|
|
32
|
+
@archive_info = Models::RarArchive.new(file_path)
|
|
33
|
+
@volume_manager = VolumeManager.new(file_path)
|
|
34
|
+
@use_native = true # Prefer native decompression
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Open and parse RAR archive
|
|
38
|
+
#
|
|
39
|
+
# @raise [RuntimeError] if file cannot be opened or parsed
|
|
40
|
+
def open
|
|
41
|
+
File.open(@file_path, "rb") do |io|
|
|
42
|
+
parse_archive(io)
|
|
43
|
+
end
|
|
44
|
+
self
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# List all files in archive
|
|
48
|
+
#
|
|
49
|
+
# @return [Array<Models::RarEntry>] File entries
|
|
50
|
+
def list_files
|
|
51
|
+
@entries
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Extract file to output path
|
|
55
|
+
#
|
|
56
|
+
# @param entry_name [String] File name to extract
|
|
57
|
+
# @param output_path [String] Destination path
|
|
58
|
+
# @param password [String, nil] Optional password
|
|
59
|
+
# @raise [RuntimeError] if entry not found or extraction fails
|
|
60
|
+
def extract_entry(entry_name, output_path, password: nil)
|
|
61
|
+
entry = @entries.find { |e| e.name == entry_name }
|
|
62
|
+
raise "Entry not found: #{entry_name}" unless entry
|
|
63
|
+
|
|
64
|
+
# Create directory if needed
|
|
65
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
66
|
+
|
|
67
|
+
# Extract file
|
|
68
|
+
if entry.directory?
|
|
69
|
+
FileUtils.mkdir_p(output_path)
|
|
70
|
+
else
|
|
71
|
+
# Try native decompression first, fall back to external
|
|
72
|
+
if @use_native && native_decompression_available?(entry)
|
|
73
|
+
extract_entry_native(entry, output_path)
|
|
74
|
+
else
|
|
75
|
+
extract_entry_external(entry_name, output_path, password)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Set timestamp if available
|
|
79
|
+
File.utime(entry.mtime, entry.mtime, output_path) if entry.mtime
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Extract all files to directory
|
|
84
|
+
#
|
|
85
|
+
# @param output_dir [String] Destination directory
|
|
86
|
+
# @param password [String, nil] Optional password
|
|
87
|
+
# @raise [RuntimeError] on extraction error
|
|
88
|
+
def extract_all(output_dir, password: nil)
|
|
89
|
+
FileUtils.mkdir_p(output_dir)
|
|
90
|
+
|
|
91
|
+
# Use decompressor to extract all
|
|
92
|
+
base_path = @volume_manager.first_volume&.path || @file_path
|
|
93
|
+
Decompressor.extract(base_path, output_dir, password: password)
|
|
94
|
+
|
|
95
|
+
# Set timestamps for extracted files
|
|
96
|
+
@entries.each do |entry|
|
|
97
|
+
next unless entry.mtime
|
|
98
|
+
|
|
99
|
+
output_path = File.join(output_dir, entry.name)
|
|
100
|
+
next unless File.exist?(output_path)
|
|
101
|
+
|
|
102
|
+
File.utime(entry.mtime, entry.mtime, output_path)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Check if archive is valid RAR format
|
|
107
|
+
#
|
|
108
|
+
# @return [Boolean] true if valid
|
|
109
|
+
def valid?
|
|
110
|
+
!@header.nil? && @header.valid?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Get volumes in multi-volume archive
|
|
114
|
+
#
|
|
115
|
+
# @return [Array<String>] Volume paths
|
|
116
|
+
def volumes
|
|
117
|
+
@volume_manager.volume_paths
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Get total number of volumes
|
|
121
|
+
#
|
|
122
|
+
# @return [Integer] Number of volumes
|
|
123
|
+
def total_volumes
|
|
124
|
+
@volume_manager.volume_count
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Check if multi-volume archive
|
|
128
|
+
#
|
|
129
|
+
# @return [Boolean] true if multi-volume
|
|
130
|
+
def multi_volume?
|
|
131
|
+
@volume_manager.multi_volume? || @header&.is_multi_volume
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
# Parse RAR archive structure
|
|
137
|
+
#
|
|
138
|
+
# @param io [IO] Input stream
|
|
139
|
+
def parse_archive(io)
|
|
140
|
+
# Read and validate header
|
|
141
|
+
@header = Header.read(io)
|
|
142
|
+
raise "Invalid RAR archive" unless @header.valid?
|
|
143
|
+
|
|
144
|
+
# Update archive info
|
|
145
|
+
@archive_info.version = @header.version
|
|
146
|
+
@archive_info.flags = @header.flags
|
|
147
|
+
@archive_info.is_multi_volume = @header.is_multi_volume
|
|
148
|
+
@archive_info.volumes = @volume_manager.volumes
|
|
149
|
+
|
|
150
|
+
# Detect recovery records
|
|
151
|
+
detect_recovery_records
|
|
152
|
+
|
|
153
|
+
# Parse entries using decompressor
|
|
154
|
+
parse_entries_with_decompressor
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Detect recovery records in archive
|
|
158
|
+
def detect_recovery_records
|
|
159
|
+
recovery = RecoveryRecord.new(@header.version)
|
|
160
|
+
|
|
161
|
+
# Check for integrated recovery
|
|
162
|
+
File.open(@file_path, "rb") do |io|
|
|
163
|
+
recovery.parse_from_archive(io, @header.flags)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Check for external .rev files
|
|
167
|
+
rev_files = recovery.detect_external_files(@file_path)
|
|
168
|
+
recovery.load_external_files(rev_files) if rev_files.any?
|
|
169
|
+
|
|
170
|
+
# Update archive info
|
|
171
|
+
@archive_info.has_recovery = recovery.available?
|
|
172
|
+
@archive_info.recovery_percent = recovery.protection_percent
|
|
173
|
+
@archive_info.recovery_files = recovery.external_files
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Parse entries using decompressor
|
|
177
|
+
#
|
|
178
|
+
# This uses the external decompressor to list archive contents
|
|
179
|
+
# Falls back to native parser if decompressor fails or unavailable
|
|
180
|
+
def parse_entries_with_decompressor
|
|
181
|
+
unless Decompressor.available?
|
|
182
|
+
# If decompressor not available, create minimal entries from header
|
|
183
|
+
parse_entries_from_header
|
|
184
|
+
return
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Try to list archive contents with external tool
|
|
188
|
+
begin
|
|
189
|
+
entry_info = Decompressor.list(@file_path)
|
|
190
|
+
entry_info.each do |info|
|
|
191
|
+
entry = Models::RarEntry.new
|
|
192
|
+
entry.name = info[:name]
|
|
193
|
+
entry.size = info[:size]
|
|
194
|
+
entry.compressed_size = info[:compressed_size]
|
|
195
|
+
entry.is_dir = info[:is_dir]
|
|
196
|
+
entry.mtime = info[:mtime]
|
|
197
|
+
entry.version = @header.version
|
|
198
|
+
|
|
199
|
+
@entries << entry
|
|
200
|
+
@archive_info.total_size += entry.size
|
|
201
|
+
@archive_info.compressed_size += entry.compressed_size
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
@archive_info.entries = @entries
|
|
205
|
+
rescue StandardError => e
|
|
206
|
+
# Fall back to native parser if external decompressor fails
|
|
207
|
+
warn "External decompressor failed: #{e.message}"
|
|
208
|
+
warn "Falling back to native block parser"
|
|
209
|
+
parse_entries_from_header
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Parse entries from header (fallback when decompressor unavailable)
|
|
214
|
+
#
|
|
215
|
+
# This provides basic information but cannot extract files
|
|
216
|
+
def parse_entries_from_header
|
|
217
|
+
# Reset to after header
|
|
218
|
+
File.open(@file_path, "rb") do |io|
|
|
219
|
+
Header.read(io) # Skip header
|
|
220
|
+
|
|
221
|
+
# Try to parse file blocks
|
|
222
|
+
parser = BlockParser.new(@header.version)
|
|
223
|
+
|
|
224
|
+
loop do
|
|
225
|
+
pos = io.pos
|
|
226
|
+
break if io.eof?
|
|
227
|
+
|
|
228
|
+
begin
|
|
229
|
+
entry = parser.parse_file_block(io)
|
|
230
|
+
break unless entry
|
|
231
|
+
|
|
232
|
+
@entries << entry
|
|
233
|
+
@archive_info.total_size += entry.size
|
|
234
|
+
@archive_info.compressed_size += entry.compressed_size
|
|
235
|
+
rescue StandardError => e
|
|
236
|
+
warn "Failed to parse block at #{pos}: #{e.message}"
|
|
237
|
+
break
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
@archive_info.entries = @entries
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Check if native decompression is available for entry
|
|
246
|
+
#
|
|
247
|
+
# @param entry [Models::RarEntry] File entry
|
|
248
|
+
# @return [Boolean] true if native decompression supported
|
|
249
|
+
def native_decompression_available?(entry)
|
|
250
|
+
# Native decompression only for RAR4 for now
|
|
251
|
+
return false unless @header.version == 4
|
|
252
|
+
|
|
253
|
+
# Check if we have the compression method
|
|
254
|
+
return false unless entry.respond_to?(:method) && entry.method
|
|
255
|
+
|
|
256
|
+
# All RAR4 methods are supported by our Dispatcher
|
|
257
|
+
true
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Extract entry using native decompression
|
|
261
|
+
#
|
|
262
|
+
# @param entry [Models::RarEntry] File entry
|
|
263
|
+
# @param output_path [String] Destination path
|
|
264
|
+
def extract_entry_native(entry, output_path)
|
|
265
|
+
# Read compressed data from archive
|
|
266
|
+
compressed_data = read_compressed_data(entry)
|
|
267
|
+
|
|
268
|
+
# Decompress using Dispatcher
|
|
269
|
+
File.open(output_path, "wb") do |output|
|
|
270
|
+
# For now, assume METHOD_STORE (0x30)
|
|
271
|
+
# Real implementation would get method from entry
|
|
272
|
+
method = entry.respond_to?(:method) ? entry.method : 0x30
|
|
273
|
+
|
|
274
|
+
Compression::Dispatcher.decompress(method, compressed_data, output)
|
|
275
|
+
end
|
|
276
|
+
rescue StandardError => e
|
|
277
|
+
# Fall back to external decompressor on error
|
|
278
|
+
warn "Native decompression failed for #{entry.name}: #{e.message}"
|
|
279
|
+
warn "Falling back to external decompressor"
|
|
280
|
+
extract_entry_external(entry.name, output_path, nil)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Extract entry using external decompressor
|
|
284
|
+
#
|
|
285
|
+
# @param entry_name [String] Entry name
|
|
286
|
+
# @param output_path [String] Destination path
|
|
287
|
+
# @param password [String, nil] Optional password
|
|
288
|
+
def extract_entry_external(entry_name, output_path, password)
|
|
289
|
+
base_path = @volume_manager.first_volume&.path || @file_path
|
|
290
|
+
Decompressor.extract_entry(base_path, entry_name,
|
|
291
|
+
output_path, password: password)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Read compressed data for entry
|
|
295
|
+
#
|
|
296
|
+
# @param entry [Models::RarEntry] File entry
|
|
297
|
+
# @return [StringIO] Compressed data stream
|
|
298
|
+
def read_compressed_data(entry)
|
|
299
|
+
require "stringio"
|
|
300
|
+
|
|
301
|
+
# Find the entry's data offset in the archive
|
|
302
|
+
File.open(@file_path, "rb") do |io|
|
|
303
|
+
# Skip signature and headers
|
|
304
|
+
Header.read(io)
|
|
305
|
+
|
|
306
|
+
# Parse file blocks to find our entry
|
|
307
|
+
parser = BlockParser.new(@header.version)
|
|
308
|
+
|
|
309
|
+
loop do
|
|
310
|
+
block_start = io.pos
|
|
311
|
+
break if io.eof?
|
|
312
|
+
|
|
313
|
+
# Peek at block type
|
|
314
|
+
crc_bytes = io.read(2)
|
|
315
|
+
break unless crc_bytes
|
|
316
|
+
|
|
317
|
+
type_byte = io.read(1)
|
|
318
|
+
break unless type_byte
|
|
319
|
+
|
|
320
|
+
head_type = type_byte.ord
|
|
321
|
+
|
|
322
|
+
# If end block, stop
|
|
323
|
+
break if head_type == BLOCK_ENDARC
|
|
324
|
+
|
|
325
|
+
# Reset to block start
|
|
326
|
+
io.seek(block_start)
|
|
327
|
+
|
|
328
|
+
# If not a file block, read and skip it
|
|
329
|
+
unless head_type == BLOCK_FILE
|
|
330
|
+
# Read header
|
|
331
|
+
io.read(2) # CRC
|
|
332
|
+
io.read(1) # TYPE
|
|
333
|
+
io.read(2)&.unpack1("v") || 0
|
|
334
|
+
size = io.read(2)&.unpack1("v") || 0
|
|
335
|
+
|
|
336
|
+
# Skip rest of block (size includes TYPE+FLAGS+SIZE = 5 bytes)
|
|
337
|
+
remaining = size - 5
|
|
338
|
+
io.read(remaining) if remaining.positive?
|
|
339
|
+
next
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Parse this file block
|
|
343
|
+
test_entry = parser.parse_file_block(io)
|
|
344
|
+
|
|
345
|
+
# Check if this is our entry
|
|
346
|
+
if test_entry && test_entry.name == entry.name
|
|
347
|
+
# BlockParser positions us right after the compressed data
|
|
348
|
+
# So we need to back up and read it
|
|
349
|
+
data_end = io.pos
|
|
350
|
+
data_start = data_end - entry.compressed_size
|
|
351
|
+
|
|
352
|
+
io.seek(data_start)
|
|
353
|
+
compressed = io.read(entry.compressed_size)
|
|
354
|
+
|
|
355
|
+
return StringIO.new(compressed)
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# If we didn't find it, return empty
|
|
361
|
+
StringIO.new("")
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Omnizip
|
|
4
|
+
module Formats
|
|
5
|
+
module Rar
|
|
6
|
+
# RAR recovery record handling
|
|
7
|
+
# Parses and manages recovery records for error correction
|
|
8
|
+
class RecoveryRecord
|
|
9
|
+
attr_reader :version, :type, :protection_percent, :block_size,
|
|
10
|
+
:recovery_blocks, :protected_size, :recovery_size,
|
|
11
|
+
:reed_solomon_params, :external_files
|
|
12
|
+
|
|
13
|
+
# Recovery record types
|
|
14
|
+
TYPE_INTEGRATED = :integrated # Inside archive
|
|
15
|
+
TYPE_EXTERNAL = :external # Separate .rev files
|
|
16
|
+
|
|
17
|
+
# Initialize recovery record
|
|
18
|
+
#
|
|
19
|
+
# @param version [Integer] RAR version (4 or 5)
|
|
20
|
+
def initialize(version)
|
|
21
|
+
@version = version
|
|
22
|
+
@type = TYPE_INTEGRATED
|
|
23
|
+
@protection_percent = 0
|
|
24
|
+
@block_size = 0
|
|
25
|
+
@recovery_blocks = 0
|
|
26
|
+
@protected_size = 0
|
|
27
|
+
@recovery_size = 0
|
|
28
|
+
@reed_solomon_params = {}
|
|
29
|
+
@external_files = []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Check if recovery records are available
|
|
33
|
+
#
|
|
34
|
+
# @return [Boolean] true if recovery available
|
|
35
|
+
def available?
|
|
36
|
+
@recovery_blocks.positive? || @external_files.any?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Check if using external .rev files
|
|
40
|
+
#
|
|
41
|
+
# @return [Boolean] true if external recovery
|
|
42
|
+
def external?
|
|
43
|
+
@type == TYPE_EXTERNAL
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Parse recovery record from archive
|
|
47
|
+
#
|
|
48
|
+
# @param io [IO] Input stream
|
|
49
|
+
# @param flags [Integer] Archive flags
|
|
50
|
+
# @return [Boolean] true if recovery record found
|
|
51
|
+
def parse_from_archive(io, flags)
|
|
52
|
+
return false unless recovery_flag_set?(flags)
|
|
53
|
+
|
|
54
|
+
if @version == 5
|
|
55
|
+
parse_rar5_recovery(io)
|
|
56
|
+
else
|
|
57
|
+
parse_rar4_recovery(io)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Detect external .rev files
|
|
62
|
+
#
|
|
63
|
+
# @param archive_path [String] Path to main archive
|
|
64
|
+
# @return [Array<String>] Paths to .rev files
|
|
65
|
+
def detect_external_files(archive_path)
|
|
66
|
+
dir = File.dirname(archive_path)
|
|
67
|
+
basename = File.basename(archive_path, ".*")
|
|
68
|
+
|
|
69
|
+
# Check for RAR5 naming (.part01.rar.rev)
|
|
70
|
+
if archive_path.match?(/\.part\d+\.rar$/i)
|
|
71
|
+
detect_rar5_rev_files(dir, archive_path)
|
|
72
|
+
else
|
|
73
|
+
# RAR4 naming (.r00.rev, .r01.rev)
|
|
74
|
+
detect_rar4_rev_files(dir, basename)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Load external recovery files
|
|
79
|
+
#
|
|
80
|
+
# @param rev_files [Array<String>] Paths to .rev files
|
|
81
|
+
def load_external_files(rev_files)
|
|
82
|
+
@external_files = rev_files.select { |f| File.exist?(f) }
|
|
83
|
+
@type = TYPE_EXTERNAL if @external_files.any?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Get total recovery data size
|
|
87
|
+
#
|
|
88
|
+
# @return [Integer] Total recovery bytes
|
|
89
|
+
def total_recovery_size
|
|
90
|
+
if external?
|
|
91
|
+
@external_files.sum { |f| File.size(f) }
|
|
92
|
+
else
|
|
93
|
+
@recovery_size
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Calculate protection level
|
|
98
|
+
#
|
|
99
|
+
# @return [Float] Protection percentage
|
|
100
|
+
def protection_level
|
|
101
|
+
return 0.0 if @protected_size.zero?
|
|
102
|
+
|
|
103
|
+
(@recovery_size.to_f / @protected_size * 100).round(2)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
# Check if recovery flag is set in archive flags
|
|
109
|
+
#
|
|
110
|
+
# @param flags [Integer] Archive flags
|
|
111
|
+
# @return [Boolean] true if recovery flag set
|
|
112
|
+
def recovery_flag_set?(flags)
|
|
113
|
+
flags.anybits?(Constants::ARCHIVE_RECOVERY)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Parse RAR5 recovery record
|
|
117
|
+
#
|
|
118
|
+
# @param io [IO] Input stream
|
|
119
|
+
# @return [Boolean] true if parsed successfully
|
|
120
|
+
def parse_rar5_recovery(io)
|
|
121
|
+
# RAR5 recovery record structure
|
|
122
|
+
# Read block header
|
|
123
|
+
block_type = read_vint(io)
|
|
124
|
+
return false unless block_type == Constants::RAR5_HEADER_SERVICE
|
|
125
|
+
|
|
126
|
+
block_flags = read_vint(io)
|
|
127
|
+
_extra_size = read_vint(io) if block_flags.anybits?(0x0001)
|
|
128
|
+
|
|
129
|
+
# Read recovery parameters
|
|
130
|
+
@block_size = read_vint(io)
|
|
131
|
+
@recovery_blocks = read_vint(io)
|
|
132
|
+
@protected_size = read_vint(io)
|
|
133
|
+
|
|
134
|
+
# Reed-Solomon parameters for RAR5
|
|
135
|
+
@reed_solomon_params[:data_blocks] = read_vint(io)
|
|
136
|
+
@reed_solomon_params[:parity_blocks] = read_vint(io)
|
|
137
|
+
|
|
138
|
+
@recovery_size = @block_size * @recovery_blocks
|
|
139
|
+
@protection_percent = protection_level.to_i
|
|
140
|
+
|
|
141
|
+
true
|
|
142
|
+
rescue StandardError
|
|
143
|
+
false
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Parse RAR4 recovery record
|
|
147
|
+
#
|
|
148
|
+
# @param io [IO] Input stream
|
|
149
|
+
# @return [Boolean] true if parsed successfully
|
|
150
|
+
def parse_rar4_recovery(io)
|
|
151
|
+
# RAR4 old recovery record structure
|
|
152
|
+
# Skip to recovery block
|
|
153
|
+
block_type = io.read(1)&.unpack1("C")
|
|
154
|
+
return false unless block_type == Constants::BLOCK_OLD_RECOVERY
|
|
155
|
+
|
|
156
|
+
# Read block header
|
|
157
|
+
_block_flags = io.read(2)&.unpack1("v")
|
|
158
|
+
io.read(2)&.unpack1("v")
|
|
159
|
+
|
|
160
|
+
# Read recovery data size
|
|
161
|
+
@recovery_size = io.read(4)&.unpack1("V") || 0
|
|
162
|
+
@protected_size = io.read(4)&.unpack1("V") || 0
|
|
163
|
+
@recovery_blocks = io.read(2)&.unpack1("v") || 0
|
|
164
|
+
|
|
165
|
+
if @recovery_blocks.positive?
|
|
166
|
+
@block_size = @recovery_size / @recovery_blocks
|
|
167
|
+
end
|
|
168
|
+
@protection_percent = protection_level.to_i
|
|
169
|
+
|
|
170
|
+
true
|
|
171
|
+
rescue StandardError
|
|
172
|
+
false
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Detect RAR5 external .rev files
|
|
176
|
+
#
|
|
177
|
+
# @param dir [String] Directory path
|
|
178
|
+
# @param archive_path [String] Archive path
|
|
179
|
+
# @return [Array<String>] .rev file paths
|
|
180
|
+
def detect_rar5_rev_files(_dir, archive_path)
|
|
181
|
+
rev_files = []
|
|
182
|
+
|
|
183
|
+
# Check for .partNN.rar.rev files
|
|
184
|
+
if archive_path =~ /^(.+)(\.part\d+\.rar)$/i
|
|
185
|
+
base_name = Regexp.last_match(1)
|
|
186
|
+
Regexp.last_match(2)
|
|
187
|
+
|
|
188
|
+
(1..999).each do |i|
|
|
189
|
+
rev_path = format("%s.part%02d.rar.rev", base_name, i)
|
|
190
|
+
break unless File.exist?(rev_path)
|
|
191
|
+
|
|
192
|
+
rev_files << rev_path
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
rev_files
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Detect RAR4 external .rev files
|
|
200
|
+
#
|
|
201
|
+
# @param dir [String] Directory path
|
|
202
|
+
# @param basename [String] Archive base name
|
|
203
|
+
# @return [Array<String>] .rev file paths
|
|
204
|
+
def detect_rar4_rev_files(dir, basename)
|
|
205
|
+
rev_files = []
|
|
206
|
+
|
|
207
|
+
# Check for archive.rev
|
|
208
|
+
main_rev = File.join(dir, "#{basename}.rev")
|
|
209
|
+
rev_files << main_rev if File.exist?(main_rev)
|
|
210
|
+
|
|
211
|
+
# Check for .rNN.rev files
|
|
212
|
+
(0..99).each do |i|
|
|
213
|
+
rev_path = File.join(dir, format("%s.r%02d.rev", basename, i))
|
|
214
|
+
break unless File.exist?(rev_path)
|
|
215
|
+
|
|
216
|
+
rev_files << rev_path
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
rev_files
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Read variable-length integer (RAR5)
|
|
223
|
+
#
|
|
224
|
+
# @param io [IO] Input stream
|
|
225
|
+
# @return [Integer] Decoded integer
|
|
226
|
+
def read_vint(io)
|
|
227
|
+
result = 0
|
|
228
|
+
shift = 0
|
|
229
|
+
|
|
230
|
+
loop do
|
|
231
|
+
byte = io.read(1)&.unpack1("C")
|
|
232
|
+
raise "Unexpected EOF" unless byte
|
|
233
|
+
|
|
234
|
+
result |= (byte & 0x7F) << shift
|
|
235
|
+
break if byte.nobits?(0x80)
|
|
236
|
+
|
|
237
|
+
shift += 7
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
result
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|