omnizip 0.3.53 → 0.3.55

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/lib/omnizip/algorithms/lzma/dictionary.rb +2 -2
  4. data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +7 -7
  5. data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +4 -4
  6. data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +7 -7
  7. data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +8 -8
  8. data/lib/omnizip/buffer/memory_archive.rb +55 -60
  9. data/lib/omnizip/buffer/memory_extractor.rb +25 -27
  10. data/lib/omnizip/buffer.rb +15 -8
  11. data/lib/omnizip/commands/metadata_command.rb +14 -6
  12. data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +2 -2
  13. data/lib/omnizip/formats/seven_zip/coder_chain.rb +5 -5
  14. data/lib/omnizip/formats/seven_zip/encrypted_header.rb +2 -2
  15. data/lib/omnizip/formats/seven_zip/file_collector.rb +1 -1
  16. data/lib/omnizip/formats/seven_zip/header.rb +4 -4
  17. data/lib/omnizip/formats/seven_zip/header_encryptor.rb +2 -2
  18. data/lib/omnizip/formats/seven_zip/parser.rb +8 -8
  19. data/lib/omnizip/formats/seven_zip/reader.rb +14 -19
  20. data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +5 -5
  21. data/lib/omnizip/formats/seven_zip/stream_compressor.rb +1 -1
  22. data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +5 -5
  23. data/lib/omnizip/formats/seven_zip/writer.rb +1 -1
  24. data/lib/omnizip/formats/zip/writer.rb +13 -6
  25. data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +5 -5
  26. data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +1 -1
  27. data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +1 -1
  28. data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +1 -1
  29. data/lib/omnizip/metadata/metadata_registry.rb +9 -2
  30. data/lib/omnizip/parallel/engine.rb +9 -5
  31. data/lib/omnizip/parallel/job_scheduler.rb +5 -1
  32. data/lib/omnizip/parallel/parallel_extractor.rb +62 -151
  33. data/lib/omnizip/parallel/thread_pool.rb +60 -0
  34. data/lib/omnizip/parallel/worker_pool.rb +5 -1
  35. data/lib/omnizip/parallel.rb +1 -0
  36. data/lib/omnizip/version.rb +1 -1
  37. data/lib/omnizip.rb +3 -0
  38. metadata +2 -1
@@ -84,16 +84,24 @@ module Omnizip
84
84
  end
85
85
  end
86
86
 
87
+ # The display is driven by the registry's field contract for
88
+ # :seven_zip — optional lines print only for registered fields.
87
89
  def show_seven_zip_entry(entry)
88
- puts "Entry: #{entry[:name]}"
89
- puts " Type: #{entry[:directory] ? 'Directory' : 'File'}"
90
- puts " Size: #{format_size(entry[:size])}"
90
+ registry = Omnizip::Metadata::MetadataRegistry
91
+ puts "Entry: #{entry[:name]}" if registry.supports?(:seven_zip, :name)
92
+ puts " Type: #{entry[:directory] ? 'Directory' : 'File'}" if registry.supports?(:seven_zip, :directory)
93
+ puts " Size: #{format_size(entry[:size])}" if registry.supports?(:seven_zip, :size)
91
94
  # Per-entry packed sizes are not tracked by the parser
92
- if entry[:compressed_size]&.positive?
95
+ if registry.supports?(:seven_zip, :compressed_size) &&
96
+ entry[:compressed_size]&.positive?
93
97
  puts " Compressed: #{format_size(entry[:compressed_size])}"
94
98
  end
95
- puts " Modified: #{entry[:mtime]}" if entry[:mtime]
96
- puts " CRC32: 0x#{entry[:crc].to_s(16).upcase}" if entry[:crc]
99
+ if registry.supports?(:seven_zip, :mtime) && entry[:mtime]
100
+ puts " Modified: #{entry[:mtime]}"
101
+ end
102
+ if registry.supports?(:seven_zip, :crc) && entry[:crc]
103
+ puts " CRC32: 0x#{entry[:crc].to_s(16).upcase}"
104
+ end
97
105
  end
