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.
Files changed (511) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +32 -0
  4. data/.rubocop_todo.yml +754 -0
  5. data/COPYING +502 -0
  6. data/Gemfile +17 -0
  7. data/LICENSE +12 -0
  8. data/README.adoc +1045 -0
  9. data/Rakefile +12 -0
  10. data/benchmark/README.md +260 -0
  11. data/benchmark/benchmark_suite.rb +125 -0
  12. data/benchmark/compression_bench.rb +181 -0
  13. data/benchmark/filter_bench.rb +180 -0
  14. data/benchmark/models/benchmark_result.rb +59 -0
  15. data/benchmark/models/comparison_result.rb +69 -0
  16. data/benchmark/profile_suite.rb +167 -0
  17. data/benchmark/reporter.rb +150 -0
  18. data/benchmark/run_benchmarks.rb +66 -0
  19. data/benchmark/test_data.rb +137 -0
  20. data/config/formats/rar3_spec.yml +91 -0
  21. data/config/formats/rar5_spec.yml +102 -0
  22. data/docs/.github/workflows/docs.yml +142 -0
  23. data/docs/.gitignore +21 -0
  24. data/docs/.lychee.toml +67 -0
  25. data/docs/Gemfile +13 -0
  26. data/docs/RAR_WRITE_SUPPORT.md +26 -0
  27. data/docs/README.md +101 -0
  28. data/docs/_config.yml +112 -0
  29. data/docs/assets/logo.svg +1 -0
  30. data/docs/assets/omnizip-logo.pdf +1540 -11
  31. data/docs/comparison/feature-matrix.adoc +694 -0
  32. data/docs/comparison/index.adoc +113 -0
  33. data/docs/comparison/vs-7zip.adoc +309 -0
  34. data/docs/comparison/vs-peazip.adoc +77 -0
  35. data/docs/comparison/vs-rubyzip.adoc +342 -0
  36. data/docs/comparison/vs-winrar.adoc +100 -0
  37. data/docs/compatibility.adoc +579 -0
  38. data/docs/concepts/index.adoc +129 -0
  39. data/docs/developer/architecture.adoc +256 -0
  40. data/docs/developer/contributing.adoc +158 -0
  41. data/docs/developer/index.adoc +25 -0
  42. data/docs/developer/testing.adoc +212 -0
  43. data/docs/getting-started/basic-usage.adoc +271 -0
  44. data/docs/getting-started/index.adoc +42 -0
  45. data/docs/getting-started/installation.adoc +138 -0
  46. data/docs/getting-started/quick-start.adoc +185 -0
  47. data/docs/getting-started/your-first-archive.adoc +218 -0
  48. data/docs/guides/advanced-features/encryption.adoc +300 -0
  49. data/docs/guides/advanced-features/index.adoc +49 -0
  50. data/docs/guides/advanced-features/parallel-processing.adoc +246 -0
  51. data/docs/guides/advanced-features/progress-tracking.adoc +320 -0
  52. data/docs/guides/advanced-features/streaming.adoc +212 -0
  53. data/docs/guides/archive-formats/gzip-format.adoc +107 -0
  54. data/docs/guides/archive-formats/index.adoc +130 -0
  55. data/docs/guides/archive-formats/rar-format.adoc +104 -0
  56. data/docs/guides/archive-formats/rar5.adoc +521 -0
  57. data/docs/guides/archive-formats/seven-zip-format.adoc +35 -0
  58. data/docs/guides/archive-formats/tar-format.adoc +106 -0
  59. data/docs/guides/archive-formats/xz-format.adoc +118 -0
  60. data/docs/guides/archive-formats/zip-format.adoc +35 -0
  61. data/docs/guides/compression-algorithms/bzip2.adoc +113 -0
  62. data/docs/guides/compression-algorithms/deflate.adoc +319 -0
  63. data/docs/guides/compression-algorithms/index.adoc +190 -0
  64. data/docs/guides/compression-algorithms/lzma.adoc +398 -0
  65. data/docs/guides/compression-algorithms/lzma2.adoc +327 -0
  66. data/docs/guides/compression-algorithms/ppmd.adoc +316 -0
  67. data/docs/guides/compression-algorithms/zstandard.adoc +361 -0
  68. data/docs/guides/creating-archives.adoc +354 -0
  69. data/docs/guides/extracting-archives.adoc +53 -0
  70. data/docs/guides/format-conversion.adoc +64 -0
  71. data/docs/guides/index.adoc +49 -0
  72. data/docs/guides/migration-rubyzip.adoc +217 -0
  73. data/docs/guides/parity-archives.adoc +605 -0
  74. data/docs/guides/performance-tuning.adoc +88 -0
  75. data/docs/index.adoc +218 -0
  76. data/docs/lychee.toml +67 -0
  77. data/docs/reference/api/overview.adoc +188 -0
  78. data/docs/reference/cli/compress-command.adoc +114 -0
  79. data/docs/reference/cli/overview.adoc +140 -0
  80. data/docs/reference/index.adoc +26 -0
  81. data/docs/resources/faq.adoc +185 -0
  82. data/docs/resources/quick-reference.adoc +222 -0
  83. data/docs/troubleshooting/index.adoc +208 -0
  84. data/examples/api_comparison.rb +205 -0
  85. data/examples/deflate64_example.rb +96 -0
  86. data/examples/par2_demo.rb +121 -0
  87. data/examples/quick_start_native.rb +150 -0
  88. data/examples/quick_start_rubyzip.rb +115 -0
  89. data/examples/rubyzip_compatibility_demo.rb +194 -0
  90. data/exe/omnizip +27 -0
  91. data/lib/omnizip/algorithm.rb +130 -0
  92. data/lib/omnizip/algorithm_registry.rb +86 -0
  93. data/lib/omnizip/algorithms/.keep +0 -0
  94. data/lib/omnizip/algorithms/bzip2/bwt.rb +225 -0
  95. data/lib/omnizip/algorithms/bzip2/decoder.rb +193 -0
  96. data/lib/omnizip/algorithms/bzip2/encoder.rb +237 -0
  97. data/lib/omnizip/algorithms/bzip2/huffman.rb +206 -0
  98. data/lib/omnizip/algorithms/bzip2/mtf.rb +101 -0
  99. data/lib/omnizip/algorithms/bzip2/rle.rb +151 -0
  100. data/lib/omnizip/algorithms/bzip2.rb +130 -0
  101. data/lib/omnizip/algorithms/deflate/constants.rb +28 -0
  102. data/lib/omnizip/algorithms/deflate/decoder.rb +38 -0
  103. data/lib/omnizip/algorithms/deflate/encoder.rb +46 -0
  104. data/lib/omnizip/algorithms/deflate.rb +128 -0
  105. data/lib/omnizip/algorithms/deflate64/constants.rb +45 -0
  106. data/lib/omnizip/algorithms/deflate64/decoder.rb +153 -0
  107. data/lib/omnizip/algorithms/deflate64/encoder.rb +98 -0
  108. data/lib/omnizip/algorithms/deflate64/huffman_coder.rb +354 -0
  109. data/lib/omnizip/algorithms/deflate64/lz77_encoder.rb +142 -0
  110. data/lib/omnizip/algorithms/deflate64.rb +109 -0
  111. data/lib/omnizip/algorithms/lzma/bit_model.rb +120 -0
  112. data/lib/omnizip/algorithms/lzma/constants.rb +112 -0
  113. data/lib/omnizip/algorithms/lzma/decoder.rb +148 -0
  114. data/lib/omnizip/algorithms/lzma/dictionary.rb +69 -0
  115. data/lib/omnizip/algorithms/lzma/distance_coder.rb +415 -0
  116. data/lib/omnizip/algorithms/lzma/encoder.rb +142 -0
  117. data/lib/omnizip/algorithms/lzma/length_coder.rb +260 -0
  118. data/lib/omnizip/algorithms/lzma/literal_decoder.rb +320 -0
  119. data/lib/omnizip/algorithms/lzma/literal_encoder.rb +210 -0
  120. data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +341 -0
  121. data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +192 -0
  122. data/lib/omnizip/algorithms/lzma/lzma_state.rb +128 -0
  123. data/lib/omnizip/algorithms/lzma/match.rb +32 -0
  124. data/lib/omnizip/algorithms/lzma/match_finder.rb +205 -0
  125. data/lib/omnizip/algorithms/lzma/match_finder_config.rb +142 -0
  126. data/lib/omnizip/algorithms/lzma/match_finder_factory.rb +88 -0
  127. data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +130 -0
  128. data/lib/omnizip/algorithms/lzma/probability_models.rb +72 -0
  129. data/lib/omnizip/algorithms/lzma/range_coder.rb +85 -0
  130. data/lib/omnizip/algorithms/lzma/range_decoder.rb +434 -0
  131. data/lib/omnizip/algorithms/lzma/range_encoder.rb +194 -0
  132. data/lib/omnizip/algorithms/lzma/state.rb +127 -0
  133. data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +325 -0
  134. data/lib/omnizip/algorithms/lzma/xz_encoder.rb +426 -0
  135. data/lib/omnizip/algorithms/lzma/xz_encoder_fast.rb +645 -0
  136. data/lib/omnizip/algorithms/lzma/xz_match_finder_adapter.rb +227 -0
  137. data/lib/omnizip/algorithms/lzma/xz_price_calculator.rb +169 -0
  138. data/lib/omnizip/algorithms/lzma/xz_probability_models.rb +261 -0
  139. data/lib/omnizip/algorithms/lzma/xz_range_encoder.rb +223 -0
  140. data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +331 -0
  141. data/lib/omnizip/algorithms/lzma/xz_state.rb +116 -0
  142. data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +2055 -0
  143. data/lib/omnizip/algorithms/lzma.rb +238 -0
  144. data/lib/omnizip/algorithms/lzma2/chunk_manager.rb +182 -0
  145. data/lib/omnizip/algorithms/lzma2/constants.rb +41 -0
  146. data/lib/omnizip/algorithms/lzma2/encoder.rb +147 -0
  147. data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +161 -0
  148. data/lib/omnizip/algorithms/lzma2/properties.rb +179 -0
  149. data/lib/omnizip/algorithms/lzma2/simple_lzma2_encoder.rb +127 -0
  150. data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +85 -0
  151. data/lib/omnizip/algorithms/lzma2.rb +141 -0
  152. data/lib/omnizip/algorithms/ppmd7/constants.rb +74 -0
  153. data/lib/omnizip/algorithms/ppmd7/context.rb +154 -0
  154. data/lib/omnizip/algorithms/ppmd7/decoder.rb +126 -0
  155. data/lib/omnizip/algorithms/ppmd7/encoder.rb +163 -0
  156. data/lib/omnizip/algorithms/ppmd7/model.rb +248 -0
  157. data/lib/omnizip/algorithms/ppmd7/symbol_state.rb +57 -0
  158. data/lib/omnizip/algorithms/ppmd7.rb +116 -0
  159. data/lib/omnizip/algorithms/ppmd8/constants.rb +61 -0
  160. data/lib/omnizip/algorithms/ppmd8/context.rb +34 -0
  161. data/lib/omnizip/algorithms/ppmd8/decoder.rb +107 -0
  162. data/lib/omnizip/algorithms/ppmd8/encoder.rb +138 -0
  163. data/lib/omnizip/algorithms/ppmd8/model.rb +250 -0
  164. data/lib/omnizip/algorithms/ppmd8/restoration_method.rb +78 -0
  165. data/lib/omnizip/algorithms/ppmd8.rb +82 -0
  166. data/lib/omnizip/algorithms/ppmd_base.rb +138 -0
  167. data/lib/omnizip/algorithms/sevenzip_lzma2.rb +123 -0
  168. data/lib/omnizip/algorithms/xz_lzma2.rb +118 -0
  169. data/lib/omnizip/algorithms/zstandard/constants.rb +25 -0
  170. data/lib/omnizip/algorithms/zstandard/decoder.rb +46 -0
  171. data/lib/omnizip/algorithms/zstandard/encoder.rb +51 -0
  172. data/lib/omnizip/algorithms/zstandard.rb +138 -0
  173. data/lib/omnizip/buffer/memory_archive.rb +251 -0
  174. data/lib/omnizip/buffer/memory_extractor.rb +224 -0
  175. data/lib/omnizip/buffer.rb +176 -0
  176. data/lib/omnizip/checksum_registry.rb +114 -0
  177. data/lib/omnizip/checksums/crc32.rb +100 -0
  178. data/lib/omnizip/checksums/crc64.rb +101 -0
  179. data/lib/omnizip/checksums/crc_base.rb +158 -0
  180. data/lib/omnizip/checksums/verifier.rb +131 -0
  181. data/lib/omnizip/chunked/memory_manager.rb +194 -0
  182. data/lib/omnizip/chunked/reader.rb +78 -0
  183. data/lib/omnizip/chunked/writer.rb +120 -0
  184. data/lib/omnizip/chunked.rb +129 -0
  185. data/lib/omnizip/cli/output_formatter.rb +104 -0
  186. data/lib/omnizip/cli.rb +572 -0
  187. data/lib/omnizip/commands/.keep +0 -0
  188. data/lib/omnizip/commands/archive_create_command.rb +427 -0
  189. data/lib/omnizip/commands/archive_extract_command.rb +272 -0
  190. data/lib/omnizip/commands/archive_list_command.rb +218 -0
  191. data/lib/omnizip/commands/archive_repair_command.rb +131 -0
  192. data/lib/omnizip/commands/archive_verify_command.rb +117 -0
  193. data/lib/omnizip/commands/compress_command.rb +117 -0
  194. data/lib/omnizip/commands/decompress_command.rb +120 -0
  195. data/lib/omnizip/commands/list_command.rb +53 -0
  196. data/lib/omnizip/commands/metadata_command.rb +153 -0
  197. data/lib/omnizip/commands/parity_create_command.rb +122 -0
  198. data/lib/omnizip/commands/parity_repair_command.rb +122 -0
  199. data/lib/omnizip/commands/parity_verify_command.rb +124 -0
  200. data/lib/omnizip/commands/profile_list_command.rb +56 -0
  201. data/lib/omnizip/commands/profile_show_command.rb +44 -0
  202. data/lib/omnizip/convenience.rb +359 -0
  203. data/lib/omnizip/converter/conversion_registry.rb +49 -0
  204. data/lib/omnizip/converter/conversion_strategy.rb +121 -0
  205. data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +97 -0
  206. data/lib/omnizip/converter/zip_to_seven_zip_strategy.rb +112 -0
  207. data/lib/omnizip/converter.rb +105 -0
  208. data/lib/omnizip/crypto/aes256/cipher.rb +100 -0
  209. data/lib/omnizip/crypto/aes256/constants.rb +28 -0
  210. data/lib/omnizip/crypto/aes256/key_derivation.rb +101 -0
  211. data/lib/omnizip/crypto/aes256.rb +102 -0
  212. data/lib/omnizip/error.rb +106 -0
  213. data/lib/omnizip/eta/exponential_smoothing_estimator.rb +98 -0
  214. data/lib/omnizip/eta/moving_average_estimator.rb +99 -0
  215. data/lib/omnizip/eta/rate_calculator.rb +104 -0
  216. data/lib/omnizip/eta/sample_history.rb +143 -0
  217. data/lib/omnizip/eta/time_estimator.rb +106 -0
  218. data/lib/omnizip/eta.rb +63 -0
  219. data/lib/omnizip/extraction/filter_chain.rb +177 -0
  220. data/lib/omnizip/extraction/glob_pattern.rb +140 -0
  221. data/lib/omnizip/extraction/pattern_matcher.rb +70 -0
  222. data/lib/omnizip/extraction/predicate_pattern.rb +52 -0
  223. data/lib/omnizip/extraction/regex_pattern.rb +50 -0
  224. data/lib/omnizip/extraction/selective_extractor.rb +240 -0
  225. data/lib/omnizip/extraction.rb +111 -0
  226. data/lib/omnizip/file_type/mime_classifier.rb +144 -0
  227. data/lib/omnizip/file_type.rb +113 -0
  228. data/lib/omnizip/filter.rb +139 -0
  229. data/lib/omnizip/filter_pipeline.rb +108 -0
  230. data/lib/omnizip/filter_registry.rb +166 -0
  231. data/lib/omnizip/filters/bcj.rb +279 -0
  232. data/lib/omnizip/filters/bcj2/constants.rb +53 -0
  233. data/lib/omnizip/filters/bcj2/decoder.rb +200 -0
  234. data/lib/omnizip/filters/bcj2/encoder.rb +61 -0
  235. data/lib/omnizip/filters/bcj2/stream_data.rb +93 -0
  236. data/lib/omnizip/filters/bcj2.rb +99 -0
  237. data/lib/omnizip/filters/bcj_arm.rb +176 -0
  238. data/lib/omnizip/filters/bcj_arm64.rb +244 -0
  239. data/lib/omnizip/filters/bcj_ia64.rb +196 -0
  240. data/lib/omnizip/filters/bcj_ppc.rb +190 -0
  241. data/lib/omnizip/filters/bcj_sparc.rb +176 -0
  242. data/lib/omnizip/filters/bcj_x86.rb +193 -0
  243. data/lib/omnizip/filters/delta.rb +196 -0
  244. data/lib/omnizip/filters/filter_base.rb +72 -0
  245. data/lib/omnizip/filters/registry.rb +123 -0
  246. data/lib/omnizip/filters/xz_delta.rb +258 -0
  247. data/lib/omnizip/format_detector.rb +162 -0
  248. data/lib/omnizip/format_registry.rb +59 -0
  249. data/lib/omnizip/formats/.keep +0 -0
  250. data/lib/omnizip/formats/bzip2_file.rb +172 -0
  251. data/lib/omnizip/formats/cpio/constants.rb +55 -0
  252. data/lib/omnizip/formats/cpio/entry.rb +385 -0
  253. data/lib/omnizip/formats/cpio/reader.rb +196 -0
  254. data/lib/omnizip/formats/cpio/writer.rb +234 -0
  255. data/lib/omnizip/formats/cpio.rb +140 -0
  256. data/lib/omnizip/formats/format_spec_loader.rb +230 -0
  257. data/lib/omnizip/formats/gzip.rb +238 -0
  258. data/lib/omnizip/formats/iso/directory_builder.rb +297 -0
  259. data/lib/omnizip/formats/iso/directory_record.rb +152 -0
  260. data/lib/omnizip/formats/iso/joliet.rb +204 -0
  261. data/lib/omnizip/formats/iso/path_table.rb +125 -0
  262. data/lib/omnizip/formats/iso/reader.rb +197 -0
  263. data/lib/omnizip/formats/iso/rock_ridge.rb +349 -0
  264. data/lib/omnizip/formats/iso/volume_builder.rb +320 -0
  265. data/lib/omnizip/formats/iso/volume_descriptor.rb +168 -0
  266. data/lib/omnizip/formats/iso/writer.rb +530 -0
  267. data/lib/omnizip/formats/iso.rb +140 -0
  268. data/lib/omnizip/formats/lzip.rb +175 -0
  269. data/lib/omnizip/formats/lzma_alone.rb +171 -0
  270. data/lib/omnizip/formats/rar/archive_repairer.rb +243 -0
  271. data/lib/omnizip/formats/rar/archive_verifier.rb +195 -0
  272. data/lib/omnizip/formats/rar/block_parser.rb +243 -0
  273. data/lib/omnizip/formats/rar/compression/bit_stream.rb +180 -0
  274. data/lib/omnizip/formats/rar/compression/dispatcher.rb +217 -0
  275. data/lib/omnizip/formats/rar/compression/lz77_huffman/decoder.rb +216 -0
  276. data/lib/omnizip/formats/rar/compression/lz77_huffman/encoder.rb +158 -0
  277. data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_builder.rb +217 -0
  278. data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_coder.rb +189 -0
  279. data/lib/omnizip/formats/rar/compression/lz77_huffman/match_finder.rb +135 -0
  280. data/lib/omnizip/formats/rar/compression/lz77_huffman/sliding_window.rb +165 -0
  281. data/lib/omnizip/formats/rar/compression/ppmd/context.rb +105 -0
  282. data/lib/omnizip/formats/rar/compression/ppmd/decoder.rb +219 -0
  283. data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +262 -0
  284. data/lib/omnizip/formats/rar/compression_method_registry.rb +106 -0
  285. data/lib/omnizip/formats/rar/constants.rb +82 -0
  286. data/lib/omnizip/formats/rar/decompressor.rb +238 -0
  287. data/lib/omnizip/formats/rar/external_writer.rb +312 -0
  288. data/lib/omnizip/formats/rar/header.rb +192 -0
  289. data/lib/omnizip/formats/rar/license_validator.rb +109 -0
  290. data/lib/omnizip/formats/rar/models/rar_archive.rb +77 -0
  291. data/lib/omnizip/formats/rar/models/rar_entry.rb +65 -0
  292. data/lib/omnizip/formats/rar/models/rar_volume.rb +56 -0
  293. data/lib/omnizip/formats/rar/parity_handler.rb +292 -0
  294. data/lib/omnizip/formats/rar/rar5/compression/lzma.rb +202 -0
  295. data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +578 -0
  296. data/lib/omnizip/formats/rar/rar5/compression/store.rb +60 -0
  297. data/lib/omnizip/formats/rar/rar5/crc32.rb +39 -0
  298. data/lib/omnizip/formats/rar/rar5/encryption/aes256_cbc.rb +97 -0
  299. data/lib/omnizip/formats/rar/rar5/encryption/encryption_header.rb +114 -0
  300. data/lib/omnizip/formats/rar/rar5/encryption/encryption_manager.rb +166 -0
  301. data/lib/omnizip/formats/rar/rar5/encryption/key_derivation.rb +97 -0
  302. data/lib/omnizip/formats/rar/rar5/header.rb +187 -0
  303. data/lib/omnizip/formats/rar/rar5/models/encryption_options.rb +74 -0
  304. data/lib/omnizip/formats/rar/rar5/models/recovery_options.rb +63 -0
  305. data/lib/omnizip/formats/rar/rar5/models/solid_options.rb +63 -0
  306. data/lib/omnizip/formats/rar/rar5/models/volume_options.rb +74 -0
  307. data/lib/omnizip/formats/rar/rar5/multi_volume/ARCHITECTURE.md +290 -0
  308. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_manager.rb +264 -0
  309. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_splitter.rb +155 -0
  310. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +194 -0
  311. data/lib/omnizip/formats/rar/rar5/solid/solid_encoder.rb +109 -0
  312. data/lib/omnizip/formats/rar/rar5/solid/solid_manager.rb +142 -0
  313. data/lib/omnizip/formats/rar/rar5/solid/solid_stream.rb +121 -0
  314. data/lib/omnizip/formats/rar/rar5/vint.rb +65 -0
  315. data/lib/omnizip/formats/rar/rar5/writer.rb +466 -0
  316. data/lib/omnizip/formats/rar/rar_format_base.rb +241 -0
  317. data/lib/omnizip/formats/rar/reader.rb +366 -0
  318. data/lib/omnizip/formats/rar/recovery_record.rb +245 -0
  319. data/lib/omnizip/formats/rar/volume_manager.rb +168 -0
  320. data/lib/omnizip/formats/rar/writer.rb +431 -0
  321. data/lib/omnizip/formats/rar.rb +205 -0
  322. data/lib/omnizip/formats/rar3/compressor.rb +73 -0
  323. data/lib/omnizip/formats/rar3/decompressor.rb +66 -0
  324. data/lib/omnizip/formats/rar3/reader.rb +386 -0
  325. data/lib/omnizip/formats/rar3/writer.rb +219 -0
  326. data/lib/omnizip/formats/rar5/compressor.rb +73 -0
  327. data/lib/omnizip/formats/rar5/decompressor.rb +66 -0
  328. data/lib/omnizip/formats/rar5/reader.rb +342 -0
  329. data/lib/omnizip/formats/rar5/writer.rb +214 -0
  330. data/lib/omnizip/formats/seven_zip/coder_chain.rb +150 -0
  331. data/lib/omnizip/formats/seven_zip/constants.rb +126 -0
  332. data/lib/omnizip/formats/seven_zip/encoded_header.rb +114 -0
  333. data/lib/omnizip/formats/seven_zip/encrypted_header.rb +142 -0
  334. data/lib/omnizip/formats/seven_zip/file_collector.rb +144 -0
  335. data/lib/omnizip/formats/seven_zip/header.rb +106 -0
  336. data/lib/omnizip/formats/seven_zip/header_encryptor.rb +134 -0
  337. data/lib/omnizip/formats/seven_zip/header_writer.rb +466 -0
  338. data/lib/omnizip/formats/seven_zip/models/coder_info.rb +30 -0
  339. data/lib/omnizip/formats/seven_zip/models/file_entry.rb +58 -0
  340. data/lib/omnizip/formats/seven_zip/models/folder.rb +69 -0
  341. data/lib/omnizip/formats/seven_zip/models/stream_info.rb +42 -0
  342. data/lib/omnizip/formats/seven_zip/parser.rb +660 -0
  343. data/lib/omnizip/formats/seven_zip/reader.rb +458 -0
  344. data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +632 -0
  345. data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +315 -0
  346. data/lib/omnizip/formats/seven_zip/stream_compressor.rb +151 -0
  347. data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +162 -0
  348. data/lib/omnizip/formats/seven_zip/writer.rb +740 -0
  349. data/lib/omnizip/formats/seven_zip.rb +93 -0
  350. data/lib/omnizip/formats/tar/constants.rb +73 -0
  351. data/lib/omnizip/formats/tar/entry.rb +94 -0
  352. data/lib/omnizip/formats/tar/header.rb +168 -0
  353. data/lib/omnizip/formats/tar/reader.rb +121 -0
  354. data/lib/omnizip/formats/tar/writer.rb +216 -0
  355. data/lib/omnizip/formats/tar.rb +84 -0
  356. data/lib/omnizip/formats/xz/reader.rb +116 -0
  357. data/lib/omnizip/formats/xz.rb +237 -0
  358. data/lib/omnizip/formats/xz_impl/block_decoder.rb +754 -0
  359. data/lib/omnizip/formats/xz_impl/block_encoder.rb +306 -0
  360. data/lib/omnizip/formats/xz_impl/block_header.rb +210 -0
  361. data/lib/omnizip/formats/xz_impl/block_header_parser.rb +186 -0
  362. data/lib/omnizip/formats/xz_impl/constants.rb +49 -0
  363. data/lib/omnizip/formats/xz_impl/index_decoder.rb +174 -0
  364. data/lib/omnizip/formats/xz_impl/index_encoder.rb +122 -0
  365. data/lib/omnizip/formats/xz_impl/stream_decoder.rb +468 -0
  366. data/lib/omnizip/formats/xz_impl/stream_encoder.rb +99 -0
  367. data/lib/omnizip/formats/xz_impl/stream_footer.rb +81 -0
  368. data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +117 -0
  369. data/lib/omnizip/formats/xz_impl/stream_header.rb +55 -0
  370. data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +108 -0
  371. data/lib/omnizip/formats/xz_impl/vli.rb +128 -0
  372. data/lib/omnizip/formats/xz_impl/writer.rb +421 -0
  373. data/lib/omnizip/formats/zip/central_directory_header.rb +195 -0
  374. data/lib/omnizip/formats/zip/constants.rb +69 -0
  375. data/lib/omnizip/formats/zip/end_of_central_directory.rb +133 -0
  376. data/lib/omnizip/formats/zip/local_file_header.rb +138 -0
  377. data/lib/omnizip/formats/zip/reader.rb +250 -0
  378. data/lib/omnizip/formats/zip/unix_extra_field.rb +153 -0
  379. data/lib/omnizip/formats/zip/writer.rb +375 -0
  380. data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +104 -0
  381. data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +66 -0
  382. data/lib/omnizip/formats/zip/zip64_extra_field.rb +114 -0
  383. data/lib/omnizip/formats/zip.rb +50 -0
  384. data/lib/omnizip/implementations/base/lzma2_decoder_base.rb +75 -0
  385. data/lib/omnizip/implementations/base/lzma2_encoder_base.rb +128 -0
  386. data/lib/omnizip/implementations/base/lzma_decoder_base.rb +83 -0
  387. data/lib/omnizip/implementations/base/lzma_encoder_base.rb +108 -0
  388. data/lib/omnizip/implementations/base/state_machine_base.rb +182 -0
  389. data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +421 -0
  390. data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +465 -0
  391. data/lib/omnizip/implementations/seven_zip/lzma/match_finder.rb +288 -0
  392. data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +200 -0
  393. data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +197 -0
  394. data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +141 -0
  395. data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +519 -0
  396. data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +723 -0
  397. data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +750 -0
  398. data/lib/omnizip/io/buffered_input.rb +146 -0
  399. data/lib/omnizip/io/buffered_output.rb +105 -0
  400. data/lib/omnizip/io/stream_manager.rb +115 -0
  401. data/lib/omnizip/link_handler/hard_link.rb +79 -0
  402. data/lib/omnizip/link_handler/symbolic_link.rb +74 -0
  403. data/lib/omnizip/link_handler.rb +124 -0
  404. data/lib/omnizip/metadata/archive_metadata.rb +114 -0
  405. data/lib/omnizip/metadata/entry_metadata.rb +146 -0
  406. data/lib/omnizip/metadata/metadata_editor.rb +171 -0
  407. data/lib/omnizip/metadata/metadata_registry.rb +64 -0
  408. data/lib/omnizip/metadata/metadata_validator.rb +99 -0
  409. data/lib/omnizip/metadata.rb +57 -0
  410. data/lib/omnizip/models/.keep +0 -0
  411. data/lib/omnizip/models/algorithm_metadata.rb +73 -0
  412. data/lib/omnizip/models/compression_options.rb +71 -0
  413. data/lib/omnizip/models/conversion_options.rb +87 -0
  414. data/lib/omnizip/models/conversion_result.rb +135 -0
  415. data/lib/omnizip/models/eta_result.rb +46 -0
  416. data/lib/omnizip/models/extraction_rule.rb +115 -0
  417. data/lib/omnizip/models/filter_chain.rb +144 -0
  418. data/lib/omnizip/models/filter_config.rb +183 -0
  419. data/lib/omnizip/models/match_result.rb +124 -0
  420. data/lib/omnizip/models/optimization_suggestion.rb +91 -0
  421. data/lib/omnizip/models/parallel_options.rb +104 -0
  422. data/lib/omnizip/models/performance_result.rb +79 -0
  423. data/lib/omnizip/models/profile_report.rb +82 -0
  424. data/lib/omnizip/models/progress_options.rb +38 -0
  425. data/lib/omnizip/models/split_options.rb +116 -0
  426. data/lib/omnizip/optimization_registry.rb +81 -0
  427. data/lib/omnizip/parallel/job_queue.rb +209 -0
  428. data/lib/omnizip/parallel/job_scheduler.rb +203 -0
  429. data/lib/omnizip/parallel/parallel_compressor.rb +347 -0
  430. data/lib/omnizip/parallel/parallel_extractor.rb +329 -0
  431. data/lib/omnizip/parallel/worker_pool.rb +223 -0
  432. data/lib/omnizip/parallel.rb +149 -0
  433. data/lib/omnizip/parity/chunked_block_processor.rb +196 -0
  434. data/lib/omnizip/parity/galois16.rb +145 -0
  435. data/lib/omnizip/parity/models/creator_packet.rb +73 -0
  436. data/lib/omnizip/parity/models/file_description_packet.rb +133 -0
  437. data/lib/omnizip/parity/models/ifsc_packet.rb +123 -0
  438. data/lib/omnizip/parity/models/main_packet.rb +128 -0
  439. data/lib/omnizip/parity/models/packet.rb +156 -0
  440. data/lib/omnizip/parity/models/packet_registry.rb +109 -0
  441. data/lib/omnizip/parity/models/recovery_slice_packet.rb +78 -0
  442. data/lib/omnizip/parity/par2_creator.rb +531 -0
  443. data/lib/omnizip/parity/par2_repairer.rb +407 -0
  444. data/lib/omnizip/parity/par2_verifier.rb +364 -0
  445. data/lib/omnizip/parity/par2cmdline_algorithm.rb +110 -0
  446. data/lib/omnizip/parity/par2cmdline_coefficients.rb +78 -0
  447. data/lib/omnizip/parity/reed_solomon_decoder.rb +266 -0
  448. data/lib/omnizip/parity/reed_solomon_encoder.rb +111 -0
  449. data/lib/omnizip/parity/reed_solomon_matrix.rb +342 -0
  450. data/lib/omnizip/parity.rb +186 -0
  451. data/lib/omnizip/password/encryption_registry.rb +65 -0
  452. data/lib/omnizip/password/encryption_strategy.rb +96 -0
  453. data/lib/omnizip/password/password_validator.rb +129 -0
  454. data/lib/omnizip/password/winzip_aes_strategy.rb +192 -0
  455. data/lib/omnizip/password/zip_crypto_strategy.rb +141 -0
  456. data/lib/omnizip/password.rb +87 -0
  457. data/lib/omnizip/pipe/stream_compressor.rb +124 -0
  458. data/lib/omnizip/pipe/stream_decompressor.rb +174 -0
  459. data/lib/omnizip/pipe.rb +121 -0
  460. data/lib/omnizip/platform/ntfs_streams.rb +201 -0
  461. data/lib/omnizip/platform.rb +189 -0
  462. data/lib/omnizip/profile/archive_profile.rb +39 -0
  463. data/lib/omnizip/profile/balanced_profile.rb +33 -0
  464. data/lib/omnizip/profile/binary_profile.rb +36 -0
  465. data/lib/omnizip/profile/compression_profile.rb +158 -0
  466. data/lib/omnizip/profile/custom_profile.rb +157 -0
  467. data/lib/omnizip/profile/fast_profile.rb +33 -0
  468. data/lib/omnizip/profile/maximum_profile.rb +33 -0
  469. data/lib/omnizip/profile/profile_detector.rb +110 -0
  470. data/lib/omnizip/profile/profile_registry.rb +161 -0
  471. data/lib/omnizip/profile/text_profile.rb +36 -0
  472. data/lib/omnizip/profile.rb +190 -0
  473. data/lib/omnizip/profiler/memory_profiler.rb +66 -0
  474. data/lib/omnizip/profiler/method_profiler.rb +49 -0
  475. data/lib/omnizip/profiler/report_generator.rb +169 -0
  476. data/lib/omnizip/profiler.rb +204 -0
  477. data/lib/omnizip/progress/callback_reporter.rb +36 -0
  478. data/lib/omnizip/progress/console_reporter.rb +62 -0
  479. data/lib/omnizip/progress/log_reporter.rb +91 -0
  480. data/lib/omnizip/progress/operation_progress.rb +118 -0
  481. data/lib/omnizip/progress/progress_bar.rb +156 -0
  482. data/lib/omnizip/progress/progress_reporter.rb +40 -0
  483. data/lib/omnizip/progress/progress_tracker.rb +190 -0
  484. data/lib/omnizip/progress/silent_reporter.rb +24 -0
  485. data/lib/omnizip/progress.rb +127 -0
  486. data/lib/omnizip/rubyzip_compat.rb +63 -0
  487. data/lib/omnizip/temp/safe_extract.rb +168 -0
  488. data/lib/omnizip/temp/temp_file.rb +124 -0
  489. data/lib/omnizip/temp/temp_file_pool.rb +109 -0
  490. data/lib/omnizip/temp.rb +181 -0
  491. data/lib/omnizip/version.rb +5 -0
  492. data/lib/omnizip/zip/entry.rb +156 -0
  493. data/lib/omnizip/zip/file.rb +485 -0
  494. data/lib/omnizip/zip/input_stream.rb +273 -0
  495. data/lib/omnizip/zip/output_stream.rb +324 -0
  496. data/lib/omnizip.rb +156 -0
  497. data/readme-docs/advanced-features.adoc +515 -0
  498. data/readme-docs/api-usage.adoc +444 -0
  499. data/readme-docs/architecture.adoc +449 -0
  500. data/readme-docs/archive-formats.adoc +479 -0
  501. data/readme-docs/cli-usage.adoc +222 -0
  502. data/readme-docs/compression-algorithms.adoc +442 -0
  503. data/readme-docs/compression-profiles.adoc +247 -0
  504. data/readme-docs/encryption-checksums.adoc +328 -0
  505. data/readme-docs/format-converter.adoc +325 -0
  506. data/readme-docs/installation.adoc +228 -0
  507. data/readme-docs/par2-archives.adoc +608 -0
  508. data/readme-docs/performance-profiler.adoc +389 -0
  509. data/readme-docs/preprocessing-filters.adoc +280 -0
  510. data/xz-file-format-1.2.1.txt +1174 -0
  511. 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