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,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "models/rar_volume"
4
+
5
+ module Omnizip
6
+ module Formats
7
+ module Rar
8
+ # Manages multi-volume RAR archives
9
+ # Handles volume detection, sequencing, and coordination
10
+ class VolumeManager
11
+ attr_reader :volumes, :base_path
12
+
13
+ # Initialize volume manager
14
+ #
15
+ # @param path [String] Path to any volume in the set
16
+ def initialize(path)
17
+ @base_path = path
18
+ @volumes = []
19
+ detect_volumes
20
+ end
21
+
22
+ # Get total number of volumes
23
+ #
24
+ # @return [Integer] Number of volumes
25
+ def volume_count
26
+ @volumes.size
27
+ end
28
+
29
+ # Check if this is a multi-volume archive
30
+ #
31
+ # @return [Boolean] true if multi-volume
32
+ def multi_volume?
33
+ @volumes.size > 1
34
+ end
35
+
36
+ # Get first volume
37
+ #
38
+ # @return [Models::RarVolume, nil] First volume or nil
39
+ def first_volume
40
+ @volumes.find(&:first?)
41
+ end
42
+
43
+ # Get last volume
44
+ #
45
+ # @return [Models::RarVolume, nil] Last volume or nil
46
+ def last_volume
47
+ @volumes.find(&:last?)
48
+ end
49
+
50
+ # Get volume by number
51
+ #
52
+ # @param number [Integer] Volume number (0-based)
53
+ # @return [Models::RarVolume, nil] Volume or nil
54
+ def volume_at(number)
55
+ @volumes.find { |v| v.volume_number == number }
56
+ end
57
+
58
+ # Get all volume paths
59
+ #
60
+ # @return [Array<String>] Paths to all volumes
61
+ def volume_paths
62
+ @volumes.map(&:path)
63
+ end
64
+
65
+ # Get recovery files for volumes
66
+ #
67
+ # @return [Array<String>] Paths to .rev files
68
+ def recovery_files
69
+ rev_files = []
70
+
71
+ @volumes.each do |volume|
72
+ # Check for .rev file for this volume
73
+ rev_path = "#{volume.path}.rev"
74
+ rev_files << rev_path if File.exist?(rev_path)
75
+ end
76
+
77
+ rev_files
78
+ end
79
+
80
+ # Validate volume sequence
81
+ #
82
+ # @return [Boolean] true if all volumes exist and sequence is valid
83
+ def valid_sequence?
84
+ return true unless multi_volume?
85
+
86
+ # Check all volumes exist
87
+ return false unless @volumes.all?(&:exist?)
88
+
89
+ # Check sequence is continuous
90
+ expected_numbers = (0...@volumes.size).to_a
91
+ actual_numbers = @volumes.map(&:volume_number).sort
92
+ expected_numbers == actual_numbers
93
+ end
94
+
95
+ private
96
+
97
+ # Detect all volumes in the set
98
+ def detect_volumes
99
+ if rar5_naming?
100
+ detect_rar5_volumes
101
+ else
102
+ detect_rar4_volumes
103
+ end
104
+
105
+ # Mark first and last
106
+ @volumes.first.is_first = true if @volumes.any?
107
+ @volumes.last.is_last = true if @volumes.any?
108
+ end
109
+
110
+ # Check if using RAR5 naming (.partNN.rar)
111
+ #
112
+ # @return [Boolean] true if RAR5 naming
113
+ def rar5_naming?
114
+ @base_path.match?(/\.part\d+\.rar$/i)
115
+ end
116
+
117
+ # Detect RAR5 volumes (.part01.rar, .part02.rar, ...)
118
+ def detect_rar5_volumes
119
+ # Extract base name and current part number
120
+ if @base_path =~ /^(.+)\.part(\d+)\.rar$/i
121
+ base_name = Regexp.last_match(1)
122
+ Regexp.last_match(2).to_i
123
+
124
+ # Find all volumes
125
+ volume_num = 0
126
+ (1..999).each do |i|
127
+ path = format("%s.part%02d.rar", base_name, i)
128
+ break unless File.exist?(path)
129
+
130
+ volume = Models::RarVolume.new(path, volume_num)
131
+ @volumes << volume
132
+ volume_num += 1
133
+ end
134
+ else
135
+ # Single volume
136
+ @volumes << Models::RarVolume.new(@base_path, 0)
137
+ end
138
+ end
139
+
140
+ # Detect RAR4 volumes (.rar, .r00, .r01, ...)
141
+ def detect_rar4_volumes
142
+ dir = File.dirname(@base_path)
143
+ basename = File.basename(@base_path, ".*")
144
+
145
+ # Check for .rar file
146
+ rar_path = File.join(dir, "#{basename}.rar")
147
+ if File.exist?(rar_path)
148
+ @volumes << Models::RarVolume.new(rar_path, 0)
149
+
150
+ # Find .r00, .r01, .r02, ...
151
+ volume_num = 1
152
+ (0..99).each do |i|
153
+ path = File.join(dir, format("%s.r%02d", basename, i))
154
+ break unless File.exist?(path)
155
+
156
+ volume = Models::RarVolume.new(path, volume_num)
157
+ @volumes << volume
158
+ volume_num += 1
159
+ end
160
+ else
161
+ # Single volume
162
+ @volumes << Models::RarVolume.new(@base_path, 0)
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,431 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "zlib"
5
+ require "stringio"
6
+ require_relative "constants"
7
+ require_relative "header"
8
+ require_relative "compression/dispatcher"
9
+
10
+ module Omnizip
11
+ module Formats
12
+ module Rar
13
+ # Pure Ruby RAR archive writer
14
+ #
15
+ # This class provides basic RAR archive creation in pure Ruby.
16
+ # It writes RAR4-compatible archives with basic compression support.
17
+ #
18
+ # @example Create a RAR archive
19
+ # writer = Writer.new('archive.rar')
20
+ # writer.add_file('document.pdf')
21
+ # writer.add_directory('photos/')
22
+ # writer.write
23
+ #
24
+ # @example Create with options
25
+ # writer = Writer.new('archive.rar',
26
+ # compression: :best,
27
+ # solid: true,
28
+ # recovery: 5
29
+ # )
30
+ class Writer
31
+ include Constants
32
+
33
+ # @return [String] Output archive path
34
+ attr_reader :output_path
35
+
36
+ # @return [Hash] Compression options
37
+ attr_reader :options
38
+
39
+ # @return [Array<Hash>] Files to add
40
+ attr_reader :files
41
+
42
+ # @return [Array<Hash>] Directories to add
43
+ attr_reader :directories
44
+
45
+ # Check if RAR creation is available
46
+ #
47
+ # @return [Boolean] always true for pure Ruby implementation
48
+ def self.available?
49
+ true
50
+ end
51
+
52
+ # Get RAR writer information
53
+ #
54
+ # @return [Hash] Writer type and version
55
+ def self.info
56
+ {
57
+ available: true,
58
+ type: :pure_ruby,
59
+ version: "4.0",
60
+ }
61
+ end
62
+
63
+ # Initialize RAR writer
64
+ #
65
+ # @param output_path [String] Output RAR file path
66
+ # @param options [Hash] Compression options
67
+ # @option options [Symbol] :compression Compression level
68
+ # (:store, :fastest, :fast, :normal, :good, :best)
69
+ # @option options [Boolean] :solid Create solid archive
70
+ # @option options [Integer] :recovery Recovery record percentage (0-10)
71
+ # @option options [Boolean] :encrypt_headers Encrypt file names
72
+ # @option options [String] :password Archive password
73
+ # @option options [Integer] :volume_size Split into volumes (bytes)
74
+ # @option options [Boolean] :test_after_create Test archive after creation
75
+ def initialize(output_path, options = {})
76
+ @output_path = output_path
77
+ @options = default_options.merge(options)
78
+ @files = []
79
+ @directories = []
80
+ end
81
+
82
+ # Add file to archive
83
+ #
84
+ # @param file_path [String] Path to file
85
+ # @param archive_path [String, nil] Path within archive
86
+ # @raise [ArgumentError] if file does not exist
87
+ def add_file(file_path, archive_path = nil)
88
+ raise ArgumentError, "File not found: #{file_path}" unless
89
+ File.exist?(file_path)
90
+
91
+ @files << {
92
+ source: File.expand_path(file_path),
93
+ archive_path: archive_path,
94
+ }
95
+ end
96
+
97
+ # Add directory to archive
98
+ #
99
+ # @param dir_path [String] Path to directory
100
+ # @param recursive [Boolean] Include subdirectories
101
+ # @param archive_path [String, nil] Path within archive
102
+ # @raise [ArgumentError] if directory does not exist
103
+ def add_directory(dir_path, recursive: true, archive_path: nil)
104
+ raise ArgumentError, "Directory not found: #{dir_path}" unless
105
+ Dir.exist?(dir_path)
106
+
107
+ @directories << {
108
+ source: File.expand_path(dir_path),
109
+ recursive: recursive,
110
+ archive_path: archive_path,
111
+ }
112
+ end
113
+
114
+ # Create RAR archive
115
+ #
116
+ # @return [String] Path to created archive
117
+ def write
118
+ File.open(@output_path, "wb") do |io|
119
+ write_signature(io)
120
+ write_marker_block(io)
121
+ write_archive_header(io)
122
+ write_file_entries(io)
123
+ write_end_block(io)
124
+ end
125
+
126
+ # Test archive if requested
127
+ test_archive if @options[:test_after_create]
128
+
129
+ @output_path
130
+ end
131
+
132
+ private
133
+
134
+ # Default compression options
135
+ #
136
+ # @return [Hash] Default options
137
+ def default_options
138
+ {
139
+ compression: :normal,
140
+ solid: false,
141
+ recovery: 0,
142
+ encrypt_headers: false,
143
+ password: nil,
144
+ volume_size: nil,
145
+ test_after_create: false,
146
+ }
147
+ end
148
+
149
+ # Calculate CRC16-CCITT for block headers
150
+ #
151
+ # RAR uses CRC16-CCITT with polynomial 0x1021
152
+ # @param data [String] Header data to checksum
153
+ # @return [Integer] 16-bit CRC value
154
+ def calculate_header_crc16(data)
155
+ crc = 0
156
+ data.bytes.each do |byte|
157
+ crc ^= (byte << 8)
158
+ 8.times do
159
+ crc = crc.anybits?(0x8000) ? ((crc << 1) ^ 0x1021) : (crc << 1)
160
+ end
161
+ end
162
+ crc & 0xFFFF
163
+ end
164
+
165
+ # Convert Ruby Time to DOS time format
166
+ #
167
+ # @param time [Time] Ruby time object
168
+ # @return [Integer] DOS time format (32-bit)
169
+ def dos_time(time)
170
+ dos_date = ((time.year - 1980) << 9) | (time.month << 5) | time.day
171
+ dos_time_part = (time.hour << 11) | (time.min << 5) | (time.sec / 2)
172
+ (dos_date << 16) | dos_time_part
173
+ end
174
+
175
+ # Write RAR signature
176
+ #
177
+ # @param io [IO] Output stream
178
+ def write_signature(io)
179
+ io.write(RAR4_SIGNATURE.pack("C*"))
180
+ end
181
+
182
+ # Write marker block
183
+ #
184
+ # @param io [IO] Output stream
185
+ def write_marker_block(io)
186
+ # Marker block has fixed CRC of 0x6152
187
+ io.write([0x6152].pack("v")) # HEAD_CRC
188
+ io.write([BLOCK_MARKER].pack("C")) # HEAD_TYPE
189
+ io.write([0x0000].pack("v")) # HEAD_FLAGS
190
+ io.write([0x0007].pack("v")) # HEAD_SIZE
191
+ end
192
+
193
+ # Write archive header
194
+ #
195
+ # @param io [IO] Output stream
196
+ def write_archive_header(io)
197
+ flags = 0
198
+ flags |= ARCHIVE_SOLID if @options[:solid]
199
+ flags |= ARCHIVE_RECOVERY if @options[:recovery].positive?
200
+ flags |= ARCHIVE_ENCRYPTED if @options[:password]
201
+ flags |= ARCHIVE_VOLUME if @options[:volume_size]
202
+
203
+ # Build header data (without CRC)
204
+ # SIZE includes: TYPE(1) + FLAGS(2) + SIZE(2) + Reserved(6) = 11 bytes
205
+ header_data = [BLOCK_ARCHIVE].pack("C") +
206
+ [flags].pack("v") +
207
+ [0x000B].pack("v") +
208
+ [0, 0, 0].pack("vvv") # 6 bytes reserved (3 x uint16)
209
+
210
+ # Calculate and write CRC
211
+ crc = calculate_header_crc16(header_data)
212
+ io.write([crc].pack("v"))
213
+ io.write(header_data)
214
+ end
215
+
216
+ # Write file entries
217
+ #
218
+ # @param io [IO] Output stream
219
+ def write_file_entries(io)
220
+ @files.each do |file_info|
221
+ write_file_entry(io, file_info)
222
+ end
223
+
224
+ @directories.each do |dir_info|
225
+ write_directory_entries(io, dir_info)
226
+ end
227
+ end
228
+
229
+ # Write a single file entry
230
+ #
231
+ # @param io [IO] Output stream
232
+ # @param file_info [Hash] File information
233
+ def write_file_entry(io, file_info)
234
+ file_path = file_info[:source]
235
+ archive_path = file_info[:archive_path] || File.basename(file_path)
236
+
237
+ file_data = File.binread(file_path)
238
+
239
+ # Select appropriate compression method based on data
240
+ method = file_info[:method] || select_compression_method(file_data)
241
+ compressed_data = compress_data(file_data, method)
242
+
243
+ # File metadata
244
+ stat = File.stat(file_path)
245
+ file_attr = stat.mode
246
+ file_time = dos_time(stat.mtime)
247
+ data_crc = Zlib.crc32(file_data)
248
+
249
+ # Name encoding
250
+ name_bytes = archive_path.encode("UTF-8").bytes
251
+
252
+ # Flags
253
+ flags = FILE_UNICODE
254
+ flags |= FILE_ENCRYPTED if @options[:password]
255
+ flags |= FILE_LARGE if file_data.size > 0xFFFFFFFF
256
+
257
+ # Calculate header size
258
+ # Fixed fields: TYPE(1) + FLAGS(2) + SIZE(2) + PACK_SIZE(4) + UNPACK_SIZE(4) +
259
+ # HOST_OS(1) + FILE_CRC(4) + FILE_TIME(4) + VERSION(1) + METHOD(1) +
260
+ # NAME_SIZE(2) + ATTR(4) = 30 bytes
261
+ header_size = 30 + name_bytes.size
262
+ header_size += 8 if flags & FILE_LARGE != 0
263
+
264
+ # Build file header (without CRC)
265
+ header_data = [BLOCK_FILE].pack("C") +
266
+ [flags].pack("v") +
267
+ [header_size].pack("v") +
268
+ [compressed_data.bytesize & 0xFFFFFFFF].pack("V") +
269
+ [file_data.bytesize & 0xFFFFFFFF].pack("V") +
270
+ [OS_UNIX].pack("C") +
271
+ [data_crc].pack("V") +
272
+ [file_time].pack("V") +
273
+ [compression_version(method)].pack("C") + # VERSION first
274
+ [method].pack("C") + # METHOD second
275
+ [name_bytes.size].pack("v") +
276
+ [file_attr].pack("V")
277
+
278
+ # Add high 32 bits for large files
279
+ if flags & FILE_LARGE != 0
280
+ header_data += [(compressed_data.bytesize >> 32) & 0xFFFFFFFF].pack("V")
281
+ header_data += [(file_data.bytesize >> 32) & 0xFFFFFFFF].pack("V")
282
+ end
283
+
284
+ # Add filename
285
+ header_data += name_bytes.pack("C*")
286
+
287
+ # Calculate and write CRC
288
+ crc = calculate_header_crc16(header_data)
289
+ io.write([crc].pack("v"))
290
+ io.write(header_data)
291
+ io.write(compressed_data)
292
+ end
293
+
294
+ # Write directory entries
295
+ #
296
+ # @param io [IO] Output stream
297
+ # @param dir_info [Hash] Directory information
298
+ def write_directory_entries(io, dir_info)
299
+ dir_path = dir_info[:source]
300
+ recursive = dir_info[:recursive]
301
+
302
+ pattern = recursive ? "**/*" : "*"
303
+ Dir.glob(File.join(dir_path, pattern)).each do |path|
304
+ next unless File.file?(path)
305
+
306
+ relative_path = path.sub("#{dir_path}/", "")
307
+ write_file_entry(io, {
308
+ source: path,
309
+ archive_path: relative_path,
310
+ })
311
+ end
312
+ end
313
+
314
+ # Write end block
315
+ #
316
+ # @param io [IO] Output stream
317
+ def write_end_block(io)
318
+ flags = 0x4000 # ENDARC flag
319
+
320
+ # Build header data (without CRC)
321
+ header_data = [BLOCK_ENDARC].pack("C") +
322
+ [flags].pack("v") +
323
+ [0x0007].pack("v")
324
+
325
+ # Calculate and write CRC
326
+ crc = calculate_header_crc16(header_data)
327
+ io.write([crc].pack("v"))
328
+ io.write(header_data)
329
+ end
330
+
331
+ # Compress data using native RAR compression
332
+ #
333
+ # @param data [String] Data to compress
334
+ # @param method [Integer] RAR compression method (default: METHOD_NORMAL)
335
+ # @return [String] Compressed data
336
+ def compress_data(data, method = METHOD_NORMAL)
337
+ input = StringIO.new(data)
338
+ output = StringIO.new
339
+
340
+ # Use native compression dispatcher
341
+ Compression::Dispatcher.compress(method, input, output, @options)
342
+
343
+ output.string
344
+ end
345
+
346
+ # Select appropriate compression method based on data
347
+ #
348
+ # @param data [String] Data to compress
349
+ # @return [Integer] RAR compression method code
350
+ def select_compression_method(data)
351
+ # Small files: use METHOD_STORE to avoid Huffman tree overhead (258 bytes)
352
+ return METHOD_STORE if data.size < 300
353
+
354
+ # User-specified compression level takes precedence
355
+ case @options[:compression]
356
+ when :store
357
+ METHOD_STORE
358
+ when :fastest
359
+ METHOD_FASTEST
360
+ when :fast
361
+ METHOD_FAST
362
+ when :normal
363
+ METHOD_NORMAL
364
+ when :good
365
+ METHOD_GOOD
366
+ when :best
367
+ METHOD_BEST
368
+ else
369
+ # Default based on compression level option if numeric
370
+ if @options[:level] == 9
371
+ METHOD_BEST # PPMd for maximum compression
372
+ else
373
+ METHOD_NORMAL # LZ77+Huffman default
374
+ end
375
+ end
376
+ end
377
+
378
+ # Get Zlib compression level (DEPRECATED - kept for compatibility)
379
+ #
380
+ # @return [Integer] Zlib compression level
381
+ def compression_zlib_level
382
+ case @options[:compression]
383
+ when :store then Zlib::NO_COMPRESSION
384
+ when :fastest then Zlib::BEST_SPEED
385
+ when :fast then 3
386
+ when :normal then Zlib::DEFAULT_COMPRESSION
387
+ when :good then 7
388
+ when :best then Zlib::BEST_COMPRESSION
389
+ else Zlib::DEFAULT_COMPRESSION
390
+ end
391
+ end
392
+
393
+ # Get compression method code (DEPRECATED - use select_compression_method)
394
+ #
395
+ # @return [Integer] Method code
396
+ def compression_method
397
+ case @options[:compression]
398
+ when :store then METHOD_STORE
399
+ when :fastest then METHOD_FASTEST
400
+ when :fast then METHOD_FAST
401
+ when :normal then METHOD_NORMAL
402
+ when :good then METHOD_GOOD
403
+ when :best then METHOD_BEST
404
+ else METHOD_NORMAL
405
+ end
406
+ end
407
+
408
+ # Get compression version based on method
409
+ #
410
+ # @param method [Integer] Compression method
411
+ # @return [Integer] Version code
412
+ def compression_version(method)
413
+ method == METHOD_STORE ? 20 : 29
414
+ end
415
+
416
+ # Test archive integrity
417
+ #
418
+ # @return [Boolean] true if valid
419
+ def test_archive
420
+ # Basic validation: check if file exists and has RAR signature
421
+ return false unless File.exist?(@output_path)
422
+
423
+ File.open(@output_path, "rb") do |io|
424
+ signature = io.read(7)
425
+ signature&.bytes == RAR4_SIGNATURE[0..6]
426
+ end
427
+ end
428
+ end
429
+ end
430
+ end
431
+ end