98
106
 
99
107
  def show_metadata(archive, pattern)
@@ -46,7 +46,7 @@ module Omnizip
46
46
  bcj2_coder = @folder.coders.find { |c| c.method_id == FilterId::BCJ2 }
47
47
  compression_coder = find_compression_coder
48
48
 
49
- raise "BCJ2 coder not found" unless bcj2_coder
49
+ raise Omnizip::InvalidArchiveError, "BCJ2 coder not found" unless bcj2_coder
50
50
 
51
51
  # Determine stream layout based on folder structure
52
52
  # BCJ2 has 4 input streams: main, call, jump, rc
@@ -217,7 +217,7 @@ module Omnizip
217
217
  return packed_data unless algo_sym
218
218
 
219
219
  algo_class = Omnizip::AlgorithmRegistry.get(algo_sym)
220
- raise "Algorithm not found: #{algo_sym}" unless algo_class
220
+ raise Omnizip::AlgorithmNotFoundError, "Algorithm not found: #{algo_sym}" unless algo_class
221
221
 
222
222
  # Build decoder options
223
223
  options = build_decoder_options(coder, algo_sym)
@@ -20,7 +20,7 @@ module Omnizip
20
20
  # Find the compression method (not a filter) among coders
21
21
  # Filters like BCJ, BCJ2 have specific method IDs
22
22
  main_coder = find_compression_coder(folder.coders)
23
- raise "No compression method found in folder" unless main_coder
23
+ raise Omnizip::InvalidArchiveError, "No compression method found in folder" unless main_coder
24
24
 
25
25
  algorithm = algorithm_for_method(main_coder.method_id)
26
26
 
@@ -82,8 +82,8 @@ module Omnizip
82
82
  when MethodId::DEFLATE64
83
83
  :deflate64
84
84
  else
85
- raise "Unsupported compression method: " \
86
- "0x#{method_id.to_s(16)}"
85
+ raise Omnizip::UnsupportedFormatError, "Unsupported compression method: " \
86
+ "0x#{method_id.to_s(16)}"
87
87
  end
88
88
  end
89
89
 
@@ -126,7 +126,7 @@ module Omnizip
126
126
  # Get algorithm
127
127
  algo_class = AlgorithmRegistry.get(chain_config[:algorithm])
128
128
  unless algo_class
129
- raise "Algorithm not found: #{chain_config[:algorithm]}"
129
+ raise Omnizip::AlgorithmNotFoundError, "Algorithm not found: #{chain_config[:algorithm]}"
130
130
  end
131
131
 
132
132
  # Create algorithm instance
@@ -143,7 +143,7 @@ module Omnizip
143
143
  pipeline = FilterPipeline.new
144
144
  chain_config[:filters].each do |filter_sym|
145
145
  filter_class = FilterRegistry.get(filter_sym)
146
- raise "Filter not found: #{filter_sym}" unless filter_class
146
+ raise Omnizip::UnknownFilterError, "Filter not found: #{filter_sym}" unless filter_class
147
147
 
148
148
  pipeline.add_filter(filter_class.new)
149
149
  end
@@ -69,7 +69,7 @@ module Omnizip
69
69
  # @return [EncryptedHeader] Parsed header
70
70
  # @raise [RuntimeError] if data is invalid
71
71
  def self.from_binary(data)
72
- raise "Invalid encrypted header: too short" if data.bytesize < 54
72
+ raise Omnizip::InvalidArchiveError, "Invalid encrypted header: too short" if data.bytesize < 54
73
73
 
74
74
  pos = 0
75
75
 
@@ -77,7 +77,7 @@ module Omnizip
77
77
  marker = data.getbyte(pos)
78
78
  pos += 1
79
79
  unless marker == PropertyId::ENCODED_HEADER
