omnizip 0.3.8 → 0.3.10

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 (201) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +51 -50
  3. data/Gemfile +2 -0
  4. data/Rakefile +3 -0
  5. data/TODO.refactor/00-overview.md +67 -0
  6. data/TODO.refactor/01-registry-base-class.md +81 -0
  7. data/TODO.refactor/02-error-hierarchy.md +57 -0
  8. data/TODO.refactor/03-autoload-entry-point.md +69 -0
  9. data/TODO.refactor/04-filter-base-consolidation.md +49 -0
  10. data/TODO.refactor/05-send-private-methods.md +64 -0
  11. data/TODO.refactor/06-instance-variable-access.md +69 -0
  12. data/TODO.refactor/07-respond-to-replacement.md +91 -0
  13. data/TODO.refactor/08-spec-doubles.md +27 -0
  14. data/TODO.refactor/09-cli-shared-module.md +35 -0
  15. data/TODO.refactor/10-format-detector-ocp.md +34 -0
  16. data/TODO.refactor/11-convenience-decoupling.md +38 -0
  17. data/TODO.refactor/12-thread-safety.md +8 -0
  18. data/TODO.refactor/13-lutaml-model-migration.md +82 -0
  19. data/TODO.refactor/14-add-missing-specs.md +25 -0
  20. data/lib/omnizip/algorithm.rb +0 -2
  21. data/lib/omnizip/algorithm_registry.rb +29 -57
  22. data/lib/omnizip/algorithms/lzma/bit_model.rb +1 -1
  23. data/lib/omnizip/algorithms/lzma/dictionary.rb +4 -3
  24. data/lib/omnizip/algorithms/lzma/distance_coder.rb +5 -102
  25. data/lib/omnizip/algorithms/lzma/length_coder.rb +4 -92
  26. data/lib/omnizip/algorithms/lzma/literal_decoder.rb +4 -119
  27. data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +2 -0
  28. data/lib/omnizip/algorithms/lzma/match_finder.rb +59 -29
  29. data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +20 -12
  30. data/lib/omnizip/algorithms/lzma/range_decoder.rb +77 -269
  31. data/lib/omnizip/algorithms/lzma/range_encoder.rb +0 -9
  32. data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +0 -15
  33. data/lib/omnizip/algorithms/lzma/xz_state.rb +0 -8
  34. data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +189 -968
  35. data/lib/omnizip/algorithms/lzma2/encoder.rb +0 -2
  36. data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +0 -2
  37. data/lib/omnizip/algorithms/lzma2/properties.rb +0 -2
  38. data/lib/omnizip/algorithms/ppmd7/model.rb +1 -1
  39. data/lib/omnizip/algorithms/ppmd8/context.rb +0 -2
  40. data/lib/omnizip/algorithms/ppmd8/model.rb +1 -1
  41. data/lib/omnizip/algorithms/ppmd8.rb +0 -2
  42. data/lib/omnizip/algorithms/ppmd_base.rb +0 -2
  43. data/lib/omnizip/algorithms/sevenzip_lzma2.rb +0 -5
  44. data/lib/omnizip/algorithms/xz_lzma2.rb +0 -5
  45. data/lib/omnizip/algorithms/zstandard/frame/header.rb +3 -5
  46. data/lib/omnizip/algorithms.rb +1 -0
  47. data/lib/omnizip/archive_handler.rb +75 -0
  48. data/lib/omnizip/archive_handlers/tar_handler.rb +51 -0
  49. data/lib/omnizip/archive_handlers/zip_handler.rb +73 -0
  50. data/lib/omnizip/archive_handlers.rb +11 -0
  51. data/lib/omnizip/checksum_registry.rb +23 -96
  52. data/lib/omnizip/cli/shared.rb +31 -0
  53. data/lib/omnizip/cli.rb +6 -43
  54. data/lib/omnizip/commands/archive_create_command.rb +0 -6
  55. data/lib/omnizip/commands/archive_extract_command.rb +0 -4
  56. data/lib/omnizip/commands/archive_list_command.rb +0 -4
  57. data/lib/omnizip/commands/archive_repair_command.rb +0 -2
  58. data/lib/omnizip/commands/archive_verify_command.rb +0 -2
  59. data/lib/omnizip/commands/compress_command.rb +0 -2
  60. data/lib/omnizip/commands/decompress_command.rb +0 -2
  61. data/lib/omnizip/commands/list_command.rb +0 -2
  62. data/lib/omnizip/commands/metadata_command.rb +0 -5
  63. data/lib/omnizip/commands/parity_create_command.rb +0 -2
  64. data/lib/omnizip/commands/parity_repair_command.rb +0 -2
  65. data/lib/omnizip/commands/parity_verify_command.rb +0 -2
  66. data/lib/omnizip/commands/profile_list_command.rb +0 -2
  67. data/lib/omnizip/commands/profile_show_command.rb +0 -2
  68. data/lib/omnizip/convenience.rb +111 -234
  69. data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +1 -1
  70. data/lib/omnizip/crypto/aes256/cipher.rb +6 -1
  71. data/lib/omnizip/error.rb +22 -18
  72. data/lib/omnizip/filter.rb +5 -3
  73. data/lib/omnizip/filter_registry.rb +74 -98
  74. data/lib/omnizip/filters/bcj2/decoder.rb +196 -130
  75. data/lib/omnizip/filters/bcj2.rb +0 -2
  76. data/lib/omnizip/filters/bcj_x86.rb +0 -2
  77. data/lib/omnizip/filters/filter_base.rb +4 -70
  78. data/lib/omnizip/filters/registry.rb +0 -2
  79. data/lib/omnizip/format_detector.rb +21 -12
  80. data/lib/omnizip/format_registry.rb +79 -38
  81. data/lib/omnizip/formats/bzip2_file.rb +0 -2
  82. data/lib/omnizip/formats/cpio/entry.rb +0 -2
  83. data/lib/omnizip/formats/cpio/reader.rb +0 -1
  84. data/lib/omnizip/formats/cpio/writer.rb +0 -1
  85. data/lib/omnizip/formats/cpio.rb +0 -1
  86. data/lib/omnizip/formats/format_spec_loader.rb +1 -1
  87. data/lib/omnizip/formats/gzip.rb +0 -2
  88. data/lib/omnizip/formats/iso/directory_record.rb +6 -3
  89. data/lib/omnizip/formats/iso/reader.rb +2 -2
  90. data/lib/omnizip/formats/iso.rb +0 -1
  91. data/lib/omnizip/formats/lzip.rb +5 -25
  92. data/lib/omnizip/formats/lzma_alone.rb +7 -25
  93. data/lib/omnizip/formats/msi/cab_extractor.rb +181 -0
  94. data/lib/omnizip/formats/msi/constants.rb +157 -0
  95. data/lib/omnizip/formats/msi/directory_resolver.rb +188 -0
  96. data/lib/omnizip/formats/msi/entry.rb +128 -0
  97. data/lib/omnizip/formats/msi/reader.rb +301 -0
  98. data/lib/omnizip/formats/msi/string_pool.rb +246 -0
  99. data/lib/omnizip/formats/msi/table_parser.rb +244 -0
  100. data/lib/omnizip/formats/msi.rb +84 -0
  101. data/lib/omnizip/formats/ole/allocation_table.rb +10 -1
  102. data/lib/omnizip/formats/ole/dirent.rb +20 -3
  103. data/lib/omnizip/formats/ole/header.rb +0 -1
  104. data/lib/omnizip/formats/ole/ranges_io.rb +21 -3
  105. data/lib/omnizip/formats/ole/storage.rb +19 -4
  106. data/lib/omnizip/formats/ole.rb +0 -2
  107. data/lib/omnizip/formats/rar/block_parser.rb +0 -1
  108. data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +1 -1
  109. data/lib/omnizip/formats/rar/decompressor.rb +38 -1
  110. data/lib/omnizip/formats/rar/header.rb +0 -1
  111. data/lib/omnizip/formats/rar/rar5/writer.rb +0 -2
  112. data/lib/omnizip/formats/rar/reader.rb +2 -3
  113. data/lib/omnizip/formats/rar/writer.rb +0 -1
  114. data/lib/omnizip/formats/rar.rb +2 -2
  115. data/lib/omnizip/formats/rpm/entry.rb +0 -1
  116. data/lib/omnizip/formats/rpm/header.rb +3 -8
  117. data/lib/omnizip/formats/rpm/lead.rb +37 -26
  118. data/lib/omnizip/formats/rpm/tag.rb +0 -1
  119. data/lib/omnizip/formats/rpm/writer.rb +0 -1
  120. data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +98 -61
  121. data/lib/omnizip/formats/seven_zip/coder_chain.rb +1 -2
  122. data/lib/omnizip/formats/seven_zip/encoded_header.rb +0 -1
  123. data/lib/omnizip/formats/seven_zip/encrypted_header.rb +0 -1
  124. data/lib/omnizip/formats/seven_zip/header.rb +0 -1
  125. data/lib/omnizip/formats/seven_zip/header_encryptor.rb +0 -1
  126. data/lib/omnizip/formats/seven_zip/header_writer.rb +22 -61
  127. data/lib/omnizip/formats/seven_zip/models/file_entry.rb +1 -1
  128. data/lib/omnizip/formats/seven_zip/parser.rb +14 -13
  129. data/lib/omnizip/formats/seven_zip/reader.rb +82 -19
  130. data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +0 -15
  131. data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +0 -1
  132. data/lib/omnizip/formats/seven_zip/writer.rb +22 -68
  133. data/lib/omnizip/formats/seven_zip.rb +63 -5
  134. data/lib/omnizip/formats/tar/entry.rb +0 -2
  135. data/lib/omnizip/formats/tar/header.rb +0 -2
  136. data/lib/omnizip/formats/tar/reader.rb +0 -2
  137. data/lib/omnizip/formats/tar/writer.rb +0 -2
  138. data/lib/omnizip/formats/tar.rb +0 -1
  139. data/lib/omnizip/formats/xar/entry.rb +0 -1
  140. data/lib/omnizip/formats/xar/header.rb +0 -1
  141. data/lib/omnizip/formats/xar/reader.rb +0 -1
  142. data/lib/omnizip/formats/xar/toc.rb +3 -3
  143. data/lib/omnizip/formats/xar/writer.rb +11 -3
  144. data/lib/omnizip/formats/xar.rb +0 -1
  145. data/lib/omnizip/formats/xz/reader.rb +7 -3
  146. data/lib/omnizip/formats/xz.rb +3 -40
  147. data/lib/omnizip/formats/xz_impl/block_decoder.rb +4 -10
  148. data/lib/omnizip/formats/xz_impl/block_encoder.rb +0 -2
  149. data/lib/omnizip/formats/xz_impl/block_header_parser.rb +0 -2
  150. data/lib/omnizip/formats/xz_impl/index_decoder.rb +0 -1
  151. data/lib/omnizip/formats/xz_impl/index_encoder.rb +0 -1
  152. data/lib/omnizip/formats/xz_impl/stream_encoder.rb +1 -1
  153. data/lib/omnizip/formats/xz_impl/stream_footer.rb +0 -1
  154. data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +0 -1
  155. data/lib/omnizip/formats/xz_impl/stream_header.rb +0 -1
  156. data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +0 -2
  157. data/lib/omnizip/formats/zip/central_directory_header.rb +2 -3
  158. data/lib/omnizip/formats/zip/end_of_central_directory.rb +0 -2
  159. data/lib/omnizip/formats/zip/local_file_header.rb +0 -2
  160. data/lib/omnizip/formats/zip/reader.rb +12 -2
  161. data/lib/omnizip/formats/zip/writer.rb +73 -1
  162. data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +0 -2
  163. data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +0 -2
  164. data/lib/omnizip/formats/zip/zip64_extra_field.rb +0 -2
  165. data/lib/omnizip/formats/zip.rb +0 -1
  166. data/lib/omnizip/formats.rb +9 -22
  167. data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +2 -2
  168. data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +1 -1
  169. data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +0 -6
  170. data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +18 -185
  171. data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +3 -3
  172. data/lib/omnizip/io/source.rb +136 -0
  173. data/lib/omnizip/io.rb +2 -0
  174. data/lib/omnizip/link_handler.rb +7 -3
  175. data/lib/omnizip/metadata/metadata_editor.rb +1 -1
  176. data/lib/omnizip/models/algorithm_metadata.rb +33 -60
  177. data/lib/omnizip/models/compression_options.rb +40 -53
  178. data/lib/omnizip/models/conversion_options.rb +9 -18
  179. data/lib/omnizip/models/parallel_options.rb +25 -0
  180. data/lib/omnizip/optimization_registry.rb +8 -44
  181. data/lib/omnizip/parallel/job_scheduler.rb +26 -8
  182. data/lib/omnizip/parallel/parallel_compressor.rb +10 -20
  183. data/lib/omnizip/parallel/parallel_extractor.rb +20 -11
  184. data/lib/omnizip/parity/par2_repairer.rb +5 -5
  185. data/lib/omnizip/parity/par2_verifier.rb +27 -22
  186. data/lib/omnizip/parity.rb +4 -4
  187. data/lib/omnizip/password/encryption_registry.rb +11 -50
  188. data/lib/omnizip/password/zip_crypto_strategy.rb +1 -4
  189. data/lib/omnizip/pipe/stream_decompressor.rb +2 -3
  190. data/lib/omnizip/profile/archive_profile.rb +0 -2
  191. data/lib/omnizip/profile/binary_profile.rb +0 -2
  192. data/lib/omnizip/profile/compression_profile.rb +0 -1
  193. data/lib/omnizip/profile/profile_detector.rb +0 -2
  194. data/lib/omnizip/profile/text_profile.rb +0 -2
  195. data/lib/omnizip/profile.rb +0 -2
  196. data/lib/omnizip/registry.rb +112 -0
  197. data/lib/omnizip/version.rb +1 -1
  198. data/lib/omnizip/zip/file.rb +20 -9
  199. data/lib/omnizip.rb +26 -55
  200. data/lib/tasks/msi_test_files.rake +66 -0
  201. metadata +57 -6
