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,273 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "entry"
4
+ require_relative "../formats/zip/constants"
5
+ require_relative "../formats/zip/local_file_header"
6
+
7
+ module Omnizip
8
+ module Zip
9
+ # Rubyzip-compatible InputStream class
10
+ # Provides streaming read API for ZIP archives
11
+ class InputStream
12
+ include Omnizip::Formats::Zip::Constants
13
+
14
+ # Open an input stream
15
+ # @param file_path_or_io [String, IO] File path or IO object
16
+ # @yield [stream] Block to read from the stream
17
+ # @return [InputStream] The stream (if no block given)
18
+ def self.open(file_path_or_io, &block)
19
+ stream = new(file_path_or_io)
20
+
21
+ if block
22
+ begin
23
+ yield(stream)
24
+ ensure
25
+ stream.close
26
+ end
27
+ else
28
+ stream
29
+ end
30
+ end
31
+
32
+ # Initialize input stream
33
+ # @param file_path_or_io [String, IO] File path or IO object
34
+ def initialize(file_path_or_io)
35
+ if file_path_or_io.is_a?(String)
36
+ @file_path = file_path_or_io
37
+ @io = ::File.open(file_path_or_io, "rb")
38
+ @owns_io = true
39
+ else
40
+ @io = file_path_or_io
41
+ @owns_io = false
42
+ end
43
+
44
+ @current_entry = nil
45
+ @current_entry_io = nil
46
+ @current_index = 0
47
+ @closed = false
48
+ @hit_nil = false
49
+
50
+ # Find and parse central directory for efficient access
51
+ parse_central_directory
52
+ end
53
+
54
+ # Get next entry in the archive
55
+ # @return [Entry, nil] Next entry or nil if no more entries
56
+ def get_next_entry
57
+ # Check if we can fetch another entry
58
+ if @current_index >= @all_entries.size
59
+ # Mark that get_next_entry returned nil
60
+ @hit_nil = true
61
+ return nil
62
+ end
63
+
64
+ # Close previous entry data if open
65
+ @current_entry_io = nil
66
+
67
+ # Get next entry from our parsed list
68
+ header = @all_entries[@current_index]
69
+ @current_index += 1
70
+
71
+ # Successfully got an entry - clear the nil flag
72
+ @hit_nil = false
73
+
74
+ # Position at entry data
75
+ position_at_entry_data(header)
76
+
77
+ @current_entry = Entry.new(header)
78
+ @current_entry
79
+ end
80
+
81
+ # Read from current entry
82
+ # @param size [Integer, nil] Number of bytes to read (nil for all)
83
+ # @return [String, nil] Data read or nil if no current entry
84
+ def read(size = nil)
85
+ return nil unless @current_entry
86
+
87
+ # Initialize entry IO if needed
88
+ unless @current_entry_io
89
+ compressed_data = @io.read(@current_entry.compressed_size)
90
+ decompressed = decompress_data(
91
+ compressed_data,
92
+ @current_entry.compression_method,
93
+ @current_entry.size,
94
+ )
95
+ require "stringio"
96
+ @current_entry_io = StringIO.new(decompressed, "rb")
97
+ end
98
+
99
+ @current_entry_io.read(size)
100
+ end
101
+
102
+ # Rewind the stream
103
+ def rewind
104
+ @current_index = 0
105
+ @current_entry = nil
106
+ @current_entry_io = nil
107
+ @hit_nil = false
108
+ end
109
+
110
+ # Close the stream
111
+ def close
112
+ return if @closed
113
+
114
+ @current_entry_io = nil
115
+ @io.close if @owns_io
116
+ @closed = true
117
+ end
118
+
119
+ # Check if stream is closed
120
+ def closed?
121
+ @closed
122
+ end
123
+
124
+ # Check if at end of file
125
+ # Returns true if the NEXT call to get_next_entry would return nil
126
+ def eof?
127
+ @current_index >= @all_entries.size
128
+ end
129
+ alias_method :eof, :eof?
130
+
131
+ private
132
+
133
+ # Parse central directory to get all entries
134
+ def parse_central_directory
135
+ # Find End of Central Directory
136
+ eocd = find_eocd
137
+
138
+ # Read central directory entries
139
+ @io.seek(eocd[:central_directory_offset], ::IO::SEEK_SET)
140
+ @all_entries = []
141
+
142
+ eocd[:total_entries].times do
143
+ header_data = @io.read(46)
144
+ break unless header_data && header_data.size == 46
145
+
146
+ # Get dynamic field lengths
147
+ _, _, _, _, _, _, _, _, _, _,
148
+ filename_length, extra_field_length, comment_length = header_data.unpack("VvvvvvvVVVvvv")
149
+
150
+ # Read complete header
151
+ complete_data = header_data + @io.read(filename_length + extra_field_length + comment_length)
152
+ entry = Omnizip::Formats::Zip::CentralDirectoryHeader.from_binary(complete_data)
153
+
154
+ @all_entries << entry
155
+ end
156
+ end
157
+
158
+ # Find End of Central Directory record
159
+ def find_eocd
160
+ # Start from end of file
161
+ @io.seek(0, ::IO::SEEK_END)
162
+ file_size = @io.pos
163
+
164
+ # EOCD is at least 22 bytes
165
+ return nil if file_size < 22
166
+
167
+ # Search backwards for EOCD signature
168
+ # Maximum comment length is 65535, so search up to 65557 bytes from end
169
+ search_start = [file_size - 65557, 0].max
170
+ @io.seek(search_start, ::IO::SEEK_SET)
171
+ buffer = @io.read
172
+
173
+ # Find signature
174
+ signature = [END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack("V")
175
+ pos = buffer.rindex(signature)
176
+
177
+ unless pos
178
+ raise Omnizip::FormatError,
179
+ "End of Central Directory not found"
180
+ end
181
+
182
+ # Parse EOCD
183
+ eocd_data = buffer[pos..]
184
+
185
+ sig, disk_num, disk_with_cd, entries_this_disk, total_entries,
186
+ cd_size, cd_offset, comment_length = eocd_data.unpack("VvvvvVVv")
187
+
188
+ {
189
+ signature: sig,
190
+ disk_number: disk_num,
191
+ disk_number_with_cd: disk_with_cd,
192
+ total_entries_this_disk: entries_this_disk,
193
+ total_entries: total_entries,
194
+ central_directory_size: cd_size,
195
+ central_directory_offset: cd_offset,
196
+ comment_length: comment_length,
197
+ comment: eocd_data[22, comment_length] || "",
198
+ }
199
+ end
200
+
201
+ # Position IO at entry's data (after local file header)
202
+ def position_at_entry_data(header)
203
+ # Seek to local file header
204
+ @io.seek(header.local_header_offset, ::IO::SEEK_SET)
205
+
206
+ # Read fixed part of local file header
207
+ fixed_header = @io.read(30)
208
+
209
+ # Extract variable lengths
210
+ _, _, _, _, _, _, _, _, _, filename_length, extra_length =
211
+ fixed_header.unpack("VvvvvvVVVvv")
212
+
213
+ # Skip variable parts to get to data
214
+ @io.read(filename_length + extra_length)
215
+ end
216
+
217
+ # Decompress data based on compression method
218
+ def decompress_data(compressed_data, method, uncompressed_size)
219
+ case method
220
+ when COMPRESSION_STORE
221
+ compressed_data
222
+ when COMPRESSION_DEFLATE
223
+ decompress_deflate(compressed_data)
224
+ when COMPRESSION_BZIP2
225
+ decompress_bzip2(compressed_data)
226
+ when COMPRESSION_LZMA
227
+ decompress_lzma(compressed_data, uncompressed_size)
228
+ when COMPRESSION_ZSTANDARD
229
+ decompress_zstandard(compressed_data)
230
+ else
231
+ raise Omnizip::UnsupportedFormatError,
232
+ "Unsupported compression method: #{method}"
233
+ end
234
+ end
235
+
236
+ # Decompress using Deflate
237
+ def decompress_deflate(data)
238
+ require "zlib"
239
+ Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(data)
240
+ rescue StandardError => e
241
+ raise Omnizip::DecompressionError,
242
+ "Deflate decompression failed: #{e.message}"
243
+ end
244
+
245
+ # Decompress using BZip2
246
+ def decompress_bzip2(data)
247
+ algorithm = Omnizip::AlgorithmRegistry.get(:bzip2)
248
+ algorithm.decompress(data)
249
+ rescue StandardError => e
250
+ raise Omnizip::DecompressionError,
251
+ "BZip2 decompression failed: #{e.message}"
252
+ end
253
+
254
+ # Decompress using LZMA
255
+ def decompress_lzma(data, uncompressed_size)
256
+ algorithm = Omnizip::AlgorithmRegistry.get(:lzma)
257
+ algorithm.decompress(data, uncompressed_size: uncompressed_size)
258
+ rescue StandardError => e
259
+ raise Omnizip::DecompressionError,
260
+ "LZMA decompression failed: #{e.message}"
261
+ end
262
+
263
+ # Decompress using Zstandard
264
+ def decompress_zstandard(data)
265
+ algorithm = Omnizip::AlgorithmRegistry.get(:zstandard)
266
+ algorithm.decompress(data)
267
+ rescue StandardError => e
268
+ raise Omnizip::DecompressionError,
269
+ "Zstandard decompression failed: #{e.message}"
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,324 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require_relative "../formats/zip/constants"
5
+ require_relative "../formats/zip/local_file_header"
6
+ require_relative "../formats/zip/central_directory_header"
7
+ require_relative "../formats/zip/end_of_central_directory"
8
+
9
+ module Omnizip
10
+ module Zip
11
+ # Rubyzip-compatible OutputStream class
12
+ # Provides streaming write API for ZIP archives
13
+ class OutputStream
14
+ include Omnizip::Formats::Zip::Constants
15
+
16
+ # Open a new output stream
17
+ # @param file_path [String] Path to ZIP file
18
+ # @yield [stream] Block to write to the stream
19
+ # @return [OutputStream] The stream (if no block given)
20
+ def self.open(file_path, &block)
21
+ stream = new(file_path)
22
+
23
+ if block
24
+ begin
25
+ yield(stream)
26
+ ensure
27
+ stream.close
28
+ end
29
+ else
30
+ stream
31
+ end
32
+ end
33
+
34
+ # Initialize output stream
35
+ # @param file_path_or_io [String, IO] File path or IO object
36
+ def initialize(file_path_or_io)
37
+ if file_path_or_io.is_a?(String)
38
+ @file_path = file_path_or_io
39
+ @io = ::File.open(file_path_or_io, "wb")
40
+ @owns_io = true
41
+ else
42
+ @io = file_path_or_io
43
+ @owns_io = false
44
+ end
45
+
46
+ @entries = []
47
+ @current_entry = nil
48
+ @current_entry_data = nil
49
+ @closed = false
50
+ end
51
+
52
+ # Start a new entry
53
+ # @param name [String] Entry name
54
+ # @param time [Time] Modification time
55
+ # @param comment [String] Entry comment
56
+ # @param compression [Symbol] Compression method (:store or :deflate)
57
+ # @param level [Integer] Compression level (1-9)
58
+ def put_next_entry(name, time: Time.now, comment: "",
59
+ compression: :deflate, level: 6)
60
+ close_entry if @current_entry
61
+
62
+ @current_entry = {
63
+ name: name,
64
+ time: time,
65
+ comment: comment,
66
+ compression: compression_method_for(compression),
67
+ level: level,
68
+ offset: @io.pos,
69
+ directory: name.end_with?("/"),
70
+ }
71
+ @current_entry_data = String.new(encoding: Encoding::BINARY)
72
+
73
+ # Write placeholder local file header (will update later)
74
+ write_local_file_header_placeholder
75
+
76
+ self
77
+ end
78
+
79
+ # Write data to current entry
80
+ # @param data [String] Data to write
81
+ def write(data)
82
+ raise "No entry started. Call put_next_entry first" unless @current_entry
83
+
84
+ @current_entry_data << data.b
85
+ self
86
+ end
87
+ alias_method :<<, :write
88
+
89
+ # Print data to current entry
90
+ def print(*args)
91
+ write(args.join)
92
+ end
93
+
94
+ # Put data to current entry
95
+ def puts(*args)
96
+ args.each { |arg| write("#{arg}\n") }
97
+ end
98
+
99
+ # Close current entry
100
+ def close_entry
101
+ return unless @current_entry
102
+
103
+ # Compress data if needed
104
+ compressed_data = compress_entry_data
105
+
106
+ # Calculate CRC32
107
+ crc32 = @current_entry[:directory] ? 0 : calculate_crc32(@current_entry_data)
108
+
109
+ # Write compressed data
110
+ @io.write(compressed_data) unless @current_entry[:directory]
111
+
112
+ # Update entry info
113
+ @current_entry.merge!({
114
+ crc32: crc32,
115
+ compressed_size: compressed_data.bytesize,
116
+ uncompressed_size: @current_entry_data.bytesize,
117
+ })
118
+
119
+ # Update local file header with correct sizes
120
+ current_pos = @io.pos
121
+ @io.seek(@current_entry[:offset], ::IO::SEEK_SET)
122
+ write_local_file_header
123
+ @io.seek(current_pos, ::IO::SEEK_SET)
124
+
125
+ # Save entry for central directory
126
+ @entries << @current_entry
127
+
128
+ @current_entry = nil
129
+ @current_entry_data = nil
130
+ end
131
+
132
+ # Set archive comment
133
+ def comment=(comment)
134
+ @comment = comment
135
+ end
136
+
137
+ # Get archive comment
138
+ def comment
139
+ @comment || ""
140
+ end
141
+
142
+ # Close the stream
143
+ def close
144
+ return if @closed
145
+
146
+ close_entry if @current_entry
147
+
148
+ # Write central directory
149
+ central_directory_offset = @io.pos
150
+ write_central_directory
151
+ central_directory_size = @io.pos - central_directory_offset
152
+
153
+ # Write end of central directory
154
+ write_end_of_central_directory(central_directory_offset,
155
+ central_directory_size)
156
+
157
+ @io.close if @owns_io
158
+ @closed = true
159
+ end
160
+
161
+ # Check if stream is closed
162
+ def closed?
163
+ @closed
164
+ end
165
+
166
+ private
167
+
168
+ # Get compression method code
169
+ def compression_method_for(symbol)
170
+ case symbol
171
+ when :store, :none then COMPRESSION_STORE
172
+ when :deflate then COMPRESSION_DEFLATE
173
+ when :bzip2 then COMPRESSION_BZIP2
174
+ when :lzma then COMPRESSION_LZMA
175
+ when :zstandard then COMPRESSION_ZSTANDARD
176
+ else COMPRESSION_DEFLATE
177
+ end
178
+ end
179
+
180
+ # Write local file header placeholder
181
+ def write_local_file_header_placeholder
182
+ # Write zeros for now, will update after compression
183
+ @io.write("\x00" * 30) # Fixed part
184
+ @io.write(@current_entry[:name].b) # Filename
185
+ @current_entry[:filename_length] = @current_entry[:name].bytesize
186
+ @current_entry[:extra_field_length] = 0
187
+ end
188
+
189
+ # Write actual local file header
190
+ def write_local_file_header
191
+ header = Omnizip::Formats::Zip::LocalFileHeader.new(
192
+ version_needed: version_for_method(@current_entry[:compression]),
193
+ flags: FLAG_UTF8,
194
+ compression_method: @current_entry[:directory] ? COMPRESSION_STORE : @current_entry[:compression],
195
+ last_mod_time: dos_time(@current_entry[:time]),
196
+ last_mod_date: dos_date(@current_entry[:time]),
197
+ crc32: @current_entry[:crc32],
198
+ compressed_size: @current_entry[:compressed_size],
199
+ uncompressed_size: @current_entry[:uncompressed_size],
200
+ filename: @current_entry[:name],
201
+ )
202
+
203
+ @io.write(header.to_binary)
204
+ end
205
+
206
+ # Compress current entry data
207
+ def compress_entry_data
208
+ return "" if @current_entry[:directory]
209
+
210
+ case @current_entry[:compression]
211
+ when COMPRESSION_STORE
212
+ @current_entry_data
213
+ when COMPRESSION_DEFLATE
214
+ compress_deflate(@current_entry_data, @current_entry[:level])
215
+ when COMPRESSION_BZIP2
216
+ compress_bzip2(@current_entry_data, @current_entry[:level])
217
+ when COMPRESSION_LZMA
218
+ compress_lzma(@current_entry_data, @current_entry[:level])
219
+ when COMPRESSION_ZSTANDARD
220
+ compress_zstandard(@current_entry_data, @current_entry[:level])
221
+ else
222
+ @current_entry_data
223
+ end
224
+ end
225
+
226
+ # Write central directory
227
+ def write_central_directory
228
+ @entries.each do |entry|
229
+ external_attrs = if entry[:directory]
230
+ UNIX_DIR_PERMISSIONS | ATTR_DIRECTORY
231
+ else
232
+ UNIX_FILE_PERMISSIONS
233
+ end
234
+
235
+ header = Omnizip::Formats::Zip::CentralDirectoryHeader.new(
236
+ version_made_by: VERSION_MADE_BY_UNIX | version_for_method(entry[:compression]),
237
+ version_needed: version_for_method(entry[:compression]),
238
+ flags: FLAG_UTF8,
239
+ compression_method: entry[:directory] ? COMPRESSION_STORE : entry[:compression],
240
+ last_mod_time: dos_time(entry[:time]),
241
+ last_mod_date: dos_date(entry[:time]),
242
+ crc32: entry[:crc32],
243
+ compressed_size: entry[:compressed_size],
244
+ uncompressed_size: entry[:uncompressed_size],
245
+ disk_number_start: 0,
246
+ internal_attributes: 0,
247
+ external_attributes: external_attrs,
248
+ local_header_offset: entry[:offset],
249
+ filename: entry[:name],
250
+ extra_field: "",
251
+ comment: entry[:comment] || "",
252
+ )
253
+
254
+ @io.write(header.to_binary)
255
+ end
256
+ end
257
+
258
+ # Write end of central directory
259
+ def write_end_of_central_directory(offset, size)
260
+ eocd = Omnizip::Formats::Zip::EndOfCentralDirectory.new(
261
+ disk_number: 0,
262
+ disk_number_with_cd: 0,
263
+ total_entries_this_disk: @entries.size,
264
+ total_entries: @entries.size,
265
+ central_directory_size: size,
266
+ central_directory_offset: offset,
267
+ comment: comment,
268
+ )
269
+
270
+ @io.write(eocd.to_binary)
271
+ end
272
+
273
+ # Compress using Deflate
274
+ def compress_deflate(data, level)
275
+ require "zlib"
276
+ Zlib::Deflate.new(level, -Zlib::MAX_WBITS).deflate(data, Zlib::FINISH)
277
+ end
278
+
279
+ # Compress using BZip2
280
+ def compress_bzip2(data, level)
281
+ algorithm = Omnizip::AlgorithmRegistry.get(:bzip2)
282
+ algorithm.compress(data, level: level)
283
+ end
284
+
285
+ # Compress using LZMA
286
+ def compress_lzma(data, level)
287
+ algorithm = Omnizip::AlgorithmRegistry.get(:lzma)
288
+ algorithm.compress(data, level: level)
289
+ end
290
+
291
+ # Compress using Zstandard
292
+ def compress_zstandard(data, level)
293
+ algorithm = Omnizip::AlgorithmRegistry.get(:zstandard)
294
+ algorithm.compress(data, level: level)
295
+ end
296
+
297
+ # Calculate CRC32
298
+ def calculate_crc32(data)
299
+ Omnizip::Checksums::Crc32.new.tap { |c| c.update(data) }.finalize
300
+ end
301
+
302
+ # Get version needed for compression method
303
+ def version_for_method(method)
304
+ case method
305
+ when COMPRESSION_STORE then VERSION_DEFAULT
306
+ when COMPRESSION_DEFLATE then VERSION_DEFLATE
307
+ when COMPRESSION_BZIP2 then VERSION_BZIP2
308
+ when COMPRESSION_LZMA then VERSION_LZMA
309
+ else VERSION_DEFAULT
310
+ end
311
+ end
312
+
313
+ # Convert Time to DOS time
314
+ def dos_time(time)
315
+ ((time.hour << 11) | (time.min << 5) | (time.sec / 2)) & 0xFFFF
316
+ end
317
+
318
+ # Convert Time to DOS date
319
+ def dos_date(time)
320
+ (((time.year - 1980) << 9) | (time.month << 5) | time.day) & 0xFFFF
321
+ end
322
+ end
323
+ end
324
+ end