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,264 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "volume_splitter"
|
|
4
|
+
require_relative "volume_writer"
|
|
5
|
+
require_relative "../models/volume_options"
|
|
6
|
+
|
|
7
|
+
module Omnizip
|
|
8
|
+
module Formats
|
|
9
|
+
module Rar
|
|
10
|
+
module Rar5
|
|
11
|
+
module MultiVolume
|
|
12
|
+
# Volume manager for multi-volume archive creation
|
|
13
|
+
#
|
|
14
|
+
# This class coordinates the creation of multi-volume RAR5 archives
|
|
15
|
+
# by managing volume splitting, file distribution, and volume writing.
|
|
16
|
+
#
|
|
17
|
+
# @example Create multi-volume archive
|
|
18
|
+
# manager = VolumeManager.new('archive.rar',
|
|
19
|
+
# max_volume_size: 10 * 1024 * 1024, # 10 MB
|
|
20
|
+
# compression: :lzma,
|
|
21
|
+
# level: 5
|
|
22
|
+
# )
|
|
23
|
+
# manager.add_file('large_file.dat')
|
|
24
|
+
# manager.create_volumes
|
|
25
|
+
class VolumeManager
|
|
26
|
+
# @return [String] Base archive path
|
|
27
|
+
attr_reader :base_path
|
|
28
|
+
|
|
29
|
+
# @return [VolumeOptions] Volume options
|
|
30
|
+
attr_reader :volume_options
|
|
31
|
+
|
|
32
|
+
# @return [Hash] Compression options
|
|
33
|
+
attr_reader :compression_options
|
|
34
|
+
|
|
35
|
+
# @return [Array<Hash>] Files to archive
|
|
36
|
+
attr_reader :files
|
|
37
|
+
|
|
38
|
+
# Initialize volume manager
|
|
39
|
+
#
|
|
40
|
+
# @param base_path [String] Base archive path (e.g., "archive.rar")
|
|
41
|
+
# @param options [Hash] Options
|
|
42
|
+
# @option options [Integer] :max_volume_size Maximum volume size in bytes
|
|
43
|
+
# @option options [String] :volume_naming Naming pattern ("part", "volume", "numeric")
|
|
44
|
+
# @option options [Symbol] :compression Compression method (:store, :lzma)
|
|
45
|
+
# @option options [Integer] :level Compression level (1-5)
|
|
46
|
+
# @option options [Boolean] :include_mtime Include modification time
|
|
47
|
+
# @option options [Boolean] :include_crc32 Include CRC32
|
|
48
|
+
def initialize(base_path, options = {})
|
|
49
|
+
@base_path = base_path
|
|
50
|
+
@volume_options = Models::VolumeOptions.new(
|
|
51
|
+
max_volume_size: options[:max_volume_size] || 104_857_600,
|
|
52
|
+
volume_naming: options[:volume_naming] || "part",
|
|
53
|
+
)
|
|
54
|
+
@volume_options.validate!
|
|
55
|
+
|
|
56
|
+
@compression_options = {
|
|
57
|
+
compression: options[:compression] || :store,
|
|
58
|
+
level: options[:level] || 3,
|
|
59
|
+
include_mtime: options[:include_mtime] || false,
|
|
60
|
+
include_crc32: options[:include_crc32] || false,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@files = []
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Add file to archive
|
|
67
|
+
#
|
|
68
|
+
# @param input_path [String] Path to file on disk
|
|
69
|
+
# @param archive_path [String, nil] Path within archive
|
|
70
|
+
# @return [void]
|
|
71
|
+
def add_file(input_path, archive_path = nil)
|
|
72
|
+
unless File.exist?(input_path)
|
|
73
|
+
raise ArgumentError,
|
|
74
|
+
"File not found: #{input_path}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
archive_path ||= File.basename(input_path)
|
|
78
|
+
|
|
79
|
+
@files << {
|
|
80
|
+
input: input_path,
|
|
81
|
+
archive: archive_path,
|
|
82
|
+
mtime: File.mtime(input_path),
|
|
83
|
+
stat: File.stat(input_path),
|
|
84
|
+
size: File.size(input_path),
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Add directory recursively
|
|
89
|
+
#
|
|
90
|
+
# @param dir_path [String] Directory path
|
|
91
|
+
# @param base_path [String, nil] Base path for relative names
|
|
92
|
+
# @return [void]
|
|
93
|
+
def add_directory(dir_path, base_path = nil)
|
|
94
|
+
base_path ||= dir_path
|
|
95
|
+
|
|
96
|
+
Dir.glob(File.join(dir_path, "**", "*")).each do |path|
|
|
97
|
+
next unless File.file?(path)
|
|
98
|
+
|
|
99
|
+
relative_path = path.sub(
|
|
100
|
+
/^#{Regexp.escape(base_path)}#{File::SEPARATOR}?/, ""
|
|
101
|
+
)
|
|
102
|
+
add_file(path, relative_path)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Create multi-volume archives
|
|
107
|
+
#
|
|
108
|
+
# @return [Array<String>] Paths to created volume files
|
|
109
|
+
def create_volumes
|
|
110
|
+
# Prepare file entries with compression
|
|
111
|
+
prepared_files = prepare_files
|
|
112
|
+
|
|
113
|
+
# Calculate file distribution across volumes
|
|
114
|
+
splitter = VolumeSplitter.new(max_volume_size: @volume_options.max_volume_size)
|
|
115
|
+
distribution = splitter.calculate_file_distribution(prepared_files)
|
|
116
|
+
|
|
117
|
+
# Create each volume
|
|
118
|
+
volume_paths = []
|
|
119
|
+
distribution.each_with_index do |file_indices, volume_idx|
|
|
120
|
+
volume_number = volume_idx + 1
|
|
121
|
+
is_last = (volume_idx == distribution.size - 1)
|
|
122
|
+
|
|
123
|
+
volume_path = VolumeWriter.volume_filename(
|
|
124
|
+
@base_path,
|
|
125
|
+
volume_number,
|
|
126
|
+
naming: @volume_options.volume_naming,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
write_volume(volume_path, volume_number, file_indices,
|
|
130
|
+
prepared_files, is_last)
|
|
131
|
+
volume_paths << volume_path
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
volume_paths
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Check if archive needs splitting
|
|
138
|
+
#
|
|
139
|
+
# @return [Boolean] true if multi-volume needed
|
|
140
|
+
def needs_splitting?
|
|
141
|
+
total_size = estimate_total_size
|
|
142
|
+
VolumeSplitter.needs_splitting?(total_size,
|
|
143
|
+
@volume_options.max_volume_size)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
# Prepare files with compression and metadata
|
|
149
|
+
#
|
|
150
|
+
# @return [Array<Hash>] Prepared file information
|
|
151
|
+
def prepare_files
|
|
152
|
+
@files.map do |file|
|
|
153
|
+
# Read and compress file data
|
|
154
|
+
data = File.binread(file[:input])
|
|
155
|
+
compression_method = select_compression_method(data)
|
|
156
|
+
compressed_data = compress_data(data, compression_method)
|
|
157
|
+
|
|
158
|
+
# Calculate CRC32 if needed
|
|
159
|
+
use_crc32 = @compression_options[:include_crc32] && compression_method == Compression::Store::METHOD
|
|
160
|
+
file_crc32 = use_crc32 ? CRC32.calculate(data) : nil
|
|
161
|
+
|
|
162
|
+
# Create file header
|
|
163
|
+
header = FileHeader.new(
|
|
164
|
+
filename: file[:archive],
|
|
165
|
+
file_size: data.bytesize,
|
|
166
|
+
compressed_size: compressed_data.bytesize,
|
|
167
|
+
compression_method: compression_method,
|
|
168
|
+
mtime: @compression_options[:include_mtime] ? file[:mtime] : nil,
|
|
169
|
+
crc32: file_crc32,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Estimate header size (approximate)
|
|
173
|
+
header_data = header.encode
|
|
174
|
+
header_size = header_data.bytesize
|
|
175
|
+
|
|
176
|
+
{
|
|
177
|
+
file: file,
|
|
178
|
+
header: header,
|
|
179
|
+
header_size: header_size,
|
|
180
|
+
compressed_data: compressed_data,
|
|
181
|
+
compressed_size: compressed_data.bytesize,
|
|
182
|
+
}
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Write single volume file
|
|
187
|
+
#
|
|
188
|
+
# @param volume_path [String] Volume file path
|
|
189
|
+
# @param volume_number [Integer] Volume number (1-based)
|
|
190
|
+
# @param file_indices [Array<Integer>] File indices to include
|
|
191
|
+
# @param prepared_files [Array<Hash>] All prepared files
|
|
192
|
+
# @param is_last [Boolean] Is this the last volume?
|
|
193
|
+
# @return [void]
|
|
194
|
+
def write_volume(volume_path, volume_number, file_indices,
|
|
195
|
+
prepared_files, is_last)
|
|
196
|
+
writer = VolumeWriter.new(volume_path,
|
|
197
|
+
volume_number: volume_number, is_last: is_last)
|
|
198
|
+
|
|
199
|
+
writer.write do |vol|
|
|
200
|
+
vol.write_signature
|
|
201
|
+
vol.write_main_header
|
|
202
|
+
|
|
203
|
+
file_indices.each do |idx|
|
|
204
|
+
prepared = prepared_files[idx]
|
|
205
|
+
vol.write_file_data(prepared[:header],
|
|
206
|
+
prepared[:compressed_data])
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
vol.write_end_header
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Select compression method
|
|
214
|
+
#
|
|
215
|
+
# @param data [String] File data
|
|
216
|
+
# @return [Integer] Compression method ID
|
|
217
|
+
def select_compression_method(data)
|
|
218
|
+
case @compression_options[:compression]
|
|
219
|
+
when :store
|
|
220
|
+
Compression::Store::METHOD
|
|
221
|
+
when :lzma
|
|
222
|
+
level = @compression_options[:level] || 3
|
|
223
|
+
Compression::Lzma.method_id(level)
|
|
224
|
+
when :auto
|
|
225
|
+
if data.bytesize < 1024
|
|
226
|
+
Compression::Store::METHOD
|
|
227
|
+
else
|
|
228
|
+
level = @compression_options[:level] || 3
|
|
229
|
+
Compression::Lzma.method_id(level)
|
|
230
|
+
end
|
|
231
|
+
else
|
|
232
|
+
Compression::Store::METHOD
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Compress data
|
|
237
|
+
#
|
|
238
|
+
# @param data [String] Data to compress
|
|
239
|
+
# @param method [Integer] Compression method ID
|
|
240
|
+
# @return [String] Compressed data
|
|
241
|
+
def compress_data(data, method)
|
|
242
|
+
if method == Compression::Store::METHOD
|
|
243
|
+
Compression::Store.compress(data)
|
|
244
|
+
else
|
|
245
|
+
level = method.clamp(1, 5)
|
|
246
|
+
Compression::Lzma.compress(data, level: level)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Estimate total archive size
|
|
251
|
+
#
|
|
252
|
+
# @return [Integer] Estimated size in bytes
|
|
253
|
+
def estimate_total_size
|
|
254
|
+
# Rough estimate: sum of file sizes + overhead
|
|
255
|
+
file_sizes = @files.sum { |f| f[:size] }
|
|
256
|
+
overhead = 1024 * @files.size # Header overhead per file
|
|
257
|
+
file_sizes + overhead
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Omnizip
|
|
4
|
+
module Formats
|
|
5
|
+
module Rar
|
|
6
|
+
module Rar5
|
|
7
|
+
module MultiVolume
|
|
8
|
+
# Volume splitter for multi-volume archives
|
|
9
|
+
#
|
|
10
|
+
# This class handles splitting compressed data streams across
|
|
11
|
+
# multiple volumes while respecting size boundaries and file atomicity.
|
|
12
|
+
#
|
|
13
|
+
# @example Split data across volumes
|
|
14
|
+
# splitter = VolumeSplitter.new(max_volume_size: 10_485_760)
|
|
15
|
+
# splitter.start_volume(1)
|
|
16
|
+
# splitter.write_to_current_volume(file1_data)
|
|
17
|
+
# if !splitter.can_fit_in_current_volume?(file2_data.bytesize)
|
|
18
|
+
# splitter.finalize_volume
|
|
19
|
+
# splitter.start_volume(2)
|
|
20
|
+
# end
|
|
21
|
+
class VolumeSplitter
|
|
22
|
+
# @return [Integer] Maximum size per volume in bytes
|
|
23
|
+
attr_reader :max_volume_size
|
|
24
|
+
|
|
25
|
+
# @return [Integer] Current volume number (1-based)
|
|
26
|
+
attr_reader :current_volume_number
|
|
27
|
+
|
|
28
|
+
# @return [Integer] Bytes written to current volume
|
|
29
|
+
attr_reader :current_volume_bytes
|
|
30
|
+
|
|
31
|
+
# @return [Array<Hash>] Volume metadata
|
|
32
|
+
attr_reader :volumes
|
|
33
|
+
|
|
34
|
+
# Minimum space reserved for headers (signature + main + end headers)
|
|
35
|
+
HEADER_OVERHEAD = 1024
|
|
36
|
+
|
|
37
|
+
# Initialize volume splitter
|
|
38
|
+
#
|
|
39
|
+
# @param max_volume_size [Integer] Maximum size per volume in bytes
|
|
40
|
+
def initialize(max_volume_size:)
|
|
41
|
+
@max_volume_size = max_volume_size
|
|
42
|
+
@current_volume_number = 0
|
|
43
|
+
@current_volume_bytes = 0
|
|
44
|
+
@current_volume_data = []
|
|
45
|
+
@volumes = []
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Start a new volume
|
|
49
|
+
#
|
|
50
|
+
# @param volume_number [Integer] Volume number (1-based)
|
|
51
|
+
# @return [void]
|
|
52
|
+
def start_volume(volume_number)
|
|
53
|
+
@current_volume_number = volume_number
|
|
54
|
+
@current_volume_bytes = HEADER_OVERHEAD # Reserve space for headers
|
|
55
|
+
@current_volume_data = []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Check if data can fit in current volume
|
|
59
|
+
#
|
|
60
|
+
# @param data_size [Integer] Size of data to write
|
|
61
|
+
# @return [Boolean] true if data fits, false if new volume needed
|
|
62
|
+
def can_fit_in_current_volume?(data_size)
|
|
63
|
+
(@current_volume_bytes + data_size) <= @max_volume_size
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Get remaining space in current volume
|
|
67
|
+
#
|
|
68
|
+
# @return [Integer] Bytes available
|
|
69
|
+
def remaining_space
|
|
70
|
+
@max_volume_size - @current_volume_bytes
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Write data to current volume
|
|
74
|
+
#
|
|
75
|
+
# @param data [String] Data to write
|
|
76
|
+
# @return [void]
|
|
77
|
+
# @raise [RuntimeError] if no volume is active
|
|
78
|
+
# @raise [RuntimeError] if data doesn't fit
|
|
79
|
+
def write_to_current_volume(data)
|
|
80
|
+
raise "No active volume" if @current_volume_number.zero?
|
|
81
|
+
raise "Data doesn't fit in current volume" unless can_fit_in_current_volume?(data.bytesize)
|
|
82
|
+
|
|
83
|
+
@current_volume_data << data
|
|
84
|
+
@current_volume_bytes += data.bytesize
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Finalize current volume
|
|
88
|
+
#
|
|
89
|
+
# @return [Hash] Volume metadata
|
|
90
|
+
def finalize_volume
|
|
91
|
+
volume_info = {
|
|
92
|
+
number: @current_volume_number,
|
|
93
|
+
size: @current_volume_bytes,
|
|
94
|
+
data: @current_volume_data.join,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@volumes << volume_info
|
|
98
|
+
volume_info
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Calculate optimal file distribution across volumes
|
|
102
|
+
#
|
|
103
|
+
# This method determines how to distribute files across volumes
|
|
104
|
+
# to minimize volume count while respecting atomicity.
|
|
105
|
+
#
|
|
106
|
+
# @param files [Array<Hash>] File information with :compressed_size
|
|
107
|
+
# @return [Array<Array<Integer>>] Volume assignments (file indices per volume)
|
|
108
|
+
def calculate_file_distribution(files)
|
|
109
|
+
distribution = []
|
|
110
|
+
current_volume_files = []
|
|
111
|
+
current_volume_used = HEADER_OVERHEAD
|
|
112
|
+
|
|
113
|
+
files.each_with_index do |file, idx|
|
|
114
|
+
file_size = file[:compressed_size] + file[:header_size]
|
|
115
|
+
|
|
116
|
+
# Check if file fits in current volume
|
|
117
|
+
if (current_volume_used + file_size) <= @max_volume_size
|
|
118
|
+
current_volume_files << idx
|
|
119
|
+
current_volume_used += file_size
|
|
120
|
+
else
|
|
121
|
+
# Start new volume
|
|
122
|
+
distribution << current_volume_files unless current_volume_files.empty?
|
|
123
|
+
current_volume_files = [idx]
|
|
124
|
+
current_volume_used = HEADER_OVERHEAD + file_size
|
|
125
|
+
|
|
126
|
+
# Check if single file exceeds volume size
|
|
127
|
+
if current_volume_used > @max_volume_size
|
|
128
|
+
# File must span multiple volumes (not implemented yet)
|
|
129
|
+
# For now, just place it in its own volume
|
|
130
|
+
distribution << [idx]
|
|
131
|
+
current_volume_files = []
|
|
132
|
+
current_volume_used = HEADER_OVERHEAD
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Add final volume if not empty
|
|
138
|
+
distribution << current_volume_files unless current_volume_files.empty?
|
|
139
|
+
|
|
140
|
+
distribution
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Check if archive needs splitting
|
|
144
|
+
#
|
|
145
|
+
# @param total_size [Integer] Total archive size
|
|
146
|
+
# @return [Boolean] true if splitting needed
|
|
147
|
+
def self.needs_splitting?(total_size, max_volume_size)
|
|
148
|
+
total_size > max_volume_size
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../header"
|
|
4
|
+
|
|
5
|
+
module Omnizip
|
|
6
|
+
module Formats
|
|
7
|
+
module Rar
|
|
8
|
+
module Rar5
|
|
9
|
+
module MultiVolume
|
|
10
|
+
# Volume writer for multi-volume archives
|
|
11
|
+
#
|
|
12
|
+
# This class writes individual .rar volume files with proper
|
|
13
|
+
# RAR5 headers and volume-specific flags.
|
|
14
|
+
#
|
|
15
|
+
# @example Write a volume
|
|
16
|
+
# writer = VolumeWriter.new('archive.part1.rar', volume_number: 1, is_last: false)
|
|
17
|
+
# writer.write_signature
|
|
18
|
+
# writer.write_main_header
|
|
19
|
+
# writer.write_file_data(file_header, compressed_data)
|
|
20
|
+
# writer.write_end_header
|
|
21
|
+
# writer.close
|
|
22
|
+
class VolumeWriter
|
|
23
|
+
# Main header flags for volume archives
|
|
24
|
+
# NOTE: Bits 0-1 are reserved for common flags (EXTRA_AREA, DATA_AREA)
|
|
25
|
+
# Use bits 2+ for format-specific flags
|
|
26
|
+
VOLUME_ARCHIVE_FLAG = 0x0004 # Bit 2: This is a volume archive
|
|
27
|
+
VOLUME_NUMBER_FLAG = 0x0008 # Bit 3: Volume number present in extra area
|
|
28
|
+
|
|
29
|
+
# End header flags
|
|
30
|
+
VOLUME_END_FLAG = 0x0001 # Not the last volume (more volumes follow)
|
|
31
|
+
|
|
32
|
+
# @return [String] Volume file path
|
|
33
|
+
attr_reader :path
|
|
34
|
+
|
|
35
|
+
# @return [Integer] Volume number (1-based)
|
|
36
|
+
attr_reader :volume_number
|
|
37
|
+
|
|
38
|
+
# @return [Boolean] Is this the last volume?
|
|
39
|
+
attr_reader :is_last
|
|
40
|
+
|
|
41
|
+
# @return [IO] File handle
|
|
42
|
+
attr_reader :io
|
|
43
|
+
|
|
44
|
+
# Initialize volume writer
|
|
45
|
+
#
|
|
46
|
+
# @param path [String] Output volume file path
|
|
47
|
+
# @param volume_number [Integer] Volume number (1-based)
|
|
48
|
+
# @param is_last [Boolean] Is this the last volume?
|
|
49
|
+
def initialize(path, volume_number:, is_last: false)
|
|
50
|
+
@path = path
|
|
51
|
+
@volume_number = volume_number
|
|
52
|
+
@is_last = is_last
|
|
53
|
+
@io = nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Open volume file for writing
|
|
57
|
+
#
|
|
58
|
+
# @return [void]
|
|
59
|
+
def open
|
|
60
|
+
@io = File.open(@path, "wb")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Close volume file
|
|
64
|
+
#
|
|
65
|
+
# @return [void]
|
|
66
|
+
def close
|
|
67
|
+
@io&.close
|
|
68
|
+
@io = nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Write with automatic open/close
|
|
72
|
+
#
|
|
73
|
+
# @yield [writer] Yields self for writing operations
|
|
74
|
+
# @return [void]
|
|
75
|
+
def write
|
|
76
|
+
open
|
|
77
|
+
yield self
|
|
78
|
+
ensure
|
|
79
|
+
close
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Write RAR5 signature
|
|
83
|
+
#
|
|
84
|
+
# @return [void]
|
|
85
|
+
def write_signature
|
|
86
|
+
raise "Volume not open" unless @io
|
|
87
|
+
|
|
88
|
+
# RAR5 signature: "Rar!\x1A\x07\x01\x00"
|
|
89
|
+
signature = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01,
|
|
90
|
+
0x00].pack("C*")
|
|
91
|
+
@io.write(signature)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Write Main header with volume flags
|
|
95
|
+
#
|
|
96
|
+
# @return [void]
|
|
97
|
+
def write_main_header
|
|
98
|
+
raise "Volume not open" unless @io
|
|
99
|
+
|
|
100
|
+
# Set volume-specific flags
|
|
101
|
+
flags = VOLUME_ARCHIVE_FLAG
|
|
102
|
+
|
|
103
|
+
# Add volume number in extra area for volumes 2+
|
|
104
|
+
extra_area = nil
|
|
105
|
+
if @volume_number > 1
|
|
106
|
+
flags |= VOLUME_NUMBER_FLAG
|
|
107
|
+
# Volume number as VINT in extra area
|
|
108
|
+
extra_area = VINT.encode(@volume_number).pack("C*")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
header = MainHeader.new(flags: flags)
|
|
112
|
+
|
|
113
|
+
# Manually add extra area if needed
|
|
114
|
+
if extra_area
|
|
115
|
+
# We need to modify the header to include extra area
|
|
116
|
+
# For now, use basic header without extra area (simplified)
|
|
117
|
+
# TODO: Enhance MainHeader to support extra_area parameter
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
@io.write(header.encode)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Write file data (header + compressed data)
|
|
124
|
+
#
|
|
125
|
+
# @param file_header [FileHeader] File header
|
|
126
|
+
# @param compressed_data [String] Compressed file data
|
|
127
|
+
# @return [void]
|
|
128
|
+
def write_file_data(file_header, compressed_data)
|
|
129
|
+
raise "Volume not open" unless @io
|
|
130
|
+
|
|
131
|
+
@io.write(file_header.encode)
|
|
132
|
+
@io.write(compressed_data)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Write End header with volume flags
|
|
136
|
+
#
|
|
137
|
+
# @return [void]
|
|
138
|
+
def write_end_header
|
|
139
|
+
raise "Volume not open" unless @io
|
|
140
|
+
|
|
141
|
+
# Set END_OF_ARCHIVE flags
|
|
142
|
+
flags = 0
|
|
143
|
+
flags | VOLUME_END_FLAG unless @is_last
|
|
144
|
+
|
|
145
|
+
header = EndHeader.new
|
|
146
|
+
# Note: EndHeader doesn't support custom flags yet
|
|
147
|
+
# For v0.5.0, we'll use basic end header
|
|
148
|
+
# TODO: Enhance EndHeader to support volume flags
|
|
149
|
+
|
|
150
|
+
@io.write(header.encode)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Generate volume filename from base name
|
|
154
|
+
#
|
|
155
|
+
# @param base_path [String] Base archive path (e.g., "archive.rar")
|
|
156
|
+
# @param volume_number [Integer] Volume number (1-based)
|
|
157
|
+
# @param naming [String] Naming pattern ("part" or "volume")
|
|
158
|
+
# @return [String] Volume filename (e.g., "archive.part1.rar")
|
|
159
|
+
def self.volume_filename(base_path, volume_number, naming: "part")
|
|
160
|
+
dir = File.dirname(base_path)
|
|
161
|
+
basename = File.basename(base_path, ".*")
|
|
162
|
+
ext = File.extname(base_path)
|
|
163
|
+
|
|
164
|
+
volume_name = case naming
|
|
165
|
+
when "part"
|
|
166
|
+
"#{basename}.part#{volume_number}#{ext}"
|
|
167
|
+
when "volume"
|
|
168
|
+
"#{basename}.vol#{volume_number}#{ext}"
|
|
169
|
+
when "numeric"
|
|
170
|
+
# Simple numeric suffix: archive.rar, archive.r00, archive.r01, etc.
|
|
171
|
+
if volume_number == 1
|
|
172
|
+
"#{basename}#{ext}"
|
|
173
|
+
else
|
|
174
|
+
# Volume 2 => r00, Volume 3 => r01, etc.
|
|
175
|
+
"#{basename}.r#{format('%02d',
|
|
176
|
+
volume_number - 2)}"
|
|
177
|
+
end
|
|
178
|
+
else
|
|
179
|
+
"#{basename}.part#{volume_number}#{ext}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Handle relative vs absolute paths
|
|
183
|
+
if dir == "."
|
|
184
|
+
volume_name
|
|
185
|
+
else
|
|
186
|
+
File.join(dir, volume_name)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|