@@ -58,6 +58,12 @@ module Omnizip
58
58
  # @return [Hash] Parsed PAR2 metadata
59
59
  attr_reader :metadata
60
60
 
61
+ # @return [Array<Hash>] Parsed file list (one entry per managed file)
62
+ attr_reader :file_list
63
+
64
+ # @return [Array] Available recovery blocks
65
+ attr_reader :recovery_blocks
66
+
61
67
  # Initialize verifier
62
68
  #
63
69
  # @param par2_file [String] Path to .par2 index file
@@ -125,11 +131,11 @@ module Omnizip
125
131
  )
126
132
  end
127
133
 
128
- private
129
-
130
- # Parse PAR2 index file using packet models
134
+ # Parse the PAR2 index file and populate +metadata+, +file_list+,
135
+ # and +recovery_blocks+. Idempotent — safe to call multiple times.
136
+ #
137
+ # @return [void]
131
138
  def parse_par2_file
132
- # Reset state to prevent accumulation if called multiple times
133
139
  @metadata = {}
134
140
  @file_list = []
135
141
  @recovery_blocks = []
@@ -159,24 +165,6 @@ module Omnizip
159
165
  load_recovery_volumes
160
166
  end
161
167
 
162
- # Process packet using model-based approach
163
- #
164
- # @param packet [Models::Packet] Parsed packet model
165
- def process_packet_model(packet)
166
- case packet
167
- when Models::MainPacket
168
- process_main_packet_model(packet)
169
- when Models::FileDescriptionPacket
170
- process_file_description_packet_model(packet)
171
- when Models::IfscPacket
172
- process_ifsc_packet_model(packet)
173
- when Models::RecoverySlicePacket
174
- process_recovery_packet_model(packet)
175
- when Models::CreatorPacket
176
- # Creator packets are informational only
177
- end
178
- end
179
-
180
168
  # Process main packet model
