omnizip 0.3.9 → 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.
- checksums.yaml +4 -4
- data/TODO.refactor/00-overview.md +67 -0
- data/TODO.refactor/01-registry-base-class.md +81 -0
- data/TODO.refactor/02-error-hierarchy.md +57 -0
- data/TODO.refactor/03-autoload-entry-point.md +69 -0
- data/TODO.refactor/04-filter-base-consolidation.md +49 -0
- data/TODO.refactor/05-send-private-methods.md +64 -0
- data/TODO.refactor/06-instance-variable-access.md +69 -0
- data/TODO.refactor/07-respond-to-replacement.md +91 -0
- data/TODO.refactor/08-spec-doubles.md +27 -0
- data/TODO.refactor/09-cli-shared-module.md +35 -0
- data/TODO.refactor/10-format-detector-ocp.md +34 -0
- data/TODO.refactor/11-convenience-decoupling.md +38 -0
- data/TODO.refactor/12-thread-safety.md +8 -0
- data/TODO.refactor/13-lutaml-model-migration.md +82 -0
- data/TODO.refactor/14-add-missing-specs.md +25 -0
- data/lib/omnizip/algorithm.rb +0 -2
- data/lib/omnizip/algorithm_registry.rb +29 -57
- data/lib/omnizip/algorithms/lzma/dictionary.rb +4 -3
- data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +2 -0
- data/lib/omnizip/algorithms/lzma/range_decoder.rb +1 -1
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +6 -5
- data/lib/omnizip/algorithms/lzma2/encoder.rb +0 -2
- data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +0 -2
- data/lib/omnizip/algorithms/lzma2/properties.rb +0 -2
- data/lib/omnizip/algorithms/ppmd7/model.rb +1 -1
- data/lib/omnizip/algorithms/ppmd8/context.rb +0 -2
- data/lib/omnizip/algorithms/ppmd8/model.rb +1 -1
- data/lib/omnizip/algorithms/ppmd8.rb +0 -2
- data/lib/omnizip/algorithms/ppmd_base.rb +0 -2
- data/lib/omnizip/algorithms/sevenzip_lzma2.rb +0 -5
- data/lib/omnizip/algorithms/xz_lzma2.rb +0 -5
- data/lib/omnizip/algorithms/zstandard/frame/header.rb +3 -5
- data/lib/omnizip/algorithms.rb +1 -0
- data/lib/omnizip/archive_handler.rb +75 -0
- data/lib/omnizip/archive_handlers/tar_handler.rb +51 -0
- data/lib/omnizip/archive_handlers/zip_handler.rb +73 -0
- data/lib/omnizip/archive_handlers.rb +11 -0
- data/lib/omnizip/checksum_registry.rb +23 -96
- data/lib/omnizip/cli/shared.rb +31 -0
- data/lib/omnizip/cli.rb +6 -43
- data/lib/omnizip/commands/archive_create_command.rb +0 -6
- data/lib/omnizip/commands/archive_extract_command.rb +0 -4
- data/lib/omnizip/commands/archive_list_command.rb +0 -4
- data/lib/omnizip/commands/archive_repair_command.rb +0 -2
- data/lib/omnizip/commands/archive_verify_command.rb +0 -2
- data/lib/omnizip/commands/compress_command.rb +0 -2
- data/lib/omnizip/commands/decompress_command.rb +0 -2
- data/lib/omnizip/commands/list_command.rb +0 -2
- data/lib/omnizip/commands/metadata_command.rb +0 -5
- data/lib/omnizip/commands/parity_create_command.rb +0 -2
- data/lib/omnizip/commands/parity_repair_command.rb +0 -2
- data/lib/omnizip/commands/parity_verify_command.rb +0 -2
- data/lib/omnizip/commands/profile_list_command.rb +0 -2
- data/lib/omnizip/commands/profile_show_command.rb +0 -2
- data/lib/omnizip/convenience.rb +111 -234
- data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +1 -1
- data/lib/omnizip/crypto/aes256/cipher.rb +6 -1
- data/lib/omnizip/error.rb +22 -18
- data/lib/omnizip/filter.rb +5 -3
- data/lib/omnizip/filter_registry.rb +74 -98
- data/lib/omnizip/filters/bcj2.rb +0 -2
- data/lib/omnizip/filters/bcj_x86.rb +0 -2
- data/lib/omnizip/filters/filter_base.rb +4 -70
- data/lib/omnizip/filters/registry.rb +0 -2
- data/lib/omnizip/format_detector.rb +21 -12
- data/lib/omnizip/format_registry.rb +69 -53
- data/lib/omnizip/formats/bzip2_file.rb +0 -2
- data/lib/omnizip/formats/cpio/entry.rb +0 -2
- data/lib/omnizip/formats/cpio/reader.rb +0 -1
- data/lib/omnizip/formats/cpio/writer.rb +0 -1
- data/lib/omnizip/formats/cpio.rb +0 -1
- data/lib/omnizip/formats/format_spec_loader.rb +1 -1
- data/lib/omnizip/formats/gzip.rb +0 -2
- data/lib/omnizip/formats/iso/directory_record.rb +6 -3
- data/lib/omnizip/formats/iso/reader.rb +2 -2
- data/lib/omnizip/formats/iso.rb +0 -1
- data/lib/omnizip/formats/lzip.rb +5 -25
- data/lib/omnizip/formats/lzma_alone.rb +7 -25
- data/lib/omnizip/formats/msi/reader.rb +1 -0
- data/lib/omnizip/formats/msi/string_pool.rb +2 -3
- data/lib/omnizip/formats/msi.rb +1 -4
- data/lib/omnizip/formats/ole/allocation_table.rb +0 -1
- data/lib/omnizip/formats/ole/dirent.rb +9 -4
- data/lib/omnizip/formats/ole/header.rb +0 -1
- data/lib/omnizip/formats/ole/ranges_io.rb +1 -1
- data/lib/omnizip/formats/ole/storage.rb +5 -6
- data/lib/omnizip/formats/ole.rb +0 -1
- data/lib/omnizip/formats/rar/block_parser.rb +0 -1
- data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +1 -1
- data/lib/omnizip/formats/rar/decompressor.rb +1 -0
- data/lib/omnizip/formats/rar/header.rb +0 -1
- data/lib/omnizip/formats/rar/rar5/writer.rb +0 -3
- data/lib/omnizip/formats/rar/reader.rb +2 -3
- data/lib/omnizip/formats/rar/writer.rb +0 -1
- data/lib/omnizip/formats/rar.rb +0 -1
- data/lib/omnizip/formats/rpm/entry.rb +0 -1
- data/lib/omnizip/formats/rpm/header.rb +1 -6
- data/lib/omnizip/formats/rpm/lead.rb +37 -26
- data/lib/omnizip/formats/rpm/tag.rb +0 -1
- data/lib/omnizip/formats/rpm/writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +0 -1
- data/lib/omnizip/formats/seven_zip/coder_chain.rb +1 -2
- data/lib/omnizip/formats/seven_zip/encoded_header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/encrypted_header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header_encryptor.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header_writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +1 -1
- data/lib/omnizip/formats/seven_zip/parser.rb +1 -2
- data/lib/omnizip/formats/seven_zip/reader.rb +13 -2
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +0 -1
- data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/writer.rb +4 -6
- data/lib/omnizip/formats/seven_zip.rb +2 -4
- data/lib/omnizip/formats/tar/entry.rb +0 -2
- data/lib/omnizip/formats/tar/header.rb +0 -2
- data/lib/omnizip/formats/tar/reader.rb +0 -2
- data/lib/omnizip/formats/tar/writer.rb +0 -2
- data/lib/omnizip/formats/tar.rb +0 -1
- data/lib/omnizip/formats/xar/entry.rb +0 -1
- data/lib/omnizip/formats/xar/header.rb +0 -1
- data/lib/omnizip/formats/xar/reader.rb +0 -1
- data/lib/omnizip/formats/xar/toc.rb +1 -2
- data/lib/omnizip/formats/xar/writer.rb +11 -3
- data/lib/omnizip/formats/xar.rb +0 -1
- data/lib/omnizip/formats/xz/reader.rb +7 -3
- data/lib/omnizip/formats/xz.rb +3 -40
- data/lib/omnizip/formats/xz_impl/block_decoder.rb +4 -10
- data/lib/omnizip/formats/xz_impl/block_encoder.rb +0 -2
- data/lib/omnizip/formats/xz_impl/block_header_parser.rb +0 -2
- data/lib/omnizip/formats/xz_impl/index_decoder.rb +0 -1
- data/lib/omnizip/formats/xz_impl/index_encoder.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_encoder.rb +1 -1
- data/lib/omnizip/formats/xz_impl/stream_footer.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_header.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +0 -2
- data/lib/omnizip/formats/zip/central_directory_header.rb +2 -3
- data/lib/omnizip/formats/zip/end_of_central_directory.rb +0 -2
- data/lib/omnizip/formats/zip/local_file_header.rb +0 -2
- data/lib/omnizip/formats/zip/reader.rb +12 -2
- data/lib/omnizip/formats/zip/writer.rb +73 -1
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +0 -2
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +0 -2
- data/lib/omnizip/formats/zip/zip64_extra_field.rb +0 -2
- data/lib/omnizip/formats/zip.rb +0 -1
- data/lib/omnizip/formats.rb +8 -24
- data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +2 -2
- data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +1 -1
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +0 -6
- data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +3 -4
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +0 -1
- data/lib/omnizip/io/source.rb +136 -0
- data/lib/omnizip/io.rb +2 -0
- data/lib/omnizip/link_handler.rb +7 -3
- data/lib/omnizip/metadata/metadata_editor.rb +1 -1
- data/lib/omnizip/models/algorithm_metadata.rb +33 -60
- data/lib/omnizip/models/compression_options.rb +40 -53
- data/lib/omnizip/models/conversion_options.rb +9 -18
- data/lib/omnizip/models/parallel_options.rb +25 -0
- data/lib/omnizip/optimization_registry.rb +8 -44
- data/lib/omnizip/parallel/job_scheduler.rb +26 -8
- data/lib/omnizip/parallel/parallel_compressor.rb +10 -20
- data/lib/omnizip/parallel/parallel_extractor.rb +20 -11
- data/lib/omnizip/parity/par2_repairer.rb +5 -5
- data/lib/omnizip/parity/par2_verifier.rb +27 -22
- data/lib/omnizip/parity.rb +4 -4
- data/lib/omnizip/password/encryption_registry.rb +11 -50
- data/lib/omnizip/password/zip_crypto_strategy.rb +1 -4
- data/lib/omnizip/pipe/stream_decompressor.rb +2 -3
- data/lib/omnizip/profile/archive_profile.rb +0 -2
- data/lib/omnizip/profile/binary_profile.rb +0 -2
- data/lib/omnizip/profile/compression_profile.rb +0 -1
- data/lib/omnizip/profile/profile_detector.rb +0 -2
- data/lib/omnizip/profile/text_profile.rb +0 -2
- data/lib/omnizip/profile.rb +0 -2
- data/lib/omnizip/registry.rb +112 -0
- data/lib/omnizip/version.rb +1 -1
- data/lib/omnizip/zip/file.rb +20 -9
- data/lib/omnizip.rb +26 -58
- metadata +24 -2
|
@@ -49,7 +49,7 @@ module Omnizip
|
|
|
49
49
|
# @return [Float] estimated seconds to completion
|
|
50
50
|
def estimate_completion_time(jobs, worker_count:,
|
|
51
51
|
bytes_per_second: 10_000_000)
|
|
52
|
-
total_bytes = jobs.sum { |job| job
|
|
52
|
+
total_bytes = jobs.sum { |job| job_size(job) }
|
|
53
53
|
return 0.0 if total_bytes.zero? || worker_count.zero?
|
|
54
54
|
|
|
55
55
|
# Simple estimate: total bytes / (workers * rate)
|
|
@@ -65,7 +65,7 @@ bytes_per_second: 10_000_000)
|
|
|
65
65
|
|
|
66
66
|
# Calculate total size per worker
|
|
67
67
|
worker_sizes = assignments.transform_values do |jobs|
|
|
68
|
-
jobs.sum { |job| job
|
|
68
|
+
jobs.sum { |job| job_size(job) }
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
sizes = worker_sizes.values
|
|
@@ -77,6 +77,24 @@ bytes_per_second: 10_000_000)
|
|
|
77
77
|
|
|
78
78
|
private
|
|
79
79
|
|
|
80
|
+
# Job size helper. Returns the job's #size if defined, else 1
|
|
81
|
+
# (counts as one unit of work). Centralized so the rest of the
|
|
82
|
+
# scheduler doesn't need +respond_to?+ checks.
|
|
83
|
+
#
|
|
84
|
+
# @param job [Object]
|
|
85
|
+
# @return [Integer]
|
|
86
|
+
def job_size(job)
|
|
87
|
+
if job.is_a?(::Numeric)
|
|
88
|
+
job
|
|
89
|
+
else
|
|
90
|
+
begin
|
|
91
|
+
job.size
|
|
92
|
+
rescue StandardError
|
|
93
|
+
nil
|
|
94
|
+
end || 1
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
80
98
|
# Validate scheduling strategy
|
|
81
99
|
#
|
|
82
100
|
# @raise [ArgumentError] if strategy is invalid
|
|
@@ -116,7 +134,7 @@ bytes_per_second: 10_000_000)
|
|
|
116
134
|
|
|
117
135
|
# Sort jobs by size (largest first) for better balance
|
|
118
136
|
sorted_jobs = jobs.sort_by do |job|
|
|
119
|
-
-(job
|
|
137
|
+
-job_size(job)
|
|
120
138
|
end
|
|
121
139
|
|
|
122
140
|
# Initialize worker assignments
|
|
@@ -125,14 +143,14 @@ bytes_per_second: 10_000_000)
|
|
|
125
143
|
|
|
126
144
|
# Assign each job to worker with smallest current load
|
|
127
145
|
sorted_jobs.each do |job|
|
|
128
|
-
|
|
146
|
+
job_size_value = job_size(job)
|
|
129
147
|
|
|
130
148
|
# Find worker with minimum load
|
|
131
149
|
min_worker = worker_loads.each_with_index.min_by { |load, _| load }[1]
|
|
132
150
|
|
|
133
151
|
# Assign job to this worker
|
|
134
152
|
assignments[min_worker] << job
|
|
135
|
-
worker_loads[min_worker] +=
|
|
153
|
+
worker_loads[min_worker] += job_size_value
|
|
136
154
|
end
|
|
137
155
|
|
|
138
156
|
# Add metadata
|
|
@@ -175,19 +193,19 @@ bytes_per_second: 10_000_000)
|
|
|
175
193
|
|
|
176
194
|
# Sort jobs by size (largest first)
|
|
177
195
|
sorted_jobs = jobs.sort_by do |job|
|
|
178
|
-
-(job
|
|
196
|
+
-job_size(job)
|
|
179
197
|
end
|
|
180
198
|
|
|
181
199
|
# First-fit decreasing bin packing
|
|
182
200
|
bins = Array.new(worker_count) { { jobs: [], total_size: 0 } }
|
|
183
201
|
|
|
184
202
|
sorted_jobs.each do |job|
|
|
185
|
-
|
|
203
|
+
job_size_value = job_size(job)
|
|
186
204
|
|
|
187
205
|
# Find bin with minimum total size
|
|
188
206
|
min_bin = bins.min_by { |bin| bin[:total_size] }
|
|
189
207
|
min_bin[:jobs] << job
|
|
190
|
-
min_bin[:total_size] +=
|
|
208
|
+
min_bin[:total_size] += job_size_value
|
|
191
209
|
end
|
|
192
210
|
|
|
193
211
|
# Convert to standard format
|
|
@@ -131,11 +131,7 @@ level: 6)
|
|
|
131
131
|
when Omnizip::Models::ParallelOptions
|
|
132
132
|
options.dup
|
|
133
133
|
when Hash
|
|
134
|
-
Omnizip::Models::ParallelOptions.new.
|
|
135
|
-
options.each do |k, v|
|
|
136
|
-
opts.send(:"#{k}=", v) if opts.respond_to?(:"#{k}=")
|
|
137
|
-
end
|
|
138
|
-
end
|
|
134
|
+
Omnizip::Models::ParallelOptions.new.apply(options)
|
|
139
135
|
else
|
|
140
136
|
Omnizip::Models::ParallelOptions.new
|
|
141
137
|
end
|
|
@@ -302,26 +298,20 @@ level: 6)
|
|
|
302
298
|
result = work_result.result
|
|
303
299
|
next unless result
|
|
304
300
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
entry[:uncompressed_size] = result[:uncompressed_size]
|
|
314
|
-
entry[:crc32] = result[:crc32]
|
|
315
|
-
entry[:compressed_data] = result[:compressed_data]
|
|
316
|
-
|
|
317
|
-
writer.instance_variable_get(:@entries) << entry
|
|
301
|
+
writer.add_precompressed_entry(
|
|
302
|
+
filename: result[:archive_path],
|
|
303
|
+
uncompressed_size: result[:uncompressed_size],
|
|
304
|
+
compressed_size: result[:compressed_size],
|
|
305
|
+
crc32: result[:crc32],
|
|
306
|
+
compressed_data: result[:compressed_data],
|
|
307
|
+
stat: result[:stat],
|
|
308
|
+
)
|
|
318
309
|
|
|
319
310
|
@stats[:bytes_processed] += result[:uncompressed_size]
|
|
320
311
|
@stats[:bytes_compressed] += result[:compressed_size]
|
|
321
312
|
end
|
|
322
313
|
|
|
323
|
-
|
|
324
|
-
writer.send(:write_with_precompressed_data, compression)
|
|
314
|
+
writer.write_precompressed(compression_method: compression)
|
|
325
315
|
end
|
|
326
316
|
|
|
327
317
|
# Calculate compression ratio
|
|
@@ -64,7 +64,7 @@ module Omnizip
|
|
|
64
64
|
dest_path: dest_path,
|
|
65
65
|
data: data,
|
|
66
66
|
directory: entry.directory?,
|
|
67
|
-
unix_perms: entry
|
|
67
|
+
unix_perms: safe_unix_perms(entry),
|
|
68
68
|
},
|
|
69
69
|
work: work,
|
|
70
70
|
)
|
|
@@ -107,10 +107,11 @@ module Omnizip
|
|
|
107
107
|
return "" unless compressed_data
|
|
108
108
|
|
|
109
109
|
# Decompress
|
|
110
|
-
reader.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
reader.decompress(
|
|
111
|
+
compressed_data,
|
|
112
|
+
reader_entry.compression_method,
|
|
113
|
+
reader_entry.uncompressed_size,
|
|
114
|
+
)
|
|
114
115
|
end
|
|
115
116
|
end
|
|
116
117
|
end
|
|
@@ -130,11 +131,7 @@ module Omnizip
|
|
|
130
131
|
when Omnizip::Models::ParallelOptions
|
|
131
132
|
options.dup
|
|
132
133
|
when Hash
|
|
133
|
-
Omnizip::Models::ParallelOptions.new.
|
|
134
|
-
options.each do |k, v|
|
|
135
|
-
opts.send(:"#{k}=", v) if opts.respond_to?(:"#{k}=")
|
|
136
|
-
end
|
|
137
|
-
end
|
|
134
|
+
Omnizip::Models::ParallelOptions.new.apply(options)
|
|
138
135
|
else
|
|
139
136
|
Omnizip::Models::ParallelOptions.new
|
|
140
137
|
end
|
|
@@ -182,7 +179,7 @@ module Omnizip
|
|
|
182
179
|
|
|
183
180
|
# Schedule jobs
|
|
184
181
|
entries.each do |entry|
|
|
185
|
-
file_size = entry
|
|
182
|
+
file_size = safe_entry_size(entry)
|
|
186
183
|
|
|
187
184
|
job_queue.push_with_size(
|
|
188
185
|
file: entry.name,
|
|
@@ -256,6 +253,18 @@ module Omnizip
|
|
|
256
253
|
|
|
257
254
|
private
|
|
258
255
|
|
|
256
|
+
def safe_unix_perms(entry)
|
|
257
|
+
entry.unix_perms
|
|
258
|
+
rescue NoMethodError
|
|
259
|
+
0
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def safe_entry_size(entry)
|
|
263
|
+
entry.size
|
|
264
|
+
rescue NoMethodError
|
|
265
|
+
0
|
|
266
|
+
end
|
|
267
|
+
|
|
259
268
|
# Read archive entries
|
|
260
269
|
#
|
|
261
270
|
# @param archive_path [String] archive path
|
|
@@ -162,7 +162,7 @@ module Omnizip
|
|
|
162
162
|
blocks = []
|
|
163
163
|
block_size = @verifier.metadata[:block_size]
|
|
164
164
|
|
|
165
|
-
file_list = @verifier.
|
|
165
|
+
file_list = @verifier.file_list
|
|
166
166
|
|
|
167
167
|
# Get list of damaged/missing files from verification
|
|
168
168
|
damaged_files = verification.damaged_files
|
|
@@ -171,7 +171,7 @@ module Omnizip
|
|
|
171
171
|
block_index = 0
|
|
172
172
|
|
|
173
173
|
file_list.each_with_index do |file_info, _file_idx|
|
|
174
|
-
file_path = @verifier.
|
|
174
|
+
file_path = @verifier.find_file_path(file_info[:filename])
|
|
175
175
|
(file_info[:size].to_f / block_size).ceil
|
|
176
176
|
|
|
177
177
|
# Treat damaged files same as missing files - don't read corrupted data
|
|
@@ -212,7 +212,7 @@ module Omnizip
|
|
|
212
212
|
#
|
|
213
213
|
# @return [Hash] Map of exponent => recovery_block_data
|
|
214
214
|
def load_parity_blocks_by_exponent
|
|
215
|
-
recovery_blocks = @verifier.
|
|
215
|
+
recovery_blocks = @verifier.recovery_blocks
|
|
216
216
|
|
|
217
217
|
# Group by exponent and take first block for each
|
|
218
218
|
# (PAR2 can have multiple slices with same exponent for large data)
|
|
@@ -234,7 +234,7 @@ module Omnizip
|
|
|
234
234
|
|
|
235
235
|
# Build file-to-blocks mapping using verifier's file list
|
|
236
236
|
# Note: @verifier was populated by the verify() call in repair()
|
|
237
|
-
file_list = @verifier.
|
|
237
|
+
file_list = @verifier.file_list
|
|
238
238
|
block_size = @verifier.metadata[:block_size]
|
|
239
239
|
|
|
240
240
|
# Ensure metadata is loaded
|
|
@@ -281,7 +281,7 @@ module Omnizip
|
|
|
281
281
|
output_dir ||= File.dirname(@par2_file)
|
|
282
282
|
FileUtils.mkdir_p(output_dir)
|
|
283
283
|
|
|
284
|
-
@verifier.
|
|
284
|
+
@verifier.file_list.each do |file_info|
|
|
285
285
|
num_blocks = (file_info[:size].to_f / @verifier.metadata[:block_size]).ceil
|
|
286
286
|
file_blocks_range = (block_idx...(block_idx + num_blocks)).to_a
|
|
287
287
|
|
|
@@ -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
|
-
|
|
129
|
-
|
|
130
|
-
#
|
|
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
|
data/lib/omnizip/parity.rb
CHANGED
|
@@ -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.
|
|
178
|
+
verifier.parse_par2_file
|
|
179
179
|
|
|
180
|
-
total_blocks = verifier.
|
|
181
|
-
recovery_blocks = verifier.
|
|
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.
|
|
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
|
-
|
|
6
|
-
class EncryptionRegistry
|
|
7
|
-
@strategies = {}
|
|
8
|
-
|
|
5
|
+
class EncryptionRegistry < Omnizip::Registry
|
|
9
6
|
class << self
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
167
|
-
|
|
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
|
data/lib/omnizip/profile.rb
CHANGED
|
@@ -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
|
data/lib/omnizip/version.rb
CHANGED