80
- raise "Invalid encrypted header marker: expected #{PropertyId::ENCODED_HEADER}, got #{marker}"
80
+ raise Omnizip::InvalidArchiveError, "Invalid encrypted header marker: expected #{PropertyId::ENCODED_HEADER}, got #{marker}"
81
81
  end
82
82
 
83
83
  # Read encrypted data size
@@ -23,7 +23,7 @@ module Omnizip
23
23
  # @param recursive [Boolean] Recursively add directories
24
24
  def add_path(path, archive_path: nil, recursive: true)
25
25
  path = File.expand_path(path)
26
- raise "Path not found: #{path}" unless File.exist?(path)
26
+ raise Errno::ENOENT, "Path not found: #{path}" unless File.exist?(path)
27
27
 
28
28
  @base_path ||= File.dirname(path)
29
29
 
@@ -29,14 +29,14 @@ module Omnizip
29
29
  def parse(io)
30
30
  # Read complete start header (32 bytes)
31
31
  header_data = io.read(START_HEADER_SIZE)
32
- raise "Invalid .7z file: too short" if header_data.nil? ||
32
+ raise Omnizip::InvalidArchiveError, "Invalid .7z file: too short" if header_data.nil? ||
33
33
  header_data.bytesize < START_HEADER_SIZE
34
34
 
35
35
  # Validate signature
36
36
  signature = header_data[0, SIGNATURE_SIZE]
37
37
  unless signature == SIGNATURE
38
- raise "Invalid .7z signature: expected #{SIGNATURE.inspect}, " \
39
- "got #{signature.inspect}"
38
+ raise Omnizip::InvalidArchiveError, "Invalid .7z signature: expected #{SIGNATURE.inspect}, " \
39
+ "got #{signature.inspect}"
40
40
  end
41
41
 
42
42
  # Parse version
@@ -44,7 +44,7 @@ module Omnizip
44
44
  @minor_version = header_data.getbyte(7)
45
45
 
46
46
  unless @major_version == MAJOR_VERSION
47
- raise "Unsupported .7z version: #{@major_version}.#{@minor_version}"
47
+ raise Omnizip::UnsupportedFormatError, "Unsupported .7z version: #{@major_version}.#{@minor_version}"
48
48
  end
49
49
 
50
50
  # Parse start header CRC (bytes 8-11)
@@ -83,9 +83,9 @@ module Omnizip
83
83
  begin
84
84
  decipher.update(encrypted_data) + decipher.final
85
85
  rescue OpenSSL::Cipher::CipherError => e
86
- raise "Failed to decrypt header: incorrect password or corrupted data (#{e.message})"
86
+ raise Omnizip::PasswordError, "Failed to decrypt header: incorrect password or corrupted data (#{e.message})"
87
87
  rescue StandardError => e
88
- raise "Failed to decrypt header: incorrect password or corrupted data (#{e.message})"
88
+ raise Omnizip::PasswordError, "Failed to decrypt header: incorrect password or corrupted data (#{e.message})"
89
89
  end
90
90
  end
91
91
 
@@ -82,7 +82,7 @@ module Omnizip
82
82
  end
83
83
 
84
84
  value = read_number
85
- raise "Unsupported 32-bit value" if value >= 0x80000000
85
+ raise Omnizip::InvalidArchiveError, "Unsupported 32-bit value" if value >= 0x80000000
86
86
 
87
87
  value
88
88
  end
@@ -233,7 +233,7 @@ module Omnizip
233
233
  end
234
234
 
235
235
  # Verify required SIZE property was present
236
- raise "Missing SIZE property in pack info" unless sizes_read
236
+ raise Omnizip::InvalidArchiveError, "Missing SIZE property in pack info" unless sizes_read
237
237
 
238
238
  # K_END is optional in some 7-Zip versions
239
239
  read_byte if !eof? && peek_byte == PropertyId::K_END