181
169
  #
182
170
  # @param packet [Models::MainPacket] Main packet
@@ -358,6 +346,23 @@ module Omnizip
358
346
  (file_info[:size].to_f / @metadata[:block_size]).ceil
359
347
  end
360
348
  end
349
+
350
+ private
351
+
352
+ def process_packet_model(packet)
353
+ case packet
354
+ when Models::MainPacket
355
+ process_main_packet_model(packet)
356
+ when Models::FileDescriptionPacket
357
+ process_file_description_packet_model(packet)
358
+ when Models::IfscPacket
359
+ process_ifsc_packet_model(packet)
360
+ when Models::RecoverySlicePacket
361
+ process_recovery_packet_model(packet)
362
+ when Models::CreatorPacket
363
+ # Creator packets are informational only
364
+ end
365
+ end
361
366
  end
362
367
  end
363
368
  end
@@ -175,16 +175,16 @@ module Omnizip
175
175
  return nil unless File.exist?(par2_file)
176
176
 
177
177
  verifier = Par2Verifier.new(par2_file)
178
- verifier.send(:parse_par2_file)
178
+ verifier.parse_par2_file
179
179
 
180
- total_blocks = verifier.send(:calculate_total_blocks)
181
- recovery_blocks = verifier.instance_variable_get(:@recovery_blocks).size
180
+ total_blocks = verifier.calculate_total_blocks
181
+ recovery_blocks = verifier.recovery_blocks.size
182
182
 
