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