@@ -259,7 +259,7 @@ module Omnizip
259
259
  end
260
260
  else
261
261
  # External folders not supported
262
- raise "External folders not supported"
262
+ raise Omnizip::UnsupportedFormatError, "External folders not supported"
263
263
  end
264
264
  end
265
265
 
@@ -268,7 +268,7 @@ module Omnizip
268
268
  # @param folder [Models::Folder] Folder to populate
269
269
  def read_folder(folder)
270
270
  num_coders = read_number
271
- raise "Too many coders" if num_coders > Constants::MAX_NUM_CODERS
271
+ raise Omnizip::InvalidArchiveError, "Too many coders" if num_coders > Constants::MAX_NUM_CODERS
272
272
 
273
273
  num_in_streams = 0
274
274
  num_out_streams = 0
@@ -386,8 +386,8 @@ module Omnizip
386
386
  end
387
387
 
388
388
  # Verify required properties were present
389
- raise "Missing FOLDER property in unpack info" unless folders_read
390
- raise "Missing CODERS_UNPACK_SIZE property in unpack info" unless unpack_sizes_read
389
+ raise Omnizip::InvalidArchiveError, "Missing FOLDER property in unpack info" unless folders_read
390
+ raise Omnizip::InvalidArchiveError, "Missing CODERS_UNPACK_SIZE property in unpack info" unless unpack_sizes_read
391
391
 
392
392
  # K_END is optional in some 7-Zip versions
393
393
  read_byte if !eof? && peek_byte == PropertyId::K_END
@@ -611,8 +611,8 @@ module Omnizip
611
611
  actual = read_byte
612
612
  return if actual == expected
613
613
 
614
- raise "Expected property 0x#{expected.to_s(16)}, " \
615
- "got 0x#{actual.to_s(16)}"
614
+ raise Omnizip::InvalidArchiveError, "Expected property 0x#{expected.to_s(16)}, " \
615
+ "got 0x#{actual.to_s(16)}"
616
616
  end
617
617
 
618
618
  # Skip size field
@@ -223,18 +223,13 @@ module Omnizip
223
223
  EncryptedHeader.from_binary(next_header_data)
224
224
  # If we got here, it's encrypted - try to decrypt
225
225
  next_header_data = decrypt_header(next_header_data)
226
- rescue RuntimeError => e
227
- # Re-raise password-related errors
228
- if e.message.include?("Password required") || e.message.include?("incorrect password")
229
- raise
230
- end
231
-
232
- # Not encrypted - it's a compressed header
233
- # Decompress it using the encoded stream info
234
- next_header_data = decompress_encoded_header(io,
235
- next_header_data)
226
+ rescue Omnizip::PasswordError
227
+ # Decryption refused or failed — a password problem,
228
+ # never a compressed header
229
+ raise
236
230
  rescue StandardError
237
- # Parsing error - not an encrypted header, try decompression
231
+ # Not an encrypted header — it's a compressed header;
232
+ # decompress it using the encoded stream info
238
233
  next_header_data = decompress_encoded_header(io,
239
234
  next_header_data)
240
235
  end
@@ -250,8 +245,8 @@ module Omnizip
250
245
  @stream_info, @entries = parse_metadata(parser)
251
246
  rescue StandardError => e
252
247
  if @headers_decrypted
253
- raise "Failed to decrypt headers: incorrect password " \
254
- "or corrupted data (#{e.message})"
248
+ raise Omnizip::PasswordError, "Failed to decrypt headers: incorrect password " \
249
+ "or corrupted data (#{e.message})"
255
250
  end
256
251
 
257
252
  raise
@@ -271,7 +266,7 @@ module Omnizip
271
266
  @encrypted_header = EncryptedHeader.from_binary(encrypted_data)
272
267
 
273
268
  unless @password
274
- raise "Archive headers are encrypted. Password required to access."
269
+ raise Omnizip::PasswordError, "Archive headers are encrypted. Password required to access."
275
270
  end