183
183
  {
184
184
  par2_file: par2_file,
185
185
  block_size: verifier.metadata[:block_size],
186
186
  total_blocks: total_blocks,
187
- file_count: verifier.metadata[:file_count] || verifier.instance_variable_get(:@file_list).size,
187
+ file_count: verifier.metadata[:file_count] || verifier.file_list.size,
188
188
  recovery_blocks: recovery_blocks,
189
189
  redundancy: calculate_redundancy(total_blocks, recovery_blocks),
190
190
  }
@@ -2,64 +2,25 @@
2
2
 
3
3
  module Omnizip
4
4
  module Password
5
- # Registry for encryption strategies
6
- class EncryptionRegistry
7
- @strategies = {}
8
-
5
+ class EncryptionRegistry < Omnizip::Registry
9
6
  class << self
10
- # Register an encryption strategy
11
- # @param name [Symbol] Strategy name
12
- # @param strategy_class [Class] Strategy class
13
- def register(name, strategy_class)
14
- @strategies[name] = strategy_class
15
- end
16
-
17
- # Get a strategy by name
18
- # @param name [Symbol] Strategy name
19
- # @return [Class] Strategy class
20
- # @raise [ArgumentError] If strategy not found
21
- def get(name)
22
- strategy = @strategies[name]
23
- return strategy if strategy
24
-
25
- raise ArgumentError, "Unknown encryption strategy: #{name}. " \
26
- "Available: #{@strategies.keys.join(', ')}"
27
- end
28
-
29
- # Check if strategy is registered
30
- # @param name [Symbol] Strategy name
31
- # @return [Boolean] True if registered
32
- def registered?(name)
33
- @strategies.key?(name)
7
+ def not_found_error_class
8
+ Omnizip::UnknownEncryptionStrategyError
34
9
  end
35
10
 
36
- # Get all registered strategy names
37
- # @return [Array<Symbol>] Strategy names
38
- def strategies
39
- @strategies.keys
11
+ def label
12
+ "Encryption strategy"
40
13
  end
41
14
 
42
- # Create a strategy instance
43
- # @param name [Symbol] Strategy name
44
- # @param password [String] Password
45
- # @param options [Hash] Strategy options
46
- # @return [EncryptionStrategy] Strategy instance
47
15
  def create(name, password, **options)
