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,466 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "header"
|
|
4
|
+
require_relative "vint"
|
|
5
|
+
require_relative "crc32"
|
|
6
|
+
require_relative "compression/store"
|
|
7
|
+
require_relative "compression/lzma"
|
|
8
|
+
require_relative "compression/lzss"
|
|
9
|
+
require_relative "multi_volume/volume_manager"
|
|
10
|
+
require_relative "models/volume_options"
|
|
11
|
+
require_relative "models/solid_options"
|
|
12
|
+
require_relative "models/encryption_options"
|
|
13
|
+
require_relative "models/recovery_options"
|
|
14
|
+
require_relative "solid/solid_manager"
|
|
15
|
+
require_relative "encryption/encryption_manager"
|
|
16
|
+
require_relative "../../../parity/par2_creator"
|
|
17
|
+
|
|
18
|
+
module Omnizip
|
|
19
|
+
module Formats
|
|
20
|
+
module Rar
|
|
21
|
+
module Rar5
|
|
22
|
+
# RAR5 format writer
|
|
23
|
+
#
|
|
24
|
+
# This class creates RAR5 archives with STORE or LZMA compression.
|
|
25
|
+
# Supports single-file archives, multi-volume archives, solid compression,
|
|
26
|
+
# AES-256-CBC encryption, and PAR2 recovery records.
|
|
27
|
+
#
|
|
28
|
+
# @example Create single archive with STORE compression
|
|
29
|
+
# writer = Writer.new('archive.rar')
|
|
30
|
+
# writer.add_file('test.txt')
|
|
31
|
+
# writer.write
|
|
32
|
+
#
|
|
33
|
+
# @example Create archive with LZMA compression
|
|
34
|
+
# writer = Writer.new('archive.rar', compression: :lzma, level: 5)
|
|
35
|
+
# writer.add_file('test.txt')
|
|
36
|
+
# writer.write
|
|
37
|
+
#
|
|
38
|
+
# @example Create solid archive
|
|
39
|
+
# writer = Writer.new('archive.rar', compression: :lzma, level: 5, solid: true)
|
|
40
|
+
# writer.add_directory('project/')
|
|
41
|
+
# writer.write
|
|
42
|
+
#
|
|
43
|
+
# @example Create encrypted archive
|
|
44
|
+
# writer = Writer.new('archive.rar',
|
|
45
|
+
# compression: :lzma,
|
|
46
|
+
# password: 'SecurePass123!',
|
|
47
|
+
# kdf_iterations: 262_144
|
|
48
|
+
# )
|
|
49
|
+
# writer.add_file('confidential.pdf')
|
|
50
|
+
# writer.write
|
|
51
|
+
#
|
|
52
|
+
# @example Create multi-volume archive
|
|
53
|
+
# writer = Writer.new('archive.rar',
|
|
54
|
+
# multi_volume: true,
|
|
55
|
+
# volume_size: '10M',
|
|
56
|
+
# compression: :lzma
|
|
57
|
+
# )
|
|
58
|
+
# writer.add_file('largefile.dat')
|
|
59
|
+
# writer.write # Returns: ['archive.part1.rar', 'archive.part2.rar', ...]
|
|
60
|
+
class Writer
|
|
61
|
+
# RAR5 signature: "Rar!\x1A\x07\x01\x00"
|
|
62
|
+
RAR5_SIGNATURE = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01,
|
|
63
|
+
0x00].pack("C*")
|
|
64
|
+
|
|
65
|
+
# Threshold for automatic compression selection (1 KB)
|
|
66
|
+
AUTO_COMPRESS_THRESHOLD = 1024
|
|
67
|
+
|
|
68
|
+
# @return [String] Output archive path
|
|
69
|
+
attr_reader :path
|
|
70
|
+
|
|
71
|
+
# @return [Hash] Archive options
|
|
72
|
+
attr_reader :options
|
|
73
|
+
|
|
74
|
+
# Initialize RAR5 writer
|
|
75
|
+
#
|
|
76
|
+
# @param path [String] Output RAR file path
|
|
77
|
+
# @param options [Hash] Archive options
|
|
78
|
+
# @option options [Symbol] :compression Compression method (:store, :lzma, :auto)
|
|
79
|
+
# @option options [Integer] :level LZMA compression level (1-5, default: 3)
|
|
80
|
+
# @option options [Boolean] :include_mtime Include modification time in file headers (default: false)
|
|
81
|
+
# @option options [Boolean] :include_crc32 Include CRC32 checksum in file headers (default: false)
|
|
82
|
+
# @option options [Boolean] :solid Enable solid compression (default: false)
|
|
83
|
+
# @option options [String] :password Password for encryption (default: nil)
|
|
84
|
+
# @option options [Integer] :kdf_iterations PBKDF2 iterations for encryption (default: 262,144)
|
|
85
|
+
# @option options [Boolean] :recovery Enable PAR2 recovery records (default: false)
|
|
86
|
+
# @option options [Integer] :recovery_percent Redundancy percentage for PAR2 (default: 5)
|
|
87
|
+
# @option options [Boolean] :multi_volume Enable multi-volume archive (default: false)
|
|
88
|
+
# @option options [Integer, String] :volume_size Maximum volume size (e.g., "10M", 10485760)
|
|
89
|
+
# @option options [String] :volume_naming Volume naming pattern ("part", "volume", "numeric")
|
|
90
|
+
def initialize(path, options = {})
|
|
91
|
+
@path = path
|
|
92
|
+
@options = {
|
|
93
|
+
compression: :store,
|
|
94
|
+
level: 3,
|
|
95
|
+
include_mtime: false,
|
|
96
|
+
include_crc32: false,
|
|
97
|
+
solid: false,
|
|
98
|
+
password: nil,
|
|
99
|
+
kdf_iterations: 262_144,
|
|
100
|
+
recovery: false,
|
|
101
|
+
recovery_percent: 5,
|
|
102
|
+
multi_volume: false,
|
|
103
|
+
volume_size: nil,
|
|
104
|
+
volume_naming: "part",
|
|
105
|
+
}.merge(options)
|
|
106
|
+
@files = []
|
|
107
|
+
|
|
108
|
+
# Initialize encryption manager if password provided (and not empty)
|
|
109
|
+
@encryption_manager = if @options[:password] && !@options[:password].empty?
|
|
110
|
+
Encryption::EncryptionManager.new(
|
|
111
|
+
@options[:password],
|
|
112
|
+
kdf_iterations: @options[:kdf_iterations],
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Add file to archive
|
|
118
|
+
#
|
|
119
|
+
# @param input_path [String] Path to file on disk
|
|
120
|
+
# @param archive_path [String, nil] Path within archive (defaults to basename)
|
|
121
|
+
# @raise [ArgumentError] if file does not exist
|
|
122
|
+
def add_file(input_path, archive_path = nil)
|
|
123
|
+
unless File.exist?(input_path)
|
|
124
|
+
raise ArgumentError,
|
|
125
|
+
"File not found: #{input_path}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
archive_path ||= File.basename(input_path)
|
|
129
|
+
|
|
130
|
+
# Store file with metadata
|
|
131
|
+
@files << {
|
|
132
|
+
input: input_path,
|
|
133
|
+
archive: archive_path,
|
|
134
|
+
mtime: File.mtime(input_path),
|
|
135
|
+
stat: File.stat(input_path),
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Add directory recursively
|
|
140
|
+
#
|
|
141
|
+
# @param dir_path [String] Directory path
|
|
142
|
+
# @param base_path [String, nil] Base path for relative names
|
|
143
|
+
# @return [void]
|
|
144
|
+
def add_directory(dir_path, base_path = nil)
|
|
145
|
+
unless File.directory?(dir_path)
|
|
146
|
+
raise ArgumentError,
|
|
147
|
+
"Directory not found: #{dir_path}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
base_path ||= dir_path
|
|
151
|
+
|
|
152
|
+
Dir.glob(File.join(dir_path, "**", "*")).each do |path|
|
|
153
|
+
next unless File.file?(path)
|
|
154
|
+
|
|
155
|
+
relative_path = path.sub(
|
|
156
|
+
/^#{Regexp.escape(base_path)}#{File::SEPARATOR}?/, ""
|
|
157
|
+
)
|
|
158
|
+
add_file(path, relative_path)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Write archive to disk
|
|
163
|
+
#
|
|
164
|
+
# For single archives, returns the archive path or array of paths (with PAR2).
|
|
165
|
+
# For multi-volume archives, returns an array of volume paths.
|
|
166
|
+
#
|
|
167
|
+
# @return [String, Array<String>] Path(s) to created archive(s) and PAR2 files
|
|
168
|
+
def write
|
|
169
|
+
archive_paths = if @options[:multi_volume]
|
|
170
|
+
write_multi_volume
|
|
171
|
+
else
|
|
172
|
+
write_single_archive
|
|
173
|
+
[@path]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Generate PAR2 recovery files if requested
|
|
177
|
+
if @options[:recovery]
|
|
178
|
+
par2_paths = generate_recovery_files(archive_paths)
|
|
179
|
+
return archive_paths + par2_paths
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
@options[:multi_volume] ? archive_paths : archive_paths.first
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
|
|
187
|
+
# Write single-file archive
|
|
188
|
+
#
|
|
189
|
+
# @return [void]
|
|
190
|
+
def write_single_archive
|
|
191
|
+
File.open(@path, "wb") do |f|
|
|
192
|
+
write_signature(f)
|
|
193
|
+
write_main_header(f)
|
|
194
|
+
|
|
195
|
+
if @options[:solid]
|
|
196
|
+
write_solid_block(f, @files)
|
|
197
|
+
else
|
|
198
|
+
@files.each do |file|
|
|
199
|
+
write_file_entry(f, file)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
write_end_header(f)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Write multi-volume archive (new behavior)
|
|
208
|
+
#
|
|
209
|
+
# @return [Array<String>] Paths to created volume files
|
|
210
|
+
def write_multi_volume
|
|
211
|
+
# Parse volume size if string
|
|
212
|
+
volume_size = if @options[:volume_size].is_a?(String)
|
|
213
|
+
Models::VolumeOptions.parse_size(@options[:volume_size])
|
|
214
|
+
else
|
|
215
|
+
@options[:volume_size] || 104_857_600 # 100 MB default
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Create volume manager
|
|
219
|
+
manager = MultiVolume::VolumeManager.new(@path,
|
|
220
|
+
max_volume_size: volume_size,
|
|
221
|
+
volume_naming: @options[:volume_naming],
|
|
222
|
+
compression: @options[:compression],
|
|
223
|
+
level: @options[:level],
|
|
224
|
+
include_mtime: @options[:include_mtime],
|
|
225
|
+
include_crc32: @options[:include_crc32])
|
|
226
|
+
|
|
227
|
+
# Add all files to manager
|
|
228
|
+
@files.each do |file|
|
|
229
|
+
manager.add_file(file[:input], file[:archive])
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Create volumes
|
|
233
|
+
manager.create_volumes
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Write solid block containing multiple files
|
|
237
|
+
#
|
|
238
|
+
# @param io [IO] Output stream
|
|
239
|
+
# @param files [Array<Hash>] Files to compress in solid mode
|
|
240
|
+
# @return [void]
|
|
241
|
+
def write_solid_block(io, files)
|
|
242
|
+
# Create solid manager
|
|
243
|
+
manager = Solid::SolidManager.new(level: @options[:level])
|
|
244
|
+
|
|
245
|
+
# Add all files to solid stream
|
|
246
|
+
files.each do |file|
|
|
247
|
+
data = File.binread(file[:input])
|
|
248
|
+
manager.add_file(file[:archive], data, mtime: file[:mtime],
|
|
249
|
+
stat: file[:stat])
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Compress entire solid block
|
|
253
|
+
result = manager.compress_all
|
|
254
|
+
|
|
255
|
+
# Write each file header with references to the solid block
|
|
256
|
+
# In RAR5, all files share the same compressed data
|
|
257
|
+
files.each_with_index do |file, idx|
|
|
258
|
+
file_info = result[:files][idx]
|
|
259
|
+
|
|
260
|
+
# Calculate CRC32 if needed (note: only for STORE in non-solid)
|
|
261
|
+
# Solid compression always uses LZMA, so CRC32 is not included
|
|
262
|
+
compression_method = Compression::Lzma.method_id(@options[:level])
|
|
263
|
+
|
|
264
|
+
header = FileHeader.new(
|
|
265
|
+
filename: file[:archive],
|
|
266
|
+
file_size: file_info[:size],
|
|
267
|
+
compressed_size: (idx.zero? ? result[:compressed_size] : 0), # Only first file has compressed size
|
|
268
|
+
compression_method: compression_method,
|
|
269
|
+
mtime: @options[:include_mtime] ? file[:mtime] : nil,
|
|
270
|
+
crc32: nil, # No CRC32 for solid/LZMA
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
io.write(header.encode)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Write the compressed data once after all headers
|
|
277
|
+
io.write(result[:compressed_data])
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Write RAR5 signature (8 bytes)
|
|
281
|
+
#
|
|
282
|
+
# @param io [IO] Output stream
|
|
283
|
+
def write_signature(io)
|
|
284
|
+
io.write(RAR5_SIGNATURE)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Write Main header
|
|
288
|
+
#
|
|
289
|
+
# @param io [IO] Output stream
|
|
290
|
+
def write_main_header(io)
|
|
291
|
+
header = MainHeader.new
|
|
292
|
+
io.write(header.encode)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Write file entry (header + data)
|
|
296
|
+
#
|
|
297
|
+
# @param io [IO] Output stream
|
|
298
|
+
# @param file [Hash] File information
|
|
299
|
+
def write_file_entry(io, file)
|
|
300
|
+
# Read file data
|
|
301
|
+
data = File.binread(file[:input])
|
|
302
|
+
|
|
303
|
+
# Select compression method
|
|
304
|
+
compression_method = select_compression_method(data)
|
|
305
|
+
|
|
306
|
+
# Compress data (may return hash with properties for LZMA)
|
|
307
|
+
compression_result = compress_data(data, compression_method)
|
|
308
|
+
|
|
309
|
+
# Extract compressed data and optional properties
|
|
310
|
+
if compression_result.is_a?(Hash)
|
|
311
|
+
compressed_data = compression_result[:data]
|
|
312
|
+
compression_properties = compression_result[:properties]
|
|
313
|
+
else
|
|
314
|
+
compressed_data = compression_result
|
|
315
|
+
compression_properties = nil
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Build extra area if compression properties are present
|
|
319
|
+
extra_area = build_compression_extra_area(compression_properties)
|
|
320
|
+
|
|
321
|
+
# Encrypt if password provided (encryption happens AFTER compression)
|
|
322
|
+
if @encryption_manager
|
|
323
|
+
encryption_result = @encryption_manager.encrypt_file_data(compressed_data)
|
|
324
|
+
final_data = encryption_result[:encrypted_data]
|
|
325
|
+
# TODO: Store encryption_result[:header] for decryption
|
|
326
|
+
else
|
|
327
|
+
final_data = compressed_data
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Calculate CRC32 if needed
|
|
331
|
+
# NOTE: RAR5's optional CRC32 is only compatible with STORE compression.
|
|
332
|
+
# When LZMA or other compression is used, disable CRC32 even if requested.
|
|
333
|
+
# When encryption is used, CRC32 is also not included.
|
|
334
|
+
use_crc32 = @options[:include_crc32] &&
|
|
335
|
+
compression_method == Compression::Store::METHOD &&
|
|
336
|
+
!@encryption_manager
|
|
337
|
+
file_crc32 = use_crc32 ? CRC32.calculate(data) : nil
|
|
338
|
+
|
|
339
|
+
# Create file header
|
|
340
|
+
header = FileHeader.new(
|
|
341
|
+
filename: file[:archive],
|
|
342
|
+
file_size: data.bytesize,
|
|
343
|
+
compressed_size: final_data.bytesize,
|
|
344
|
+
compression_method: compression_method,
|
|
345
|
+
mtime: @options[:include_mtime] ? file[:mtime] : nil,
|
|
346
|
+
crc32: file_crc32,
|
|
347
|
+
extra_area: extra_area,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# Write header
|
|
351
|
+
io.write(header.encode)
|
|
352
|
+
|
|
353
|
+
# Write data (compressed and encrypted if applicable)
|
|
354
|
+
io.write(final_data)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Write End header
|
|
358
|
+
#
|
|
359
|
+
# @param io [IO] Output stream
|
|
360
|
+
def write_end_header(io)
|
|
361
|
+
header = EndHeader.new
|
|
362
|
+
io.write(header.encode)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Select compression method based on options and data
|
|
366
|
+
#
|
|
367
|
+
# @param data [String] File data
|
|
368
|
+
# @return [Integer] Compression method ID
|
|
369
|
+
def select_compression_method(data)
|
|
370
|
+
case @options[:compression]
|
|
371
|
+
when :store
|
|
372
|
+
Compression::Store::METHOD
|
|
373
|
+
when :lzss, :lzma
|
|
374
|
+
# RAR5 uses LZSS compression (methods 1-5)
|
|
375
|
+
# Note: :lzma is deprecated, use :lzss for clarity
|
|
376
|
+
level = @options[:level] || 3
|
|
377
|
+
if Compression::Lzss.available?
|
|
378
|
+
Compression::Lzss.method_id(level)
|
|
379
|
+
else
|
|
380
|
+
# LZSS not implemented, fall back to STORE
|
|
381
|
+
Compression::Store::METHOD
|
|
382
|
+
end
|
|
383
|
+
when :auto
|
|
384
|
+
# Auto-select based on file size
|
|
385
|
+
if data.bytesize < AUTO_COMPRESS_THRESHOLD
|
|
386
|
+
Compression::Store::METHOD
|
|
387
|
+
else
|
|
388
|
+
level = @options[:level] || 3
|
|
389
|
+
if Compression::Lzss.available?
|
|
390
|
+
Compression::Lzss.method_id(level)
|
|
391
|
+
else
|
|
392
|
+
# LZSS not implemented, fall back to STORE
|
|
393
|
+
Compression::Store::METHOD
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
else
|
|
397
|
+
Compression::Store::METHOD
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Compress data using selected method
|
|
402
|
+
#
|
|
403
|
+
# @param data [String] Data to compress
|
|
404
|
+
# @param method [Integer] Compression method ID
|
|
405
|
+
# @return [String, Hash] Compressed data or hash with :data and :properties
|
|
406
|
+
def compress_data(data, method)
|
|
407
|
+
if method == Compression::Store::METHOD
|
|
408
|
+
Compression::Store.compress(data)
|
|
409
|
+
else
|
|
410
|
+
# Methods 1-5 are RAR5 LZSS with different levels
|
|
411
|
+
level = method.clamp(1, 5)
|
|
412
|
+
if Compression::Lzss.available?
|
|
413
|
+
Compression::Lzss.compress(data, level: level)
|
|
414
|
+
else
|
|
415
|
+
# LZSS not implemented, fall back to STORE
|
|
416
|
+
Compression::Store.compress(data)
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# Build extra area for compression parameters
|
|
422
|
+
#
|
|
423
|
+
# RAR5 stores compression parameters in an extra area with type 0x03.
|
|
424
|
+
# The format depends on the compression method used.
|
|
425
|
+
#
|
|
426
|
+
# @param properties [String, nil] Compression properties
|
|
427
|
+
# @return [String, nil] Encoded extra area or nil if no properties
|
|
428
|
+
def build_compression_extra_area(properties)
|
|
429
|
+
return nil if properties.nil?
|
|
430
|
+
|
|
431
|
+
extra_data = []
|
|
432
|
+
|
|
433
|
+
# Extra record type: 0x03 = Compression parameters
|
|
434
|
+
extra_data.concat(VINT.encode(0x03))
|
|
435
|
+
|
|
436
|
+
# Compression properties (method-specific)
|
|
437
|
+
extra_data.concat(properties.bytes)
|
|
438
|
+
|
|
439
|
+
extra_data.pack("C*")
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# Generate PAR2 recovery files for archives
|
|
443
|
+
#
|
|
444
|
+
# @param archive_paths [Array<String>] Paths to archive files
|
|
445
|
+
# @return [Array<String>] Paths to created PAR2 files
|
|
446
|
+
def generate_recovery_files(archive_paths)
|
|
447
|
+
base_name = @path.sub(/\.rar$/, "")
|
|
448
|
+
|
|
449
|
+
creator = Parity::Par2Creator.new(
|
|
450
|
+
redundancy: @options[:recovery_percent] || 5,
|
|
451
|
+
block_size: 16_384,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
# Add all archive files
|
|
455
|
+
archive_paths.each do |path|
|
|
456
|
+
creator.add_file(path)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# Create PAR2 files
|
|
460
|
+
creator.create(base_name)
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "lutaml/model"
|
|
5
|
+
rescue LoadError, ArgumentError
|
|
6
|
+
# lutaml-model not available, using simple classes
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require_relative "../format_spec_loader"
|
|
10
|
+
|
|
11
|
+
module Omnizip
|
|
12
|
+
module Formats
|
|
13
|
+
module Rar
|
|
14
|
+
# Base class for RAR format implementations
|
|
15
|
+
#
|
|
16
|
+
# This class provides common functionality for RAR v3 and RAR v5 formats,
|
|
17
|
+
# following the Strategy pattern where each version has its own
|
|
18
|
+
# implementation strategy.
|
|
19
|
+
#
|
|
20
|
+
# @abstract Subclass and override {#read_archive}, {#write_archive},
|
|
21
|
+
# {#compress}, and {#decompress} to implement a RAR version strategy.
|
|
22
|
+
class RarFormatBase
|
|
23
|
+
attr_reader :spec, :version
|
|
24
|
+
|
|
25
|
+
# Initialize a RAR format handler
|
|
26
|
+
#
|
|
27
|
+
# @param spec_name [String] The format specification name
|
|
28
|
+
# (e.g., "rar3", "rar5")
|
|
29
|
+
def initialize(spec_name)
|
|
30
|
+
@spec = FormatSpecLoader.load(spec_name)
|
|
31
|
+
@version = @spec.version
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Read a RAR archive
|
|
35
|
+
#
|
|
36
|
+
# @param io [IO] The input stream
|
|
37
|
+
# @return [Array<Entry>] The archive entries
|
|
38
|
+
# @raise [NotImplementedError] Must be implemented by subclasses
|
|
39
|
+
def read_archive(io)
|
|
40
|
+
raise NotImplementedError,
|
|
41
|
+
"#{self.class} must implement #read_archive"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Write a RAR archive
|
|
45
|
+
#
|
|
46
|
+
# @param io [IO] The output stream
|
|
47
|
+
# @param entries [Array<Entry>] The entries to write
|
|
48
|
+
# @return [void]
|
|
49
|
+
# @raise [NotImplementedError] Must be implemented by subclasses
|
|
50
|
+
def write_archive(io, entries)
|
|
51
|
+
raise NotImplementedError,
|
|
52
|
+
"#{self.class} must implement #write_archive"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Compress data
|
|
56
|
+
#
|
|
57
|
+
# @param data [String] The data to compress
|
|
58
|
+
# @param method [Symbol] The compression method
|
|
59
|
+
# @param options [Hash] Compression options
|
|
60
|
+
# @return [String] The compressed data
|
|
61
|
+
# @raise [NotImplementedError] Must be implemented by subclasses
|
|
62
|
+
def compress(data, method: :normal, **options)
|
|
63
|
+
raise NotImplementedError,
|
|
64
|
+
"#{self.class} must implement #compress"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Decompress data
|
|
68
|
+
#
|
|
69
|
+
# @param data [String] The compressed data
|
|
70
|
+
# @param method [Symbol] The compression method
|
|
71
|
+
# @param options [Hash] Decompression options
|
|
72
|
+
# @return [String] The decompressed data
|
|
73
|
+
# @raise [NotImplementedError] Must be implemented by subclasses
|
|
74
|
+
def decompress(data, method: :normal, **options)
|
|
75
|
+
raise NotImplementedError,
|
|
76
|
+
"#{self.class} must implement #decompress"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Verify magic bytes match this format
|
|
80
|
+
#
|
|
81
|
+
# @param io [IO] The input stream
|
|
82
|
+
# @return [Boolean] True if magic bytes match
|
|
83
|
+
def verify_magic_bytes(io)
|
|
84
|
+
magic = spec.magic_bytes.pack("C*")
|
|
85
|
+
bytes = io.read(magic.bytesize)
|
|
86
|
+
io.rewind
|
|
87
|
+
bytes == magic
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Get compression method code from symbol
|
|
91
|
+
#
|
|
92
|
+
# @param method [Symbol] The method name (e.g., :normal, :best)
|
|
93
|
+
# @return [Integer] The method code
|
|
94
|
+
# @raise [FormatError] If method is not supported
|
|
95
|
+
def compression_method_code(method)
|
|
96
|
+
code = spec.format.compression_methods[method]
|
|
97
|
+
return code if code
|
|
98
|
+
|
|
99
|
+
raise FormatError,
|
|
100
|
+
"Unsupported compression method: #{method}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Get compression method symbol from code
|
|
104
|
+
#
|
|
105
|
+
# @param code [Integer] The method code
|
|
106
|
+
# @return [Symbol] The method name
|
|
107
|
+
# @raise [FormatError] If code is not recognized
|
|
108
|
+
def compression_method_name(code)
|
|
109
|
+
methods = spec.format.compression_methods
|
|
110
|
+
name = methods.key(code)
|
|
111
|
+
return name if name
|
|
112
|
+
|
|
113
|
+
# Handle unknown method codes gracefully
|
|
114
|
+
# RAR can have version-specific or PPMd methods not in standard list
|
|
115
|
+
case code
|
|
116
|
+
when 0x00..0x2F then :store # Very old or stored
|
|
117
|
+
when 0x30 then :store
|
|
118
|
+
when 0x31 then :fastest
|
|
119
|
+
when 0x32 then :fast
|
|
120
|
+
when 0x33 then :normal
|
|
121
|
+
when 0x34 then :good
|
|
122
|
+
when 0x35 then :best
|
|
123
|
+
when 0x36..0x40 then :normal # Extended range
|
|
124
|
+
when 0x80..0xFF then :ppmd # PPMd or version-specific
|
|
125
|
+
else :unknown
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Get block type code from symbol
|
|
130
|
+
#
|
|
131
|
+
# @param type [Symbol] The block type name
|
|
132
|
+
# @return [Integer] The block type code
|
|
133
|
+
# @raise [FormatError] If type is not supported
|
|
134
|
+
def block_type_code(type)
|
|
135
|
+
code = spec.format.block_types[type]
|
|
136
|
+
return code if code
|
|
137
|
+
|
|
138
|
+
raise FormatError, "Unknown block type: #{type}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Get block type symbol from code
|
|
142
|
+
#
|
|
143
|
+
# @param code [Integer] The block type code
|
|
144
|
+
# @return [Symbol] The block type name
|
|
145
|
+
# @raise [FormatError] If code is not recognized
|
|
146
|
+
def block_type_name(code)
|
|
147
|
+
types = spec.format.block_types
|
|
148
|
+
name = types.key(code)
|
|
149
|
+
return name if name
|
|
150
|
+
|
|
151
|
+
raise FormatError, "Unknown block type code: #{code}"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Check if format supports a feature
|
|
155
|
+
#
|
|
156
|
+
# @param feature [Symbol] The feature name
|
|
157
|
+
# @return [Boolean] True if supported
|
|
158
|
+
def supports_feature?(feature)
|
|
159
|
+
features = spec.format.features || {}
|
|
160
|
+
compression_features = spec.format.compression_features || {}
|
|
161
|
+
advanced_features = spec.format.advanced_features || {}
|
|
162
|
+
|
|
163
|
+
features[feature] ||
|
|
164
|
+
compression_features[feature] ||
|
|
165
|
+
advanced_features[feature] ||
|
|
166
|
+
false
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Get encryption algorithm
|
|
170
|
+
#
|
|
171
|
+
# @return [String, nil] The encryption algorithm or nil
|
|
172
|
+
def encryption_algorithm
|
|
173
|
+
return nil unless spec.format.encryption&.supported
|
|
174
|
+
|
|
175
|
+
algorithms = spec.format.encryption.algorithms
|
|
176
|
+
algorithms&.first
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Get dictionary size for compression level
|
|
180
|
+
#
|
|
181
|
+
# @param level [Symbol] The compression level
|
|
182
|
+
# @return [Integer] The dictionary size code
|
|
183
|
+
def dictionary_size_code(level)
|
|
184
|
+
spec.format.dictionary_sizes[level] ||
|
|
185
|
+
spec.format.dictionary_sizes[:auto]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
protected
|
|
189
|
+
|
|
190
|
+
# Read a vint (variable-length integer) from stream
|
|
191
|
+
#
|
|
192
|
+
# Used in RAR 5 format for variable-length encoding
|
|
193
|
+
#
|
|
194
|
+
# @param io [IO] The input stream
|
|
195
|
+
# @return [Integer] The decoded integer
|
|
196
|
+
def read_vint(io)
|
|
197
|
+
result = 0
|
|
198
|
+
shift = 0
|
|
199
|
+
|
|
200
|
+
loop do
|
|
201
|
+
byte = io.read(1)&.unpack1("C")
|
|
202
|
+
raise FormatError, "Unexpected EOF" unless byte
|
|
203
|
+
|
|
204
|
+
result |= (byte & 0x7F) << shift
|
|
205
|
+
break if byte.nobits?(0x80)
|
|
206
|
+
|
|
207
|
+
shift += 7
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
result
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Write a vint (variable-length integer) to stream
|
|
214
|
+
#
|
|
215
|
+
# @param io [IO] The output stream
|
|
216
|
+
# @param value [Integer] The value to encode
|
|
217
|
+
# @return [void]
|
|
218
|
+
def write_vint(io, value)
|
|
219
|
+
loop do
|
|
220
|
+
byte = value & 0x7F
|
|
221
|
+
value >>= 7
|
|
222
|
+
|
|
223
|
+
byte |= 0x80 if value.positive?
|
|
224
|
+
io.write([byte].pack("C"))
|
|
225
|
+
|
|
226
|
+
break if value.zero?
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Calculate CRC32 checksum
|
|
231
|
+
#
|
|
232
|
+
# @param data [String] The data to checksum
|
|
233
|
+
# @return [Integer] The CRC32 value
|
|
234
|
+
def calculate_crc32(data)
|
|
235
|
+
require "zlib"
|
|
236
|
+
Zlib.crc32(data)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|