276
271
 
277
272
  # Decrypt using password
@@ -287,7 +282,7 @@ module Omnizip
287
282
  @headers_decrypted = true
288
283
  decrypted
289
284
  rescue OpenSSL::Cipher::CipherError => e
290
- raise "Failed to decrypt headers: incorrect password (#{e.message})"
285
+ raise Omnizip::PasswordError, "Failed to decrypt headers: incorrect password (#{e.message})"
291
286
  end
292
287
 
293
288
  # Decompress encoded (compressed) header
@@ -317,7 +312,7 @@ module Omnizip
317
312
  parser.read_unpack_info(stream_info)
318
313
  end
319
314
  else
320
- raise "Unexpected property in encoded header: 0x#{type.to_s(16)}"
315
+ raise Omnizip::InvalidArchiveError, "Unexpected property in encoded header: 0x#{type.to_s(16)}"
321
316
  end
322
317
 
323
318
  # Decompress the header using the stream info
@@ -341,7 +336,7 @@ module Omnizip
341
336
 
342
337
  # Read main header
343
338
  type = parser.read_byte
344
- raise "Expected Header, got 0x#{type.to_s(16)}" unless
339
+ raise Omnizip::InvalidArchiveError, "Expected Header, got 0x#{type.to_s(16)}" unless
345
340
  type == PropertyId::HEADER
346
341
 
347
342
  # Parse header sections
@@ -529,7 +524,7 @@ module Omnizip
529
524
  crc = Omnizip::Checksums::Crc32.new
530
525
  crc.update(data)
531
526
  unless crc.value == entry.crc
532
- raise "CRC mismatch for #{entry.name}: expected 0x#{entry.crc.to_s(16)}, got 0x#{crc.value.to_s(16)}"
527
+ raise Omnizip::ChecksumError, "CRC mismatch for #{entry.name}: expected 0x#{entry.crc.to_s(16)}, got 0x#{crc.value.to_s(16)}"
533
528
  end
534
529
  end
535
530
 
@@ -589,7 +584,7 @@ module Omnizip
589
584
  crc = Omnizip::Checksums::Crc32.new
590
585
  crc.update(data)
591
586
  unless crc.value == entry.crc
592
- raise "CRC mismatch for #{entry.name}: expected 0x#{entry.crc.to_s(16)}, got 0x#{crc.value.to_s(16)}"
587
+ raise Omnizip::ChecksumError, "CRC mismatch for #{entry.name}: expected 0x#{entry.crc.to_s(16)}, got 0x#{crc.value.to_s(16)}"
593
588
  end
594
589
  end
595
590
 
@@ -167,7 +167,7 @@ module Omnizip
167
167
  volume_num += 1
168
168
  end
169
169
 
170
- raise "No volumes found for #{@base_path}" if @volumes.empty?
170
+ raise Errno::ENOENT, "No volumes found for #{@base_path}" if @volumes.empty?
171
171
  end
172
172
 
173
173
  # Detect volumes with alpha naming (.aa, .ab, ...)
@@ -185,7 +185,7 @@ module Omnizip
185
185
  volume_num += 1
186
186
  end
187
187
 
188
- raise "No volumes found for #{@base_path}" if @volumes.empty?
188
+ raise Errno::ENOENT, "No volumes found for #{@base_path}" if @volumes.empty?
189
189
  end
190
190
 
191
191
  # Open all volume files
@@ -293,7 +293,7 @@ module Omnizip
293
293
 
294
294
  # Read main header
295
295
  type = parser.read_byte
296
- raise "Expected Header, got 0x#{type.to_s(16)}" unless
296
+ raise Omnizip::InvalidArchiveError, "Expected Header, got 0x#{type.to_s(16)}" unless
297
297
  type == PropertyId::HEADER
298
298
 
299
299
  # Parse header sections