48
- strategy_class = get(name)
49
- strategy_class.new(password, **options)
50
- end
51
-
52
- # Reset registry (for testing)
53
- def reset
54
- @strategies = {}
16
+ get(name).new(password, **options)
55
17
  end
56
18
  end
57
19
  end
58
-
59
- # Register built-in strategies
60
- EncryptionRegistry.register(:traditional, ZipCryptoStrategy)
61
- EncryptionRegistry.register(:zip_crypto, ZipCryptoStrategy)
62
- EncryptionRegistry.register(:winzip_aes, WinzipAesStrategy)
63
- EncryptionRegistry.register(:aes256, WinzipAesStrategy)
64
20
  end
65
21
  end
22
+
23
+ Omnizip::Password::EncryptionRegistry.register(:traditional, Omnizip::Password::ZipCryptoStrategy)
24
+ Omnizip::Password::EncryptionRegistry.register(:zip_crypto, Omnizip::Password::ZipCryptoStrategy)
25
+ Omnizip::Password::EncryptionRegistry.register(:winzip_aes, Omnizip::Password::WinzipAesStrategy)
26
+ Omnizip::Password::EncryptionRegistry.register(:aes256, Omnizip::Password::WinzipAesStrategy)
@@ -114,10 +114,7 @@ module Omnizip
114
114
  # @param byte [Integer] Byte to add
115
115
  # @return [Integer] Updated CRC
116
116
  def crc32_update(crc, byte)
117
- crc32 = Omnizip::Checksums::Crc32.new
118
- crc32.instance_variable_set(:@crc, crc)
119
- crc32.update(byte.chr)
120
- crc32.value
117
+ Omnizip::Checksums::Crc32.new.process_byte(crc, byte)
121
118
  end
122
119
 
123
120
  # Generate encryption header
@@ -163,9 +163,8 @@ module Omnizip
163
163
  end
164
164
 
165
165
  # Preserve timestamp if available
166
- if entry.respond_to?(:mtime) && entry.mtime
167
- File.utime(entry.mtime, entry.mtime, dest_path)
168
- end
166
+ mtime = entry.mtime if entry.is_a?(Omnizip::Metadata::EntryMetadata)
167
+ File.utime(mtime, mtime, dest_path) if mtime
169
168
 
170
169
  bytes
171
170
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "omnizip/file_type/mime_classifier"
4
-
5
3
  module Omnizip
6
4
  module Profile
7
5
  # Archive compression profile
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "omnizip/file_type/mime_classifier"
4
-
5
3
  module Omnizip
6
4
  module Profile
7
5
  # Binary compression profile
@@ -148,7 +148,6 @@ module Omnizip
148
148
 
149
149
  # Auto-select BCJ filter based on MIME type
150
150
  # For executables, default to x86 architecture
151
- require "omnizip/file_type/mime_classifier"
152
151
  if FileType::MimeClassifier.executable?(mime_type)
153
152
  :bcj_x86
154
153
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "omnizip/file_type/mime_classifier"
4
-
5
3
  module Omnizip
6
4
  module Profile
7
5
  # Automatic profile detection based on file analysis
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "omnizip/file_type/mime_classifier"
4
-
5
3
  module Omnizip
6
4
  module Profile
7
5
  # Text compression profile
@@ -161,8 +161,6 @@ module Omnizip
161
161
  # @param mime_type [String] MIME type string
162
162
  # @return [CompressionProfile] Best profile
163
163
  def select_best_profile_for_mime(profiles, mime_type)
164
- require "omnizip/file_type/mime_classifier"
165
-
166
164
  # Determine category from MIME type
167
165
  category = FileType::MimeClassifier.profile_category(mime_type)
