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,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ module Formats
5
+ module Rar
6
+ # RAR format constants
7
+ module Constants
8
+ # RAR signatures
9
+ RAR4_SIGNATURE = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00].freeze
10
+ RAR5_SIGNATURE = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00].freeze
11
+
12
+ # Block types (RAR4)
13
+ BLOCK_MARKER = 0x72
14
+ BLOCK_ARCHIVE = 0x73
15
+ BLOCK_FILE = 0x74
16
+ BLOCK_COMMENT = 0x75
17
+ BLOCK_OLD_EXTRA = 0x76
18
+ BLOCK_OLD_SUBBLOCK = 0x77
19
+ BLOCK_OLD_RECOVERY = 0x78
20
+ BLOCK_OLD_AUTH = 0x79
21
+ BLOCK_SUBBLOCK = 0x7A
22
+ BLOCK_ENDARC = 0x7B
23
+
24
+ # Archive flags
25
+ ARCHIVE_VOLUME = 0x0001
26
+ ARCHIVE_COMMENT = 0x0002
27
+ ARCHIVE_LOCKED = 0x0004
28
+ ARCHIVE_SOLID = 0x0008
29
+ ARCHIVE_NEW_NAMING = 0x0010
30
+ ARCHIVE_AUTH_INFO = 0x0020
31
+ ARCHIVE_RECOVERY = 0x0040
32
+ ARCHIVE_ENCRYPTED = 0x0080
33
+ ARCHIVE_FIRST_VOLUME = 0x0100
34
+
35
+ # File flags
36
+ FILE_SPLIT_BEFORE = 0x0001
37
+ FILE_SPLIT_AFTER = 0x0002
38
+ FILE_ENCRYPTED = 0x0004
39
+ FILE_COMMENT = 0x0008
40
+ FILE_SOLID = 0x0010
41
+ FILE_DIRECTORY = 0x00E0
42
+ FILE_LARGE = 0x0100
43
+ FILE_UNICODE = 0x0200
44
+ FILE_SALT = 0x0400
45
+ FILE_VERSION = 0x0800
46
+ FILE_EXT_TIME = 0x1000
47
+
48
+ # RAR5 header types
49
+ RAR5_HEADER_MAIN = 1
50
+ RAR5_HEADER_FILE = 2
51
+ RAR5_HEADER_SERVICE = 3
52
+ RAR5_HEADER_ENCRYPTION = 4
53
+ RAR5_HEADER_END = 5
54
+
55
+ # RAR5 flags
56
+ RAR5_FLAG_EXTRA_AREA = 0x0001
57
+ RAR5_FLAG_DATA_AREA = 0x0002
58
+ RAR5_FLAG_UNKNOWN_BLOCKS = 0x0004
59
+ RAR5_FLAG_DATA_INHERITED = 0x0008
60
+ RAR5_FLAG_CHILD_BLOCKS = 0x0010
61
+ RAR5_FLAG_IS_DIR = 0x0020
62
+ RAR5_FLAG_MULTI_VOLUME = 0x0001
63
+
64
+ # Compression methods
65
+ METHOD_STORE = 0x30
66
+ METHOD_FASTEST = 0x31
67
+ METHOD_FAST = 0x32
68
+ METHOD_NORMAL = 0x33
69
+ METHOD_GOOD = 0x34
70
+ METHOD_BEST = 0x35
71
+
72
+ # Host OS
73
+ OS_MSDOS = 0
74
+ OS_OS2 = 1
75
+ OS_WIN32 = 2
76
+ OS_UNIX = 3
77
+ OS_MACOS = 4
78
+ OS_BEOS = 5
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
4
+ module Omnizip
5
+ module Formats
6
+ module Rar
7
+ # RAR decompressor wrapper
8
+ # Provides fallback chain: unrar gem → system command → error
9
+ class Decompressor
10
+ class << self
11
+ # Check if RAR decompression is available
12
+ #
13
+ # @return [Boolean] true if available
14
+ def available?
15
+ gem_available? || command_available?
16
+ end
17
+
18
+ # Get decompressor information
19
+ #
20
+ # @return [Hash] Decompressor type and version
21
+ def info
22
+ if gem_available?
23
+ { type: :gem, version: gem_version }
24
+ elsif command_available?
25
+ { type: :command, version: command_version }
26
+ else
27
+ { type: :none, version: nil }
28
+ end
29
+ end
30
+
31
+ # Check if unrar gem is available
32
+ #
33
+ # @return [Boolean] true if gem available
34
+ def gem_available?
35
+ require "unrar"
36
+ true
37
+ rescue LoadError
38
+ false
39
+ end
40
+
41
+ # Check if system unrar command is available
42
+ #
43
+ # @return [Boolean] true if command available
44
+ def command_available?
45
+ !command_path.nil?
46
+ end
47
+
48
+ # Get unrar command path
49
+ #
50
+ # @return [String, nil] Path to unrar or nil
51
+ def command_path
52
+ @command_path ||= find_command
53
+ end
54
+
55
+ # Extract RAR archive to directory
56
+ #
57
+ # @param archive_path [String] Path to RAR archive
58
+ # @param output_dir [String] Output directory
59
+ # @param password [String, nil] Optional password
60
+ # @raise [RuntimeError] if extraction fails
61
+ def extract(archive_path, output_dir, password: nil)
62
+ raise "RAR extraction not available" unless available?
63
+
64
+ if gem_available?
65
+ extract_with_gem(archive_path, output_dir, password)
66
+ elsif command_available?
67
+ extract_with_command(archive_path, output_dir, password)
68
+ else
69
+ raise unsupported_error
70
+ end
71
+ end
72
+
73
+ # List RAR archive contents
74
+ #
75
+ # @param archive_path [String] Path to RAR archive
76
+ # @return [Array<Hash>] Entry information
77
+ # @raise [RuntimeError] if listing fails
78
+ def list(archive_path)
79
+ raise "RAR extraction not available" unless available?
80
+
81
+ if gem_available?
82
+ list_with_gem(archive_path)
83
+ elsif command_available?
84
+ list_with_command(archive_path)
85
+ else
86
+ raise unsupported_error
87
+ end
88
+ end
89
+
90
+ # Extract single entry from RAR
91
+ #
92
+ # @param archive_path [String] Path to RAR archive
93
+ # @param entry_name [String] Entry name
94
+ # @param output_path [String] Output path
95
+ # @param password [String, nil] Optional password
96
+ def extract_entry(archive_path, entry_name, output_path,
97
+ password: nil)
98
+ raise "RAR extraction not available" unless available?
99
+
100
+ if gem_available?
101
+ extract_entry_with_gem(archive_path, entry_name,
102
+ output_path, password)
103
+ elsif command_available?
104
+ extract_entry_with_command(archive_path, entry_name,
105
+ output_path, password)
106
+ else
107
+ raise unsupported_error
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ # Find unrar command
114
+ #
115
+ # @return [String, nil] Command path or nil
116
+ def find_command
117
+ ["unrar", "/usr/bin/unrar", "/usr/local/bin/unrar"].each do |cmd|
118
+ return cmd if system("which #{cmd} > /dev/null 2>&1")
119
+ end
120
+ nil
121
+ end
122
+
123
+ # Get gem version
124
+ #
125
+ # @return [String] Version string
126
+ def gem_version
127
+ require "unrar"
128
+ begin
129
+ Unrar::VERSION
130
+ rescue StandardError
131
+ "unknown"
132
+ end
133
+ end
134
+
135
+ # Get command version
136
+ #
137
+ # @return [String] Version string
138
+ def command_version
139
+ return nil unless command_available?
140
+
141
+ output = `#{command_path} 2>&1`
142
+ output.match(/UNRAR\s+([\d.]+)/i)&.captures&.first || "unknown"
143
+ end
144
+
145
+ # Extract with unrar gem
146
+ def extract_with_gem(archive_path, output_dir, password)
147
+ require "unrar"
148
+ Unrar.extract(archive_path, output_dir, password: password)
149
+ rescue StandardError => e
150
+ raise "Gem extraction failed: #{e.message}"
151
+ end
152
+
153
+ # Extract with system command
154
+ def extract_with_command(archive_path, output_dir, password)
155
+ cmd = build_extract_command(archive_path, output_dir, password)
156
+ return if system(cmd)
157
+
158
+ raise "Command extraction failed: #{archive_path}"
159
+ end
160
+
161
+ # List with unrar gem
162
+ def list_with_gem(archive_path)
163
+ require "unrar"
164
+ archive = Unrar::Archive.new(archive_path)
165
+ archive.list.map do |entry|
166
+ {
167
+ name: entry.filename,
168
+ size: entry.unpacked_size,
169
+ compressed_size: entry.packed_size,
170
+ is_dir: entry.directory?,
171
+ mtime: entry.file_time,
172
+ }
173
+ end
174
+ rescue StandardError => e
175
+ raise "Gem listing failed: #{e.message}"
176
+ end
177
+
178
+ # List with system command
179
+ def list_with_command(archive_path)
180
+ output = `#{command_path} vb "#{archive_path}" 2>&1`
181
+ raise "Command listing failed" unless $CHILD_STATUS.success?
182
+
183
+ output.split("\n").map do |line|
184
+ { name: line.strip, size: 0, compressed_size: 0,
185
+ is_dir: false, mtime: nil }
186
+ end
187
+ end
188
+
189
+ # Extract entry with gem
190
+ def extract_entry_with_gem(archive_path, entry_name,
191
+ output_path, password)
192
+ require "unrar"
193
+ archive = Unrar::Archive.new(archive_path)
194
+ archive.extract_to_file(entry_name, output_path,
195
+ password: password)
196
+ rescue StandardError => e
197
+ raise "Gem entry extraction failed: #{e.message}"
198
+ end
199
+
200
+ # Extract entry with command
201
+ def extract_entry_with_command(archive_path, entry_name,
202
+ output_path, password)
203
+ temp_dir = Dir.mktmpdir
204
+ cmd = build_extract_command(archive_path, temp_dir, password)
205
+ unless system(cmd)
206
+ raise "Command entry extraction failed: #{entry_name}"
207
+ end
208
+
209
+ source = File.join(temp_dir, entry_name)
210
+ FileUtils.mv(source, output_path) if File.exist?(source)
211
+ ensure
212
+ FileUtils.rm_rf(temp_dir) if temp_dir
213
+ end
214
+
215
+ # Build extract command
216
+ def build_extract_command(archive_path, output_dir, password)
217
+ cmd = "#{command_path} x -y"
218
+ cmd += " -p#{password}" if password
219
+ cmd += " \"#{archive_path}\" \"#{output_dir}/\""
220
+ cmd
221
+ end
222
+
223
+ # Unsupported error message
224
+ def unsupported_error
225
+ <<~ERROR
226
+ RAR extraction not available.
227
+
228
+ To enable RAR support, install one of:
229
+ 1. unrar gem: gem install unrar
230
+ 2. System unrar: brew install unrar (macOS) or
231
+ apt-get install unrar (Linux)
232
+ ERROR
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,312 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "fileutils"
5
+ require_relative "license_validator"
6
+
7
+ module Omnizip
8
+ module Formats
9
+ module Rar
10
+ # External RAR writer using licensed WinRAR installation
11
+ #
12
+ # This class provides RAR archive creation by wrapping the user's
13
+ # licensed WinRAR command-line tool. It does NOT implement RAR
14
+ # compression internally due to proprietary licensing restrictions.
15
+ #
16
+ # @example Create a RAR archive
17
+ # writer = ExternalWriter.new('archive.rar')
18
+ # writer.add_file('document.pdf')
19
+ # writer.add_directory('photos/')
20
+ # writer.write
21
+ #
22
+ # @example Create with options
23
+ # writer = ExternalWriter.new('archive.rar',
24
+ # compression: :best,
25
+ # solid: true,
26
+ # recovery: 5
27
+ # )
28
+ class ExternalWriter
29
+ # @return [String] Output archive path
30
+ attr_reader :output_path
31
+
32
+ # @return [Hash] Compression options
33
+ attr_reader :options
34
+
35
+ # @return [Array<String>] Files to add
36
+ attr_reader :files
37
+
38
+ # Check if RAR creation is available
39
+ #
40
+ # @return [Boolean] true if WinRAR executable found
41
+ def self.available?
42
+ !find_rar_executable.nil?
43
+ end
44
+
45
+ # Get RAR executable information
46
+ #
47
+ # @return [Hash] Executable path and version
48
+ def self.info
49
+ exe = find_rar_executable
50
+ return { available: false } unless exe
51
+
52
+ version_output, = Open3.capture2e(exe)
53
+ version = version_output.match(/RAR\s+([\d.]+)/i)&.[](1) || "unknown"
54
+
55
+ {
56
+ available: true,
57
+ executable: exe,
58
+ version: version,
59
+ }
60
+ end
61
+
62
+ # Find RAR executable on system
63
+ #
64
+ # @return [String, nil] Path to executable or nil
65
+ def self.find_rar_executable
66
+ if RUBY_PLATFORM.match?(/win32|mingw/)
67
+ find_windows_rar
68
+ else
69
+ find_unix_rar
70
+ end
71
+ end
72
+
73
+ # Initialize RAR writer
74
+ #
75
+ # @param output_path [String] Output RAR file path
76
+ # @param options [Hash] Compression options
77
+ # @option options [Symbol] :compression Compression level
78
+ # (:store, :fastest, :fast, :normal, :good, :best)
79
+ # @option options [Boolean] :solid Create solid archive
80
+ # @option options [Integer] :recovery Recovery record percentage (0-10)
81
+ # @option options [Boolean] :encrypt_headers Encrypt file names
82
+ # @option options [String] :password Archive password
83
+ # @option options [Integer] :volume_size Split into volumes (bytes)
84
+ # @option options [Boolean] :test_after_create Test archive after creation
85
+ # @option options [Boolean] :license_confirmed Skip license confirmation
86
+ def initialize(output_path, options = {})
87
+ @output_path = output_path
88
+ @options = default_options.merge(options)
89
+ @files = []
90
+ @directories = []
91
+
92
+ validate_availability!
93
+ validate_license! unless @options[:license_confirmed]
94
+ end
95
+
96
+ # Add file to archive
97
+ #
98
+ # @param file_path [String] Path to file
99
+ # @param archive_path [String, nil] Path within archive
100
+ # @raise [ArgumentError] if file does not exist
101
+ def add_file(file_path, archive_path = nil)
102
+ raise ArgumentError, "File not found: #{file_path}" unless
103
+ File.exist?(file_path)
104
+
105
+ @files << {
106
+ source: File.expand_path(file_path),
107
+ archive_path: archive_path,
108
+ }
109
+ end
110
+
111
+ # Add directory to archive
112
+ #
113
+ # @param dir_path [String] Path to directory
114
+ # @param recursive [Boolean] Include subdirectories
115
+ # @param archive_path [String, nil] Path within archive
116
+ # @raise [ArgumentError] if directory does not exist
117
+ def add_directory(dir_path, recursive: true, archive_path: nil)
118
+ raise ArgumentError, "Directory not found: #{dir_path}" unless
119
+ Dir.exist?(dir_path)
120
+
121
+ @directories << {
122
+ source: File.expand_path(dir_path),
123
+ recursive: recursive,
124
+ archive_path: archive_path,
125
+ }
126
+ end
127
+
128
+ # Create RAR archive
129
+ #
130
+ # @raise [RarNotAvailableError] if WinRAR not available
131
+ # @raise [RuntimeError] if archive creation fails
132
+ def write
133
+ raise RarNotAvailableError unless self.class.available?
134
+
135
+ # Build command
136
+ cmd = build_command
137
+
138
+ # Execute RAR
139
+ stdout, stderr, status = Open3.capture3(*cmd)
140
+
141
+ unless status.success?
142
+ raise "RAR creation failed: #{stderr}\n#{stdout}"
143
+ end
144
+
145
+ # Test archive if requested
146
+ test_archive if @options[:test_after_create]
147
+
148
+ @output_path
149
+ end
150
+
151
+ private
152
+
153
+ # Default compression options
154
+ #
155
+ # @return [Hash] Default options
156
+ def default_options
157
+ {
158
+ compression: :normal,
159
+ solid: false,
160
+ recovery: 0,
161
+ encrypt_headers: false,
162
+ password: nil,
163
+ volume_size: nil,
164
+ test_after_create: false,
165
+ license_confirmed: false,
166
+ }
167
+ end
168
+
169
+ # Validate WinRAR availability
170
+ #
171
+ # @raise [RarNotAvailableError] if not available
172
+ def validate_availability!
173
+ raise RarNotAvailableError unless self.class.available?
174
+ end
175
+
176
+ # Validate license ownership
177
+ #
178
+ # @raise [NotLicensedError] if license not confirmed
179
+ def validate_license!
180
+ return if LicenseValidator.license_confirmed?
181
+
182
+ return if LicenseValidator.confirm_license!
183
+
184
+ raise NotLicensedError
185
+ end
186
+
187
+ # Build RAR command line
188
+ #
189
+ # @return [Array<String>] Command and arguments
190
+ def build_command
191
+ cmd = [rar_executable]
192
+
193
+ # Command: a = add
194
+ cmd << "a"
195
+
196
+ # Compression method
197
+ cmd << compression_switch
198
+
199
+ # Solid archive
200
+ cmd << "-s" if @options[:solid]
201
+
202
+ # Recovery record
203
+ cmd << "-rr#{@options[:recovery]}%" if @options[:recovery].positive?
204
+
205
+ # Password
206
+ if @options[:password]
207
+ cmd << "-p#{@options[:password]}"
208
+ cmd << "-hp" if @options[:encrypt_headers]
209
+ end
210
+
211
+ # Volume size
212
+ cmd << "-v#{@options[:volume_size]}b" if @options[:volume_size]
213
+
214
+ # Overwrite existing
215
+ cmd << "-o+"
216
+
217
+ # Output archive path
218
+ cmd << @output_path
219
+
220
+ # Add files and directories
221
+ @files.each do |file_info|
222
+ cmd << if file_info[:archive_path]
223
+ # RAR doesn't support renaming in command line easily
224
+ # Would need to use temporary directory structure
225
+ end
226
+ file_info[:source]
227
+ end
228
+
229
+ @directories.each do |dir_info|
230
+ cmd << "-r" if dir_info[:recursive]
231
+ cmd << "#{dir_info[:source]}/*"
232
+ end
233
+
234
+ cmd
235
+ end
236
+
237
+ # Get compression switch for level
238
+ #
239
+ # @return [String] Compression switch
240
+ def compression_switch
241
+ case @options[:compression]
242
+ when :store then "-m0"
243
+ when :fastest then "-m1"
244
+ when :fast then "-m2"
245
+ when :normal then "-m3"
246
+ when :good then "-m4"
247
+ when :best then "-m5"
248
+ else "-m3"
249
+ end
250
+ end
251
+
252
+ # Test archive integrity
253
+ #
254
+ # @raise [RuntimeError] if test fails
255
+ def test_archive
256
+ cmd = [rar_executable, "t", @output_path]
257
+ stdout, stderr, status = Open3.capture3(*cmd)
258
+
259
+ return if status.success?
260
+
261
+ raise "Archive test failed: #{stderr}\n#{stdout}"
262
+ end
263
+
264
+ # Get RAR executable path
265
+ #
266
+ # @return [String] Path to executable
267
+ def rar_executable
268
+ self.class.find_rar_executable
269
+ end
270
+
271
+ # Find WinRAR on Windows
272
+ #
273
+ # @return [String, nil] Path or nil
274
+ def self.find_windows_rar
275
+ # Check common installation paths
276
+ paths = [
277
+ "C:\\Program Files\\WinRAR\\Rar.exe",
278
+ "C:\\Program Files (x86)\\WinRAR\\Rar.exe",
279
+ ]
280
+
281
+ # Check PATH
282
+ path_rar = which("rar.exe") || which("Rar.exe")
283
+ paths.unshift(path_rar) if path_rar
284
+
285
+ paths.find { |path| File.exist?(path) }
286
+ end
287
+
288
+ # Find RAR on Unix systems
289
+ #
290
+ # @return [String, nil] Path or nil
291
+ def self.find_unix_rar
292
+ which("rar")
293
+ end
294
+
295
+ # Find executable in PATH
296
+ #
297
+ # @param cmd [String] Command name
298
+ # @return [String, nil] Path or nil
299
+ def self.which(cmd)
300
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
301
+ ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
302
+ exts.each do |ext|
303
+ exe = File.join(path, "#{cmd}#{ext}")
304
+ return exe if File.executable?(exe) && !File.directory?(exe)
305
+ end
306
+ end
307
+ nil
308
+ end
309
+ end
310
+ end
311
+ end
312
+ end