@@ -372,7 +372,7 @@ module Omnizip
372
372
  parser.read_unpack_info(stream_info)
373
373
  end
374
374
  else
375
- raise "Unexpected property in encoded header: 0x#{type.to_s(16)}"
375
+ raise Omnizip::InvalidArchiveError, "Unexpected property in encoded header: 0x#{type.to_s(16)}"
376
376
  end
377
377
 
378
378
  # Decompress the header using the stream info
@@ -491,7 +491,7 @@ module Omnizip
491
491
  crc = Omnizip::Checksums::Crc32.new
492
492
  crc.update(data)
493
493
  unless crc.value == entry.crc
494
- raise "CRC mismatch for #{entry.name}: expected 0x#{entry.crc.to_s(16)}, got 0x#{crc.value.to_s(16)}"
494
+ raise Omnizip::ChecksumError, "CRC mismatch for #{entry.name}: expected 0x#{entry.crc.to_s(16)}, got 0x#{crc.value.to_s(16)}"
495
495
  end
496
496
  end
497
497
 
@@ -44,7 +44,7 @@ module Omnizip
44
44
  # Apply compression algorithm
45
45
  if @algorithm && @algorithm != :copy
46
46
  algo_class = Omnizip::AlgorithmRegistry.get(@algorithm)
47
- raise "Algorithm not found: #{@algorithm}" unless algo_class
47
+ raise Omnizip::AlgorithmNotFoundError, "Algorithm not found: #{@algorithm}" unless algo_class
48
48
 
49
49
  encoder = algo_class.new
50
50
  input_io = StringIO.new(result)
@@ -57,7 +57,7 @@ module Omnizip
57
57
 
58
58
  # Get algorithm class
59
59
  algo_class = Omnizip::AlgorithmRegistry.get(algo_sym)
60
- raise "Algorithm not found: #{algo_sym}" unless algo_class
60
+ raise Omnizip::AlgorithmNotFoundError, "Algorithm not found: #{algo_sym}" unless algo_class
61
61
 
62
62
  # Decompress
63
63
  input_io = StringIO.new(packed_data)
@@ -80,8 +80,8 @@ module Omnizip
80
80
 
81
81
  # BCJ2 requires special handling with multiple streams
82
82
  if filter_sym == :bcj2
83
- raise "BCJ2 archives require multi-stream decompression which is not yet implemented. " \
84
- "Please use the 7z command-line tool for this archive."
83
+ raise Omnizip::UnsupportedFormatError, "BCJ2 archives require multi-stream decompression which is not yet implemented. " \
84
+ "Please use the 7z command-line tool for this archive."
85
85
  end
86
86
 
87
87
  filter = filter_class.new
@@ -109,8 +109,8 @@ module Omnizip
109
109
  actual_crc = crc.value
110
110
 
111
111
  unless actual_crc == expected_crc
112
- raise "CRC mismatch: expected 0x#{expected_crc.to_s(16)}, " \
113
- "got 0x#{actual_crc.to_s(16)}"
112
+ raise Omnizip::ChecksumError, "CRC mismatch: expected 0x#{expected_crc.to_s(16)}, " \
113
+ "got 0x#{actual_crc.to_s(16)}"
114
114
  end
115
115
  end
116
116
 
@@ -299,7 +299,7 @@ module Omnizip
299
299
  # @return [String] Encrypted header with metadata
300
300
  def encrypt_header(header_data)
301
301
  unless @options[:password]
302
- raise "Password required for header encryption"
302
+ raise Omnizip::PasswordError, "Password required for header encryption"
303
303
  end
304
304
 
305
305
  encryptor = HeaderEncryptor.new(@options[:password])
@@ -92,14 +92,17 @@ module Omnizip
92
92
  @entries << entry
93
93
  end
94
94
 