168
166
 
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ # Generic, thread-safe registry base class.
5
+ #
6
+ # Subclasses override the hooks +#not_found_error_class+,
7
+ # +#normalize_key+, and +#label+ to specialize behavior. They may also
8
+ # override +#register+ / +#get+ to add domain-specific arguments
9
+ # (e.g., +#register_with_formats+ on FilterRegistry).
10
+ #
11
+ # The base class is intentionally minimal: it owns a Hash store and a
12
+ # Mutex, and exposes the canonical CRUD quintet (+register+, +get+,
13
+ # +registered?+, +available+, +reset!+). Backward-compat aliases are
14
+ # provided so existing call sites keep working.
15
+ class Registry
16
+ class << self
17
+ def not_found_error_class
18
+ Omnizip::Error
19
+ end
20
+
21
+ def normalize_key(key)
22
+ key.to_sym
23
+ end
24
+
25
+ def label
26
+ return "Item" unless name
27
+
28
+ name.gsub(/^Omnizip::/, "")
29
+ end
30
+
31
+ def register(key, value)
32
+ synchronize { storage[normalize_key(key)] = value }
33
+ value
34
+ end
35
+
36
+ # Register a lazy trigger for +key+. When +get(key)+ is called and
37
+ # the key isn't yet in storage, the block is invoked. The block is
38
+ # expected to trigger autoload of the implementation file (e.g., by
39
+ # referencing a constant), which then self-registers.
40
+ def register_lazy(key, &trigger)
41
+ lazy_triggers[normalize_key(key)] = trigger
42
+ end
43
+
44
+ def get(key)
45
+ normalized = normalize_key(key)
46
+ value = storage[normalized]
47
+ return value if value
48
+
49
+ trigger = lazy_triggers[normalized]
50
+ if trigger
51
+ synchronize { lazy_triggers.delete(normalized) }
52
+ trigger.call
53
+ value = storage[normalized]
54
+ return value if value
55
+ end
56
+
57
+ raise not_found_error_class,
58
+ "#{label} not registered: #{key.inspect}. " \
59
+ "Available: #{available.join(', ')}"
60
+ end
61
+
62
+ def registered?(key)
63
+ normalized = normalize_key(key)
64
+ storage.key?(normalized) || lazy_triggers.key?(normalized)
65
+ end
66
+
67
+ def available
68
+ storage.keys
69
+ end
70
+ alias all available
71
+ alias strategies available
72
+
73
+ def reset!
74
+ # Clear stored entries but keep lazy triggers — the builtin
75
+ # autoload list should survive a reset so subsequent +get+ calls
76
+ # for builtins still trigger the autoload (which re-registers).
77
+ synchronize { storage.clear }
78
+ end
79
+ alias clear reset!
80
+ alias clear! reset!
81
+ alias reset reset!
82
+
83
+ def entries
84
+ synchronize { storage.dup }
85
+ end
86
+
87
+ def each(&block)
88
+ return to_enum(:each) unless block
89
+
90
+ entries.each(&block)
91
+ end
92
+
93
+ private
94
+
95
+ def storage
96
+ @storage ||= {}
97
+ end
98
+
99
+ def lazy_triggers
100
+ @lazy_triggers ||= {}
101
+ end
102
+
103
+ def synchronize(&block)
104
+ mutex.synchronize(&block)
105
+ end
106
+
107
+ def mutex
108
+ @mutex ||= Mutex.new
109
+ end
110
+ end
111
+ end
112
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnizip
4
- VERSION = "0.3.8"
4
+ VERSION = "0.3.10"
5
5
  end
@@ -50,7 +50,6 @@ module Omnizip
50
50
  @modified = false
51
51
  @reader = nil
52
52
  @writer = nil
53
-
54
53
  # Load existing archive if it exists
55
54
  if ::File.exist?(file_path) && !create
56
55
  load_archive
@@ -189,6 +188,18 @@ module Omnizip
189
188
  end
190
189
  alias_method :length, :size
191
190
 
191
+ # Mark this archive as having unsaved changes. The next +#commit+
192
+ # or +#close+ will write the archive to disk.
193
+ def modified!
194
+ @modified = true
195
+ self
196
+ end
197
+
198
+ # True if there are unsaved changes.
199
+ def modified?
200
+ @modified
201
+ end
202
+
192
203
  # Check if archive contains an entry
193
204
  # @param entry_name [String] Entry name to check
194
205
  # @return [Boolean] True if entry exists
@@ -374,8 +385,7 @@ module Omnizip
374
385
  extra_field: "",
375
386
  comment: "",
376
387
  ).tap do |header|
377
- # Store original data in header for writing
378
- header.instance_variable_set(:@_original_data, data)
388
+ header.cached_original_data = data
379
389
  end
380
390
  end
381
391
 
@@ -384,7 +394,7 @@ module Omnizip
384
394
  return "" if entry.directory?
385
395
 
386
396
  # Check if we have cached data (for new entries not yet written)
387
- cached_data = entry.header.instance_variable_get(:@_original_data)
397
+ cached_data = entry.header.cached_original_data
388
398
  return cached_data if cached_data
389
399
 
390
400
  # Otherwise read from file
@@ -416,10 +426,11 @@ module Omnizip
416
426
  return "" unless compressed_data
417
427
 
418
428
  # Decompress
419
- @reader.send(:decompress_data,
420
- compressed_data,
421
- reader_entry.compression_method,
422
- reader_entry.uncompressed_size)
429
+ @reader.decompress(
430
+ compressed_data,
431
+ reader_entry.compression_method,
432
+ reader_entry.uncompressed_size,
433
+ )
423
434
  end
424
435
  end
425
436
 
@@ -447,7 +458,7 @@ module Omnizip
447
458
  next if entry.directory?
448
459
 
449
460
  header = entry.header
450
- cached = header.instance_variable_get(:@_original_data)
461
+ cached = header.cached_original_data
451
462
  entry_data[entry.name] = cached || read_entry_data(entry)
452
463
  end
453
464
 
data/lib/omnizip.rb CHANGED
@@ -17,27 +17,30 @@
17
17
  #
18
18
 
19
19
  module Omnizip
20
- # Core version - autoloaded
21
20
  autoload :VERSION, "omnizip/version"
