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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +65 -0
- data/lib/omnizip/algorithms/lzma/dictionary.rb +2 -2
- data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +7 -7
- data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +4 -4
- data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +7 -7
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +8 -8
- data/lib/omnizip/buffer/memory_archive.rb +55 -60
- data/lib/omnizip/buffer/memory_extractor.rb +25 -27
- data/lib/omnizip/buffer.rb +15 -8
- data/lib/omnizip/commands/metadata_command.rb +14 -6
- data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +2 -2
- data/lib/omnizip/formats/seven_zip/coder_chain.rb +5 -5
- data/lib/omnizip/formats/seven_zip/encrypted_header.rb +2 -2
- data/lib/omnizip/formats/seven_zip/file_collector.rb +1 -1
- data/lib/omnizip/formats/seven_zip/header.rb +4 -4
- data/lib/omnizip/formats/seven_zip/header_encryptor.rb +2 -2
- data/lib/omnizip/formats/seven_zip/parser.rb +8 -8
- data/lib/omnizip/formats/seven_zip/reader.rb +14 -19
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +5 -5
- data/lib/omnizip/formats/seven_zip/stream_compressor.rb +1 -1
- data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +5 -5
- data/lib/omnizip/formats/seven_zip/writer.rb +1 -1
- data/lib/omnizip/formats/zip/writer.rb +13 -6
- data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +5 -5
- data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +1 -1
- data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +1 -1
- data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +1 -1
- data/lib/omnizip/metadata/metadata_registry.rb +9 -2
- data/lib/omnizip/parallel/engine.rb +9 -5
- data/lib/omnizip/parallel/job_scheduler.rb +5 -1
- data/lib/omnizip/parallel/parallel_extractor.rb +62 -151
- data/lib/omnizip/parallel/thread_pool.rb +60 -0
- data/lib/omnizip/parallel/worker_pool.rb +5 -1
- data/lib/omnizip/parallel.rb +1 -0
- data/lib/omnizip/version.rb +1 -1
- data/lib/omnizip.rb +3 -0
- 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
|
-
|
|
89
|
-
puts "
|
|
90
|
-
puts "
|
|
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
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
227
|
-
#
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
@@ -55,10 +55,17 @@ module Omnizip
|
|
|
55
55
|
extra_field
|
|
56
56
|
])
|
|
57
57
|
|
|
58
|
-
# Register 7z format metadata support
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
41
|
-
|
|
40
|
+
begin
|
|
41
|
+
pool.submit_batch(work_items)
|
|
42
|
+
pool.run
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
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,
|