95
- # Add data directly to the archive
96
- def add_data(archive_path, data, stat = nil)
95
+ # Add data directly to the archive. +compression_method+
96
+ # (a ZIP method code) overrides the archive-wide default for
97
+ # this entry alone.
98
+ def add_data(archive_path, data, stat = nil, compression_method: nil)
97
99
  entry = create_entry(
98
100
  filename: archive_path,
99
101
  uncompressed_data: data,
100
102
  stat: stat,
101
103
  )
102
104
 
105
+ entry[:compression_method] = compression_method
103
106
  @entries << entry
104
107
  end
105
108
 
@@ -188,13 +191,17 @@ module Omnizip
188
191
  def write_to_io(io, compression_method: COMPRESSION_DEFLATE, level: 6)
189
192
  local_header_offsets = []
190
193
 
191
- # Write local file headers and data
194
+ # Write local file headers and data. An entry-level
195
+ # :compression_method overrides the archive-wide default
196
+ # (used by the buffer layer's per-entry :store option).
192
197
  entries.each do |entry|
193
198
  offset = io.pos
194
199
  local_header_offsets << offset
195
200
 
201
+ entry_method = entry[:compression_method] || compression_method
202
+
196
203
  # Create local file header
197
- local_header = create_local_header(entry, compression_method)
204
+ local_header = create_local_header(entry, entry_method)
198
205
 
199
206
  # Compress data if not a directory
200
207
  if entry[:directory]
@@ -205,7 +212,7 @@ module Omnizip
205
212
  else
206
213
  compressed_data = compress_data(
207
214
  entry[:uncompressed_data],
208
- compression_method,
215
+ entry_method,
209
216
  level,
210
217
  )
211
218
  entry[:compressed_size] = compressed_data.bytesize
@@ -232,7 +239,7 @@ module Omnizip
232
239
  entries.each_with_index do |entry, index|
233
240
  central_header = create_central_header(
234
241
  entry,
235
- compression_method,
242
+ entry[:compression_method] || compression_method,
236
243
  local_header_offsets[index],
237
244
  )
238
245
  io.write(central_header.to_binary)
@@ -136,19 +136,19 @@ module Omnizip
136
136
  # Parse LZMA header
137
137
  def parse_header
138
138
  props = @input.getbyte
139
- raise "Invalid LZMA header: missing properties" unless props
139
+ raise Omnizip::DecompressionError, "Invalid LZMA header: missing properties" unless props
140
140
 
141
141
  @lc = props % 9
142
142
  rem = props / 9
143
143
  @lp = rem % 5
144
144
  @pb = rem / 5
145
145
 
146
- raise "Invalid LZMA properties: pb=#{@pb} > 4" if @pb > 4
146
+ raise Omnizip::DecompressionError, "Invalid LZMA properties: pb=#{@pb} > 4" if @pb > 4
147
147
 
148
148
  @dict_size = 0
149
149
  4.times do |i|
150
150
  byte = @input.getbyte
151
- raise "Invalid LZMA header: missing dictionary size" unless byte
151
+ raise Omnizip::DecompressionError, "Invalid LZMA header: missing dictionary size" unless byte
152
152
 
153
153
  @dict_size |= (byte << (i * 8))
154
154
  end
@@ -159,7 +159,7 @@ module Omnizip
159
159
  @uncompressed_size = 0
160
160
  8.times do |i|
161
161
  byte = @input.getbyte
162
- raise "Invalid LZMA header: missing uncompressed size" unless byte
162
+ raise Omnizip::DecompressionError, "Invalid LZMA header: missing uncompressed size" unless byte
163
163
 
164
164
  @uncompressed_size |= (byte * (1 << (i * 8)))
165
165
  end
@@ -328,7 +328,7 @@ module Omnizip
328
328
  # Decoder returns distance before +1, so add 1 to get actual distance
329
329
  distance += 1
330
330
 
331
- raise "Invalid distance: #{distance}" if distance >= @dict_size && @dict_full
331
+ raise Omnizip::DecompressionError, "Invalid distance: #{distance}" if distance >= @dict_size && @dict_full
332
332
 