22
21
 
23
- # Error classes - autoloaded
24
22
  autoload :Error, "omnizip/error"
25
23
  autoload :CompressionError, "omnizip/error"
26
24
  autoload :DecompressionError, "omnizip/error"
27
25
  autoload :AlgorithmNotFoundError, "omnizip/error"
28
26
  autoload :UnknownAlgorithmError, "omnizip/error"
27
+ autoload :UnknownChecksumError, "omnizip/error"
28
+ autoload :UnknownFilterError, "omnizip/error"
29
+ autoload :UnknownEncryptionStrategyError, "omnizip/error"
30
+ autoload :ConversionNotSupportedError, "omnizip/error"
29
31
  autoload :UnsupportedFormatError, "omnizip/error"
30
32
  autoload :FormatError, "omnizip/error"
31
33
  autoload :InvalidArchiveError, "omnizip/error"
34
+ autoload :IOOperationError, "omnizip/error"
32
35
  autoload :IOError, "omnizip/error"
33
36
  autoload :ChecksumError, "omnizip/error"
34
- autoload :OptimizationNotFound, "omnizip/error"
37
+ autoload :OptimizationNotFoundError, "omnizip/error"
35
38
  autoload :ProgressError, "omnizip/error"
36
39
  autoload :ETAError, "omnizip/error"
37
40
  autoload :NotLicensedError, "omnizip/error"
38
41
  autoload :RarNotAvailableError, "omnizip/error"
39
42
 
40
- # Core registries
43
+ autoload :Registry, "omnizip/registry"
41
44
  autoload :Algorithm, "omnizip/algorithm"
42
45
  autoload :AlgorithmRegistry, "omnizip/algorithm_registry"
43
46
  autoload :FormatRegistry, "omnizip/format_registry"
@@ -45,11 +48,9 @@ module Omnizip
45
48
  autoload :ChecksumRegistry, "omnizip/checksum_registry"
46
49
  autoload :FilterRegistry, "omnizip/filter_registry"
47
50
 
48
- # Base classes
49
51
  autoload :Filter, "omnizip/filter"
50
52
  autoload :FilterPipeline, "omnizip/filter_pipeline"
51
53
 
52
- # Feature modules
53
54
  autoload :Buffer, "omnizip/buffer"
54
55
  autoload :Pipe, "omnizip/pipe"
55
56
  autoload :Chunked, "omnizip/chunked"
@@ -66,15 +67,25 @@ module Omnizip
66
67
  autoload :Platform, "omnizip/platform"
67
68
  autoload :Profiler, "omnizip/profiler"
68
69
  autoload :Commands, "omnizip/commands"
69
-
70
- # Sub-module files
70
+ autoload :CliOutputFormatter, "omnizip/cli/output_formatter"
71
+
72
+ # Implementation namespaces. Each namespace file declares autoloads
73
+ # for its concrete classes. The classes self-register with their
74
+ # registry on load (see lib/omnizip/algorithms/lzma.rb etc.).
75
+ autoload :Algorithms, "omnizip/algorithms"
76
+ autoload :Filters, "omnizip/filters"
77
+ autoload :Checksums, "omnizip/checksums"
71
78
  autoload :IO, "omnizip/io"
72
79
  autoload :Crypto, "omnizip/crypto"
73
80
  autoload :Formats, "omnizip/formats"
74
81
  autoload :Zip, "omnizip/zip"
82
+ autoload :Extraction, "omnizip/extraction"
83
+ autoload :Implementations, "omnizip/implementations"
84
+ autoload :ArchiveHandler, "omnizip/archive_handler"
85
+ autoload :ArchiveHandlers, "omnizip/archive_handlers"
86
+ autoload :Convenience, "omnizip/convenience"
75
87
  end
76
88
 
77
- # Models module with autoloaded classes
78
89
  module Omnizip
79
90
  module Models
80
91
  autoload :AlgorithmMetadata, "omnizip/models/algorithm_metadata"
@@ -95,56 +106,15 @@ module Omnizip
95
106
  end
96
107
  end
97
108
 
