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,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Omnizip
6
+ module Pipe
7
+ # Stream-based decompression for pipe operations
8
+ #
9
+ # Accepts compressed archive from any IO-like source and extracts
10
+ # to directory or streams to output. Handles multi-file archives
11
+ # and provides error recovery for corrupted streams.
12
+ #
13
+ # @example Decompress to directory
14
+ # decompressor = StreamDecompressor.new($stdin, output_dir: 'extracted/')
15
+ # decompressor.decompress
16
+ #
17
+ # @example Decompress to stdout
18
+ # decompressor = StreamDecompressor.new($stdin, output: $stdout)
19
+ # decompressor.decompress
20
+ class StreamDecompressor
21
+ # Default chunk size for streaming extraction (64KB)
22
+ DEFAULT_CHUNK_SIZE = 64 * 1024
23
+
24
+ attr_reader :input, :output_dir, :output, :options, :bytes_written
25
+
26
+ # Initialize stream decompressor
27
+ #
28
+ # @param input [IO] Input stream containing archive
29
+ # @param output_dir [String, nil] Directory to extract to
30
+ # @param output [IO, nil] Output stream (for single-file extraction)
31
+ # @param options [Hash] Decompression options
32
+ # @option options [Symbol] :format Archive format (auto-detected if nil)
33
+ # @option options [Integer] :chunk_size Write buffer size
34
+ # @option options [Proc] :progress Progress callback
35
+ # @option options [Boolean] :preserve_paths Preserve directory structure
36
+ def initialize(input, output_dir: nil, output: nil, **options)
37
+ @input = input
38
+ @output_dir = output_dir
39
+ @output = output
40
+ @options = options
41
+ @chunk_size = options[:chunk_size] || DEFAULT_CHUNK_SIZE
42
+ @bytes_written = 0
43
+ @progress_callback = options[:progress]
44
+ @format = options[:format]
45
+ @preserve_paths = options.fetch(:preserve_paths, true)
46
+ end
47
+
48
+ # Decompress input stream
49
+ #
50
+ # Extracts archive to output directory or streams to output.
51
+ # Returns hash of extracted files or bytes written.
52
+ #
53
+ # @return [Hash<String, Integer>, Integer] Files => bytes or total bytes
54
+ # @raise [Omnizip::FormatError] If archive format is invalid
55
+ def decompress
56
+ if @output
57
+ decompress_to_stream
58
+ elsif @output_dir
59
+ decompress_to_directory
60
+ else
61
+ raise ArgumentError,
62
+ "Either output_dir or output must be specified"
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ # Decompress to output stream
69
+ #
70
+ # Extracts first non-directory entry to output stream.
71
+ # Useful for single-file archives or piping to stdout.
72
+ #
73
+ # @return [Integer] Bytes written to output
74
+ def decompress_to_stream
75
+ # Read archive into memory (necessary for format detection)
76
+ archive_data = @input.read
77
+
78
+ Omnizip::Buffer.open(archive_data, format: @format) do |archive|
79
+ archive.each_entry do |entry|
80
+ next if entry.directory?
81
+
82
+ # Stream first file to output
83
+ while chunk = entry.read(@chunk_size)
84
+ @output.write(chunk)
85
+ @bytes_written += chunk.bytesize
86
+
87
+ @progress_callback&.call(entry.name, @bytes_written)
88
+ end
89
+
90
+ break # Only extract first file for streaming
91
+ end
92
+ end
93
+
94
+ @bytes_written
95
+ rescue StandardError => e
96
+ raise Omnizip::Error,
97
+ "Stream decompression failed: #{e.message}"
98
+ end
99
+
100
+ # Decompress to directory
101
+ #
102
+ # Extracts all entries to specified directory with
103
+ # directory structure preservation.
104
+ #
105
+ # @return [Hash<String, Integer>] Filename => bytes mapping
106
+ def decompress_to_directory
107
+ FileUtils.mkdir_p(@output_dir)
108
+
109
+ # Read archive into memory
110
+ archive_data = @input.read
111
+ extracted_files = {}
112
+
113
+ Omnizip::Buffer.open(archive_data, format: @format) do |archive|
114
+ archive.each_entry do |entry|
115
+ if entry.directory?
116
+ create_directory(entry)
117
+ else
118
+ bytes = extract_file(entry)
119
+ extracted_files[entry.name] = bytes
120
+ end
121
+ end
122
+ end
123
+
124
+ extracted_files
125
+ rescue StandardError => e
126
+ raise Omnizip::Error,
127
+ "Directory extraction failed: #{e.message}"
128
+ end
129
+
130
+ # Create directory from entry
131
+ #
132
+ # @param entry [Object] Directory entry
133
+ def create_directory(entry)
134
+ return unless @preserve_paths
135
+
136
+ dir_path = File.join(@output_dir, entry.name)
137
+ FileUtils.mkdir_p(dir_path)
138
+ end
139
+
140
+ # Extract file from entry
141
+ #
142
+ # @param entry [Object] File entry
143
+ # @return [Integer] Bytes written
144
+ def extract_file(entry)
145
+ dest_path = if @preserve_paths
146
+ File.join(@output_dir, entry.name)
147
+ else
148
+ File.join(@output_dir, File.basename(entry.name))
149
+ end
150
+
151
+ # Create parent directory if needed
152
+ FileUtils.mkdir_p(File.dirname(dest_path))
153
+
154
+ bytes = 0
155
+ File.open(dest_path, "wb") do |file|
156
+ while chunk = entry.read(@chunk_size)
157
+ file.write(chunk)
158
+ bytes += chunk.bytesize
159
+ @bytes_written += chunk.bytesize
160
+
161
+ @progress_callback&.call(entry.name, @bytes_written)
162
+ end
163
+ end
164
+
165
+ # Preserve timestamp if available
166
+ if entry.respond_to?(:mtime) && entry.mtime
167
+ File.utime(entry.mtime, entry.mtime, dest_path)
168
+ end
169
+
170
+ bytes
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pipe/stream_compressor"
4
+ require_relative "pipe/stream_decompressor"
5
+
6
+ module Omnizip
7
+ # Unix pipe operations for stdin/stdout integration
8
+ #
9
+ # Enables compression and decompression through Unix pipes without
10
+ # temporary files, supporting container workflows, CI/CD pipelines,
11
+ # and stream processing scenarios.
12
+ #
13
+ # @example Compress stdin to stdout
14
+ # Omnizip::Pipe.compress($stdin, $stdout, format: :zip)
15
+ #
16
+ # @example Decompress stdin to directory
17
+ # Omnizip::Pipe.decompress($stdin, output_dir: 'extracted/')
18
+ #
19
+ # @example File-to-file via pipe
20
+ # File.open('input.txt', 'rb') do |input|
21
+ # File.open('output.zip', 'wb') do |output|
22
+ # Omnizip::Pipe.compress(input, output, format: :zip)
23
+ # end
24
+ # end
25
+ module Pipe
26
+ class << self
27
+ # Compress from input stream to output stream
28
+ #
29
+ # @param input [IO] Input stream (stdin, File, Socket, etc.)
30
+ # @param output [IO] Output stream (stdout, File, network, etc.)
31
+ # @param format [Symbol] Archive format (:zip, :seven_zip)
32
+ # @param compression [Symbol] Compression algorithm
33
+ # @param options [Hash] Additional compression options
34
+ # @option options [String] :entry_name Name for entry in archive
35
+ # @option options [Integer] :chunk_size Read buffer size (default 64KB)
36
+ # @option options [Integer] :level Compression level (1-9)
37
+ # @return [Integer] Number of bytes written
38
+ #
39
+ # @example Compress stdin to stdout
40
+ # Omnizip::Pipe.compress($stdin, $stdout, format: :zip)
41
+ #
42
+ # @example Compress file with custom options
43
+ # File.open('data.txt', 'rb') do |input|
44
+ # File.open('data.zip', 'wb') do |output|
45
+ # Omnizip::Pipe.compress(
46
+ # input, output,
47
+ # format: :zip,
48
+ # compression: :deflate,
49
+ # level: 9,
50
+ # entry_name: 'data.txt'
51
+ # )
52
+ # end
53
+ # end
54
+ def compress(input, output, format: :zip, compression: nil, **options)
55
+ compressor = Pipe::StreamCompressor.new(
56
+ input,
57
+ output,
58
+ format,
59
+ compression: compression,
60
+ **options,
61
+ )
62
+ compressor.compress
63
+ end
64
+
65
+ # Decompress from input stream
66
+ #
67
+ # @param input [IO] Input stream containing archive
68
+ # @param output_dir [String, nil] Directory to extract to (nil = stdout)
69
+ # @param options [Hash] Extraction options
70
+ # @return [Integer, Hash] Bytes written or extracted files hash
71
+ #
72
+ # @example Extract to directory
73
+ # Omnizip::Pipe.decompress($stdin, output_dir: 'extracted/')
74
+ #
75
+ # @example Extract to stdout (single file)
76
+ # Omnizip::Pipe.decompress($stdin, output: $stdout)
77
+ def decompress(input, output_dir: nil, output: nil, **options)
78
+ decompressor = Pipe::StreamDecompressor.new(
79
+ input,
80
+ output_dir: output_dir,
81
+ output: output,
82
+ **options,
83
+ )
84
+ decompressor.decompress
85
+ end
86
+
87
+ # Check if running in pipe mode
88
+ #
89
+ # Detects if stdin/stdout are pipes (not TTY), indicating
90
+ # the program is being used in a Unix pipeline.
91
+ #
92
+ # @return [Boolean] True if stdin and stdout are pipes
93
+ #
94
+ # @example
95
+ # if Omnizip::Pipe.pipe_mode?
96
+ # # Process stdin to stdout
97
+ # else
98
+ # # Show interactive help
99
+ # end
100
+ def pipe_mode?
101
+ !$stdin.tty? && !$stdout.tty?
102
+ end
103
+
104
+ # Detect if input is stdin
105
+ #
106
+ # @param input [String, IO] Input path or IO object
107
+ # @return [Boolean] True if input is stdin
108
+ def stdin?(input)
109
+ ["-", $stdin].include?(input)
110
+ end
111
+
112
+ # Detect if output is stdout
113
+ #
114
+ # @param output [String, IO] Output path or IO object
115
+ # @return [Boolean] True if output is stdout
116
+ def stdout?(output)
117
+ ["-", $stdout].include?(output)
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
4
+ require_relative "../platform"
5
+
6
+ module Omnizip
7
+ module Platform
8
+ # NTFS Alternate Data Streams handler
9
+ # Windows-only feature for managing file alternate streams
10
+ module NtfsStreams
11
+ # Check if NTFS streams are available
12
+ #
13
+ # @return [Boolean] true if available
14
+ def self.available?
15
+ Platform.supports_ntfs_streams?
16
+ end
17
+
18
+ # List alternate data streams for a file
19
+ #
20
+ # @param file_path [String] Path to file
21
+ # @return [Array<String>] List of stream names
22
+ def self.list_streams(file_path)
23
+ return [] unless available?
24
+ return [] unless File.exist?(file_path)
25
+
26
+ streams = []
27
+
28
+ begin
29
+ # Use PowerShell to list streams
30
+ cmd = "powershell -Command \"Get-Item '#{file_path}' -Stream * | " \
31
+ "Select-Object -ExpandProperty Stream\""
32
+ output = `#{cmd} 2>&1`
33
+
34
+ if $CHILD_STATUS.success?
35
+ streams = output.lines.map(&:strip).reject do |s|
36
+ s.empty? || s == ":$DATA"
37
+ end
38
+ end
39
+ rescue StandardError => e
40
+ warn "Failed to list NTFS streams: #{e.message}" if ENV["DEBUG"]
41
+ end
42
+
43
+ streams
44
+ end
45
+
46
+ # Read alternate data stream
47
+ #
48
+ # @param file_path [String] Path to file
49
+ # @param stream_name [String] Stream name
50
+ # @return [String, nil] Stream content or nil
51
+ def self.read_stream(file_path, stream_name)
52
+ return nil unless available?
53
+ return nil unless File.exist?(file_path)
54
+
55
+ begin
56
+ # Read using alternate stream syntax: file.txt:StreamName
57
+ stream_path = "#{file_path}:#{stream_name}"
58
+ File.binread(stream_path)
59
+ rescue Errno::ENOENT, Errno::EINVAL
60
+ nil
61
+ rescue StandardError => e
62
+ warn "Failed to read NTFS stream: #{e.message}" if ENV["DEBUG"]
63
+ nil
64
+ end
65
+ end
66
+
67
+ # Write alternate data stream
68
+ #
69
+ # @param file_path [String] Path to file
70
+ # @param stream_name [String] Stream name
71
+ # @param data [String] Stream data
72
+ # @return [Boolean] true if successful
73
+ def self.write_stream(file_path, stream_name, data)
74
+ return false unless available?
75
+ return false unless File.exist?(file_path)
76
+
77
+ begin
78
+ # Write using alternate stream syntax: file.txt:StreamName
79
+ stream_path = "#{file_path}:#{stream_name}"
80
+ File.binwrite(stream_path, data)
81
+ true
82
+ rescue StandardError => e
83
+ warn "Failed to write NTFS stream: #{e.message}" if ENV["DEBUG"]
84
+ false
85
+ end
86
+ end
87
+
88
+ # Delete alternate data stream
89
+ #
90
+ # @param file_path [String] Path to file
91
+ # @param stream_name [String] Stream name
92
+ # @return [Boolean] true if successful
93
+ def self.delete_stream(file_path, stream_name)
94
+ return false unless available?
95
+ return false unless File.exist?(file_path)
96
+
97
+ begin
98
+ # Use PowerShell to remove stream
99
+ cmd = "powershell -Command \"Remove-Item -Path '#{file_path}' " \
100
+ "-Stream '#{stream_name}'\""
101
+ system(cmd)
102
+ $CHILD_STATUS.success?
103
+ rescue StandardError => e
104
+ warn "Failed to delete NTFS stream: #{e.message}" if ENV["DEBUG"]
105
+ false
106
+ end
107
+ end
108
+
109
+ # Copy all alternate streams from source to destination
110
+ #
111
+ # @param source_path [String] Source file
112
+ # @param dest_path [String] Destination file
113
+ # @return [Integer] Number of streams copied
114
+ def self.copy_streams(source_path, dest_path)
115
+ return 0 unless available?
116
+ return 0 unless File.exist?(source_path)
117
+ return 0 unless File.exist?(dest_path)
118
+
119
+ copied = 0
120
+ streams = list_streams(source_path)
121
+
122
+ streams.each do |stream|
123
+ next if stream == "$DATA" # Skip main data stream
124
+
125
+ data = read_stream(source_path, stream)
126
+ copied += 1 if data && write_stream(dest_path, stream, data)
127
+ end
128
+
129
+ copied
130
+ end
131
+
132
+ # Get stream information
133
+ #
134
+ # @param file_path [String] Path to file
135
+ # @param stream_name [String] Stream name
136
+ # @return [Hash, nil] Stream info or nil
137
+ def self.stream_info(file_path, stream_name)
138
+ return nil unless available?
139
+ return nil unless File.exist?(file_path)
140
+
141
+ data = read_stream(file_path, stream_name)
142
+ return nil unless data
143
+
144
+ {
145
+ name: stream_name,
146
+ size: data.bytesize,
147
+ exists: true,
148
+ }
149
+ end
150
+
151
+ # Check if file has any alternate streams
152
+ #
153
+ # @param file_path [String] Path to file
154
+ # @return [Boolean] true if has streams
155
+ def self.has_streams?(file_path)
156
+ return false unless available?
157
+
158
+ streams = list_streams(file_path)
159
+ streams.any? { |s| s != "$DATA" }
160
+ end
161
+
162
+ # Archive streams to a hash
163
+ #
164
+ # @param file_path [String] Path to file
165
+ # @return [Hash<String, String>] Stream name => data
166
+ def self.archive_streams(file_path)
167
+ return {} unless available?
168
+
169
+ streams_data = {}
170
+ streams = list_streams(file_path)
171
+
172
+ streams.each do |stream|
173
+ next if stream == "$DATA"
174
+
175
+ data = read_stream(file_path, stream)
176
+ streams_data[stream] = data if data
177
+ end
178
+
179
+ streams_data
180
+ end
181
+
182
+ # Restore streams from hash
183
+ #
184
+ # @param file_path [String] Path to file
185
+ # @param streams_data [Hash<String, String>] Stream name => data
186
+ # @return [Integer] Number of streams restored
187
+ def self.restore_streams(file_path, streams_data)
188
+ return 0 unless available?
189
+ return 0 unless File.exist?(file_path)
190
+
191
+ restored = 0
192
+
193
+ streams_data.each do |stream_name, data|
194
+ restored += 1 if write_stream(file_path, stream_name, data)
195
+ end
196
+
197
+ restored
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ # Platform detection and capabilities
5
+ # Provides cross-platform compatibility checks
6
+ module Platform
7
+ # Detect if running on Windows
8
+ #
9
+ # @return [Boolean] true if Windows
10
+ def self.windows?
11
+ !!(RUBY_PLATFORM =~ /mswin|mingw|cygwin/)
12
+ end
13
+
14
+ # Detect if running on macOS
15
+ #
16
+ # @return [Boolean] true if macOS
17
+ def self.macos?
18
+ !!RUBY_PLATFORM.include?("darwin")
19
+ end
20
+
21
+ # Detect if running on Linux
22
+ #
23
+ # @return [Boolean] true if Linux
24
+ def self.linux?
25
+ !!RUBY_PLATFORM.include?("linux")
26
+ end
27
+
28
+ # Detect if running on Unix-like system
29
+ #
30
+ # @return [Boolean] true if Unix-like (macOS, Linux, BSD, etc.)
31
+ def self.unix?
32
+ !windows?
33
+ end
34
+
35
+ # Get platform name
36
+ #
37
+ # @return [String] Platform name
38
+ def self.name
39
+ return "Windows" if windows?
40
+ return "macOS" if macos?
41
+ return "Linux" if linux?
42
+
43
+ "Unknown"
44
+ end
45
+
46
+ # Check if NTFS alternate streams are supported
47
+ # Only available on Windows with NTFS filesystem
48
+ #
49
+ # @return [Boolean] true if NTFS streams supported
50
+ def self.supports_ntfs_streams?
51
+ windows?
52
+ end
53
+
54
+ # Check if symbolic links are supported
55
+ #
56
+ # @return [Boolean] true if symlinks supported
57
+ def self.supports_symlinks?
58
+ unix? || (windows? && windows_developer_mode?)
59
+ end
60
+
61
+ # Check if hard links are supported
62
+ #
63
+ # @return [Boolean] true if hard links supported
64
+ def self.supports_hardlinks?
65
+ true # Supported on all modern platforms
66
+ end
67
+
68
+ # Check if extended attributes are supported
69
+ #
70
+ # @return [Boolean] true if xattrs supported
71
+ def self.supports_extended_attributes?
72
+ unix?
73
+ end
74
+
75
+ # Check if file permissions are supported
76
+ #
77
+ # @return [Boolean] true if POSIX permissions supported
78
+ def self.supports_file_permissions?
79
+ unix?
80
+ end
81
+
82
+ # Get platform-specific features
83
+ #
84
+ # @return [Hash] Feature flags
85
+ def self.features
86
+ {
87
+ ntfs_streams: supports_ntfs_streams?,
88
+ symlinks: supports_symlinks?,
89
+ hardlinks: supports_hardlinks?,
90
+ extended_attributes: supports_extended_attributes?,
91
+ file_permissions: supports_file_permissions?,
92
+ }
93
+ end
94
+
95
+ # Check Windows Developer Mode (for symlink support)
96
+ #
97
+ # @return [Boolean] true if developer mode enabled
98
+ def self.windows_developer_mode?
99
+ return false unless windows?
100
+
101
+ # Check registry for developer mode setting
102
+ # HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock
103
+ # AllowDevelopmentWithoutDevLicense = 1
104
+ begin
105
+ require "win32/registry"
106
+ Win32::Registry::HKEY_LOCAL_MACHINE.open(
107
+ 'SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock',
108
+ Win32::Registry::KEY_READ,
109
+ ) do |reg|
110
+ value = reg["AllowDevelopmentWithoutDevLicense"]
111
+ return value == 1
112
+ end
113
+ rescue LoadError, StandardError
114
+ # win32/registry not available or key doesn't exist
115
+ false
116
+ end
117
+ end
118
+
119
+ # Get file system type for a path
120
+ #
121
+ # @param path [String] File or directory path
122
+ # @return [String, nil] Filesystem type or nil if unknown
123
+ def self.filesystem_type(path)
124
+ return nil unless File.exist?(path)
125
+
126
+ if windows?
127
+ detect_windows_filesystem(path)
128
+ elsif macos?
129
+ detect_macos_filesystem(path)
130
+ elsif linux?
131
+ detect_linux_filesystem(path)
132
+ end
133
+ end
134
+
135
+ # Check if path is on NTFS filesystem
136
+ #
137
+ # @param path [String] File or directory path
138
+ # @return [Boolean] true if NTFS
139
+ def self.ntfs?(path)
140
+ filesystem_type(path)&.upcase == "NTFS"
141
+ end
142
+
143
+ # Detect Windows filesystem type
144
+ #
145
+ # @param path [String] Path
146
+ # @return [String, nil] Filesystem type
147
+ def self.detect_windows_filesystem(path)
148
+ # Get drive letter
149
+ drive = File.expand_path(path)[0, 2]
150
+ return nil unless drive =~ /^[A-Za-z]:$/
151
+
152
+ # Use fsutil to get filesystem type
153
+ output = `fsutil fsinfo volumeinfo #{drive} 2>&1`
154
+ Regexp.last_match(1) if output =~ /File System Name\s*:\s*(\w+)/i
155
+ rescue StandardError
156
+ nil
157
+ end
158
+
159
+ # Detect macOS filesystem type
160
+ #
161
+ # @param path [String] Path
162
+ # @return [String, nil] Filesystem type
163
+ def self.detect_macos_filesystem(path)
164
+ output = `df -T #{path} 2>&1`.lines.last
165
+ return nil unless output
166
+
167
+ # Format: filesystem type blocks used avail capacity mounted
168
+ parts = output.split
169
+ parts[1] if parts.size > 1
170
+ rescue StandardError
171
+ nil
172
+ end
173
+
174
+ # Detect Linux filesystem type
175
+ #
176
+ # @param path [String] Path
177
+ # @return [String, nil] Filesystem type
178
+ def self.detect_linux_filesystem(path)
179
+ output = `df -T #{path} 2>&1`.lines.last
180
+ return nil unless output
181
+
182
+ # Format: filesystem type blocks used avail use% mounted
183
+ parts = output.split
184
+ parts[1] if parts.size > 1
185
+ rescue StandardError
186
+ nil
187
+ end
188
+ end
189
+ end