333
333
  # Update reps
334
334
  @reps[3] = @reps[2]
@@ -309,7 +309,7 @@ module Omnizip
309
309
  # @return [void]
310
310
  def encode_match(match, data)
311
311
  # Defensive check: distance must be >= 1
312
- raise "Invalid match distance: #{match.distance}" if match.distance < 1
312
+ raise Omnizip::CompressionError, "Invalid match distance: #{match.distance}" if match.distance < 1
313
313
 
314
314
  pos_state = @pos & ((1 << @pb) - 1)
315
315
 
@@ -158,7 +158,7 @@ module Omnizip
158
158
  def init_decoder
159
159
  # Read first byte (should be 0 for valid LZMA stream)
160
160
  first = read_byte
161
- raise "Invalid LZMA stream: first byte not 0" unless first.zero?
161
+ raise Omnizip::DecompressionError, "Invalid LZMA stream: first byte not 0" unless first.zero?
162
162
 
163
163
  # Read 4 bytes for initial code value
164
164
  @code = 0
@@ -108,7 +108,7 @@ module Omnizip
108
108
  when 9
109
109
  CATEGORY_SHORT_REP
110
110
  else
111
- raise "Invalid state: #{@index}"
111
+ raise Omnizip::CompressionError, "Invalid state: #{@index}"
112
112
  end
113
113
  end
114
114
 
@@ -55,10 +55,17 @@ module Omnizip
55
55
  extra_field
56
56
  ])
57
57
 
58
- # Register 7z format metadata support (limited)
58
+ # Register 7z format metadata support: the readable fields the
59
+ # handler detail contract carries (name/size/directory always
60
+ # present; compressed_size when tracked, mtime/crc when stored).
61
+ # Editing is ZIP-only — the metadata command raises truthfully.
59
62
  MetadataRegistry.register(:seven_zip, %i[
63
+ name
64
+ size
65
+ directory
66
+ compressed_size
60
67
  mtime
61
- attributes
68
+ crc
62
69
  ])
63
70
  end
64
71
  end
@@ -31,17 +31,21 @@ module Omnizip
31
31
  # @return [Array<Fractor::WorkResult>] the failed results, for
32
32
  # the caller to decide how to surface.
33
33
  def run(work_items)
34
- pool = Fractor::WorkerPool.new(
34
+ pool = WorkerPool.new(
35
35
  worker_class: @worker_class,
36
36
  num_workers: @threads,
37
37
  continuous: false,
38
38
  )
39
39
  pool.start
40
- pool.submit_batch(work_items)
41
- pool.run
40
+ begin
41
+ pool.submit_batch(work_items)
42
+ pool.run
42
43
 
43
- pool.successful_results.each { |r| yield r if block_given? }
44
- pool.failed_results
44
+ pool.successful_results.each { |r| yield r if block_given? }
45
+ pool.failed_results
46
+ ensure
47
+ pool.shutdown
48
+ end
45
49
  end
46
50
  end
47
51
  end
@@ -38,6 +38,10 @@ module Omnizip
38
38
  schedule_dynamic(jobs, worker_count)
39
39
  when :static
40
40
  schedule_static(jobs, worker_count)
41
+ when :round_robin
42
+ schedule_round_robin(jobs, worker_count)
43
+ when :bin_packing
44
+ schedule_bin_packing(jobs, worker_count)
41
45
  end
42
46
  end
43
47
 
@@ -99,7 +103,7 @@ bytes_per_second: 10_000_000)
99
103
  #
100
104
  # @raise [ArgumentError] if strategy is invalid
101
105
  def validate_strategy!
102
- valid_strategies = %i[dynamic static]
106
+ valid_strategies = %i[dynamic static round_robin bin_packing]
103
107
  return if valid_strategies.include?(@strategy)
104
108
 
105
109
  raise ArgumentError,