98
- # Algorithms (with registration - must be required explicitly)
99
- require_relative "omnizip/algorithms/ppmd_base"
100
- require_relative "omnizip/algorithms/lzma"
101
- require_relative "omnizip/algorithms/lzma2"
102
- require_relative "omnizip/algorithms/ppmd7"
103
- require_relative "omnizip/algorithms/ppmd8"
104
- require_relative "omnizip/algorithms/bzip2"
105
- require_relative "omnizip/algorithms/deflate"
106
- require_relative "omnizip/algorithms/deflate64"
107
- require_relative "omnizip/algorithms/zstandard"
108
-
109
- # Filters implementations (with registration)
110
- require_relative "omnizip/filters/bcj"
111
- require_relative "omnizip/filters/bcj_x86"
112
- require_relative "omnizip/filters/bcj2"
113
- require_relative "omnizip/filters/bcj_arm"
114
- require_relative "omnizip/filters/bcj_arm64"
115
- require_relative "omnizip/filters/bcj_ppc"
116
- require_relative "omnizip/filters/bcj_sparc"
117
- require_relative "omnizip/filters/bcj_ia64"
118
- require_relative "omnizip/filters/delta"
119
- require_relative "omnizip/filters/registry"
120
-
121
- # Checksum implementations (with registration)
122
- require_relative "omnizip/checksums/crc_base"
123
- require_relative "omnizip/checksums/crc32"
124
- require_relative "omnizip/checksums/crc64"
125
-
126
- # Archive formats registry (with autoload declarations for lazy loading)
127
- require_relative "omnizip/formats"
128
-
129
- # Archive formats (with registration - must be required explicitly)
130
- require_relative "omnizip/formats/seven_zip"
131
- require_relative "omnizip/formats/zip"
132
- require_relative "omnizip/formats/rar"
133
- require_relative "omnizip/formats/tar"
134
- require_relative "omnizip/formats/gzip"
135
- require_relative "omnizip/formats/bzip2_file"
136
- require_relative "omnizip/formats/xz"
137
-
138
- # Platform-specific features
139
109
  module Omnizip
140
110
  module Platform
141
111
  autoload :NtfsStreams, "omnizip/platform/ntfs_streams"
142
112
  end
143
113
 
144
- # Implementations module with autoloaded classes
145
114
  module Implementations
146
115
  autoload :SevenZip, "omnizip/implementations/seven_zip"
147
116
  autoload :XZUtils, "omnizip/implementations/xz_utils"
117
+
148
118
  module SevenZip
149
119
  module LZMA
150
120
  autoload :StateMachine,
@@ -172,7 +142,8 @@ module Omnizip
172
142
  end
173
143
  end
174
144
 
175
- # Load convenience module to extend Omnizip with utility methods
176
- require "omnizip/convenience"
177
-
178
- # Formats auto-register when their files are loaded (see individual format files)
145
+ # Trigger autoload of Convenience so its methods are available on the
146
+ # Omnizip module (it calls `extend Convenience` at the bottom of the
147
+ # file). Without this, Omnizip.compress_file and friends would not be
148
+ # defined until the first manual reference.
149
+ Omnizip::Convenience
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "open-uri"
5
+ require "digest/sha2"
6
+
7
+ namespace :test_files do
8
+ desc "Download Adobe font pack MSI for testing (60MB)"
9
+ task :adobe_msi do
10
+ url = "https://web.archive.org/web/20200816153035/http://ardownload.adobe.com/pub/adobe/reader/win/AcrobatDC/misc/FontPack1902120058_XtdAlf_Lang_DC.msi"
11
+ output_dir = "spec/fixtures/adobe"
12
+ output_path = "#{output_dir}/FontPack1902120058_XtdAlf_Lang_DC.msi"
13
+ expected_sha256 = "b5b9e15791a177715fa5e93ea458f8627cada7ac3218531461cfd35cecef6c24"
14
+
15
+ FileUtils.mkdir_p(output_dir)
16
+
17
+ if File.exist?(output_path)
18
+ # Verify existing file
19
+ actual_sha256 = Digest::SHA256.file(output_path).hexdigest
20
+ if actual_sha256 == expected_sha256
21
+ puts "Adobe font pack MSI already exists and verified: #{output_path}"
22
+ return
23
+ else
24
+ puts "Existing file has wrong SHA256, re-downloading..."
25
+ File.delete(output_path)
26
+ end
27
+ end
28
+
29
+ puts "Downloading Adobe font pack MSI (60MB)..."
30
+ puts "URL: #{url}"
31
+
32
+ begin
33
+ URI.open(url, "rb") do |remote|
34
+ File.binwrite(output_path, remote.read)
35
+ end
36
+ rescue OpenURI::HTTPError, Net::OpenTimeout, Errno::ECONNREFUSED => e
37
+ puts "Failed to download: #{e.message}"
38
+ puts "You may need to download manually from:"
39
+ puts " #{url}"
40
+ puts "And place it at: #{output_path}"
41
+ exit 1
42
+ end
43
+
44
+ # Verify SHA256
45
+ actual_sha256 = Digest::SHA256.file(output_path).hexdigest
46
+ if actual_sha256 == expected_sha256
47
+ puts "Downloaded and verified: #{output_path}"
48
+ puts "SHA256: #{actual_sha256}"
49
+ else
50
+ File.delete(output_path)
51
+ puts "SHA256 mismatch!"
52
+ puts "Expected: #{expected_sha256}"
53
+ puts "Got: #{actual_sha256}"
54
+ exit 1
55
+ end
56
+ end
57
+
58
+ desc "Clean up downloaded test files"
59
+ task :clean do
60
+ adobe_dir = "spec/fixtures/adobe"
61
+ if File.directory?(adobe_dir)
62
+ FileUtils.rm_rf(adobe_dir)
63
+ puts "Removed: #{adobe_dir}"
64
+ end
65
+ end
66
+ end