omnizip 0.3.56 → 0.3.57
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 +14 -0
- data/docs/developer/architecture.adoc +2 -2
- data/docs/getting-started/quick-start.adoc +1 -3
- data/docs/getting-started/your-first-archive.adoc +9 -8
- data/lib/omnizip/cli.rb +0 -2
- data/lib/omnizip/commands/archive_repair_command.rb +1 -1
- data/lib/omnizip/commands/archive_verify_command.rb +1 -1
- data/lib/omnizip/entry.rb +1 -1
- data/lib/omnizip/formats/cpio/entry.rb +3 -3
- data/lib/omnizip/formats/rar/decompressor.rb +2 -2
- data/lib/omnizip/formats/rar/external_writer.rb +2 -2
- data/lib/omnizip/formats/rar/header.rb +4 -4
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_splitter.rb +2 -2
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +4 -4
- data/lib/omnizip/formats/rar/recovery_record.rb +1 -1
- data/lib/omnizip/formats/tar/header.rb +43 -2
- data/lib/omnizip/parity/par2_creator.rb +1 -1
- data/lib/omnizip/parity/par2_repairer.rb +1 -1
- data/lib/omnizip/parity/par2cmdline_algorithm.rb +1 -1
- data/lib/omnizip/parity/reed_solomon_decoder.rb +1 -1
- data/lib/omnizip/parity/reed_solomon_matrix.rb +2 -2
- data/lib/omnizip/profile.rb +4 -4
- data/lib/omnizip/version.rb +1 -1
- data/lib/omnizip/zip/file.rb +1 -1
- data/lib/omnizip/zip/output_stream.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ea5d1a7c799ac0f1b17c60e1c715fc0bf95a2d8dc3d3a02e0776bac6efbccf0
|
|
4
|
+
data.tar.gz: 198583bbdc571acdd26a8a1e25f4a6ce0e5aa6b33d613c247bf49556d164200c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: edaf17357204a214beecdd32e18bd53139bb6a7b9797b29a181a44db80b531e847f6d3e48da97f6ce5f02e80feeb456c71e593df92b471b4d5875a8d13ae1e98
|
|
7
|
+
data.tar.gz: 6f82822274ad086eef57304f2386a43f979525dd6893f0eabf693b6c8c60a08a71729beb5c2a3328c8617871bbbc4b9d33a2b6801ae801eeb3614a8f4cbd6d4e
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.56] - 2026-09-02
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `FilterRegistry#entry_for` read its storage and lazy triggers
|
|
14
|
+
outside the mutex `register` writes under — a latent data race in
|
|
15
|
+
the 7z codec chain's filter resolution. Reads now hold the lock,
|
|
16
|
+
with the lazy trigger fired outside it (it re-enters through
|
|
17
|
+
register's own synchronize).
|
|
18
|
+
- The progress lifecycle is complete: `ProgressTracker` gains
|
|
19
|
+
`#start` and `#finish`, firing the reporter start/finish hooks
|
|
20
|
+
every reporter class overrides but nothing ever called —
|
|
21
|
+
"operation started/finished" output could never appear. `#finish`
|
|
22
|
+
also forces a final report and is idempotent.
|
|
23
|
+
|
|
10
24
|
## [0.3.55] - 2026-09-01
|
|
11
25
|
|
|
12
26
|
### Changed
|
|
@@ -103,11 +103,9 @@ omnizip archive list backup.7z
|
|
|
103
103
|
require 'omnizip'
|
|
104
104
|
|
|
105
105
|
reader = Omnizip::Formats::SevenZip::Reader.new('backup.7z')
|
|
106
|
-
reader.
|
|
107
|
-
reader.each_file do |entry|
|
|
106
|
+
reader.entries.each do |entry|
|
|
108
107
|
puts "#{entry.name}: #{entry.size} bytes"
|
|
109
108
|
end
|
|
110
|
-
reader.close
|
|
111
109
|
----
|
|
112
110
|
|
|
113
111
|
=== Using Different Compression Algorithms
|
|
@@ -180,12 +180,15 @@ writer.add_directory('/home/user/photos')
|
|
|
180
180
|
writer.close
|
|
181
181
|
|
|
182
182
|
# Get statistics
|
|
183
|
-
|
|
183
|
+
entries = Omnizip.list_archive('backup.7z', details: true)
|
|
184
|
+
files = entries.reject { |e| e[:directory] }
|
|
185
|
+
uncompressed = files.sum { |e| e[:size] }
|
|
186
|
+
compressed = File.size('backup.7z')
|
|
184
187
|
puts "Archive created:"
|
|
185
|
-
puts " Files: #{
|
|
186
|
-
puts " Size: #{
|
|
187
|
-
puts " Compressed: #{
|
|
188
|
-
puts " Ratio: #{(
|
|
188
|
+
puts " Files: #{files.size}"
|
|
189
|
+
puts " Size: #{uncompressed} bytes"
|
|
190
|
+
puts " Compressed: #{compressed} bytes"
|
|
191
|
+
puts " Ratio: #{(compressed.to_f / uncompressed * 100).round(1)}%"
|
|
189
192
|
----
|
|
190
193
|
|
|
191
194
|
=== Verifying Your Archive
|
|
@@ -202,12 +205,10 @@ omnizip archive list backup.7z
|
|
|
202
205
|
require 'omnizip'
|
|
203
206
|
|
|
204
207
|
reader = Omnizip::Formats::SevenZip::Reader.new('backup.7z')
|
|
205
|
-
reader.open
|
|
206
208
|
puts "Archive contents:"
|
|
207
|
-
reader.
|
|
209
|
+
reader.entries.each do |entry|
|
|
208
210
|
puts " #{entry.name} (#{entry.size} bytes)"
|
|
209
211
|
end
|
|
210
|
-
reader.close
|
|
211
212
|
----
|
|
212
213
|
|
|
213
214
|
=== Next Steps
|
data/lib/omnizip/cli.rb
CHANGED
|
@@ -182,8 +182,6 @@ module Omnizip
|
|
|
182
182
|
desc: "Password for encryption (7z: header encryption, RAR5: AES-256-CBC)"
|
|
183
183
|
option :encrypt_headers, type: :boolean, default: false,
|
|
184
184
|
desc: "Encrypt archive headers (7z only, hides filenames)"
|
|
185
|
-
option :preserve_ntfs_streams, type: :boolean, default: false,
|
|
186
|
-
desc: "Preserve NTFS alternate data streams (Windows only, 7z only)"
|
|
187
185
|
option :rar_version, type: :numeric, default: 5,
|
|
188
186
|
desc: "RAR version (4 or 5, default: 5 for pure Ruby)"
|
|
189
187
|
option :rar_compression, type: :string, default: "store",
|
|
@@ -18,7 +18,7 @@ module Omnizip
|
|
|
18
18
|
# @param input_path [String] Path to corrupted archive
|
|
19
19
|
# @param output_path [String] Path for repaired archive
|
|
20
20
|
def run(input_path, output_path)
|
|
21
|
-
raise "Archive not found: #{input_path}" unless File.exist?(input_path)
|
|
21
|
+
raise Omnizip::IOError, "Archive not found: #{input_path}" unless File.exist?(input_path)
|
|
22
22
|
|
|
23
23
|
# Detect format
|
|
24
24
|
format = detect_format(input_path)
|
|
@@ -18,7 +18,7 @@ module Omnizip
|
|
|
18
18
|
# @param archive_path [String] Path to archive
|
|
19
19
|
def run(archive_path)
|
|
20
20
|
unless File.exist?(archive_path)
|
|
21
|
-
raise "Archive not found: #{archive_path}"
|
|
21
|
+
raise Omnizip::IOError, "Archive not found: #{archive_path}"
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Detect format
|
data/lib/omnizip/entry.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Omnizip
|
|
|
7
7
|
# Each format-specific entry class (Zip::Entry, Tar::Entry,
|
|
8
8
|
# SevenZip::Models::FileEntry, Iso::DirectoryRecord, Ole::Dirent,
|
|
9
9
|
# Xar::Entry, Rpm::Entry, Msi::Entry, Cpio::Entry, Rar3::Entry,
|
|
10
|
-
# Rar5::Entry,
|
|
10
|
+
# Rar5::Entry, Metadata::EntryMetadata) includes this
|
|
11
11
|
# module so callers can rely on a single narrow interface instead
|
|
12
12
|
# of duck-typing through `respond_to?(:name) / :path / :filename`
|
|
13
13
|
# cascades.
|
|
@@ -232,7 +232,7 @@ module Omnizip
|
|
|
232
232
|
when :odc
|
|
233
233
|
parse_odc(io)
|
|
234
234
|
else
|
|
235
|
-
raise "Unknown CPIO format"
|
|
235
|
+
raise Omnizip::InvalidArchiveError, "Unknown CPIO format"
|
|
236
236
|
end
|
|
237
237
|
end
|
|
238
238
|
|
|
@@ -258,7 +258,7 @@ module Omnizip
|
|
|
258
258
|
# @return [Entry] Parsed entry
|
|
259
259
|
def self.parse_newc(io)
|
|
260
260
|
header = io.read(NEWC_HEADER_SIZE)
|
|
261
|
-
raise "Truncated CPIO header" unless header && header.bytesize == NEWC_HEADER_SIZE
|
|
261
|
+
raise Omnizip::InvalidArchiveError, "Truncated CPIO header" unless header && header.bytesize == NEWC_HEADER_SIZE
|
|
262
262
|
|
|
263
263
|
# Parse ASCII hex fields
|
|
264
264
|
magic = header[0, 6]
|
|
@@ -318,7 +318,7 @@ module Omnizip
|
|
|
318
318
|
# @return [Entry] Parsed entry
|
|
319
319
|
def self.parse_odc(io)
|
|
320
320
|
header = io.read(76)
|
|
321
|
-
raise "Truncated CPIO header" unless header && header.bytesize == 76
|
|
321
|
+
raise Omnizip::InvalidArchiveError, "Truncated CPIO header" unless header && header.bytesize == 76
|
|
322
322
|
|
|
323
323
|
# Parse octal fields
|
|
324
324
|
magic = header[0, 6]
|
|
@@ -232,7 +232,7 @@ module Omnizip
|
|
|
232
232
|
archive.extract_to_file(entry_name, output_path,
|
|
233
233
|
password: password)
|
|
234
234
|
rescue StandardError => e
|
|
235
|
-
raise "Gem entry extraction failed: #{e.message}"
|
|
235
|
+
raise Omnizip::DecompressionError, "Gem entry extraction failed: #{e.message}"
|
|
236
236
|
end
|
|
237
237
|
|
|
238
238
|
# Extract entry with command
|
|
@@ -255,7 +255,7 @@ module Omnizip
|
|
|
255
255
|
cmd = build_extract_command(archive_path, cached_dir, password)
|
|
256
256
|
unless system(*cmd)
|
|
257
257
|
FileUtils.rm_rf(cached_dir)
|
|
258
|
-
raise "Command entry extraction failed: #{entry_name}"
|
|
258
|
+
raise Omnizip::DecompressionError, "Command entry extraction failed: #{entry_name}"
|
|
259
259
|
end
|
|
260
260
|
extract_cache[cache_key] = cached_dir
|
|
261
261
|
install_exit_sweeper
|
|
@@ -138,7 +138,7 @@ module Omnizip
|
|
|
138
138
|
stdout, stderr, status = Open3.capture3(*cmd)
|
|
139
139
|
|
|
140
140
|
unless status.success?
|
|
141
|
-
raise "RAR creation failed: #{stderr}\n#{stdout}"
|
|
141
|
+
raise Omnizip::CompressionError, "RAR creation failed: #{stderr}\n#{stdout}"
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
# Test archive if requested
|
|
@@ -257,7 +257,7 @@ module Omnizip
|
|
|
257
257
|
|
|
258
258
|
return if status.success?
|
|
259
259
|
|
|
260
|
-
raise "Archive test failed: #{stderr}\n#{stdout}"
|
|
260
|
+
raise Omnizip::CompressionError, "Archive test failed: #{stderr}\n#{stdout}"
|
|
261
261
|
end
|
|
262
262
|
|
|
263
263
|
# Get RAR executable path
|
|
@@ -52,10 +52,10 @@ module Omnizip
|
|
|
52
52
|
@version = 5
|
|
53
53
|
parse_rar5_header(io)
|
|
54
54
|
else
|
|
55
|
-
raise "Invalid RAR signature: #{(sig_bytes + (extra_byte&.bytes || [])).inspect}"
|
|
55
|
+
raise Omnizip::InvalidArchiveError, "Invalid RAR signature: #{(sig_bytes + (extra_byte&.bytes || [])).inspect}"
|
|
56
56
|
end
|
|
57
57
|
else
|
|
58
|
-
raise "Invalid RAR signature: #{sig_bytes.inspect}"
|
|
58
|
+
raise Omnizip::InvalidArchiveError, "Invalid RAR signature: #{sig_bytes.inspect}"
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
@@ -103,7 +103,7 @@ module Omnizip
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
unless head_type == BLOCK_ARCHIVE
|
|
106
|
-
raise "Expected archive block, got 0x#{head_type.to_s(16)}"
|
|
106
|
+
raise Omnizip::InvalidArchiveError, "Expected archive block, got 0x#{head_type.to_s(16)}"
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
@flags = head_flags
|
|
@@ -140,7 +140,7 @@ module Omnizip
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
unless header_type == RAR5_HEADER_MAIN
|
|
143
|
-
raise "Expected main header, got #{header_type}"
|
|
143
|
+
raise Omnizip::InvalidArchiveError, "Expected main header, got #{header_type}"
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
@flags = header_flags
|
|
@@ -77,8 +77,8 @@ module Omnizip
|
|
|
77
77
|
# @raise [RuntimeError] if no volume is active
|
|
78
78
|
# @raise [RuntimeError] if data doesn't fit
|
|
79
79
|
def write_to_current_volume(data)
|
|
80
|
-
raise "No active volume" if @current_volume_number.zero?
|
|
81
|
-
raise "Data doesn't fit in current volume" unless can_fit_in_current_volume?(data.bytesize)
|
|
80
|
+
raise Omnizip::CompressionError, "No active volume" if @current_volume_number.zero?
|
|
81
|
+
raise Omnizip::CompressionError, "Data doesn't fit in current volume" unless can_fit_in_current_volume?(data.bytesize)
|
|
82
82
|
|
|
83
83
|
@current_volume_data << data
|
|
84
84
|
@current_volume_bytes += data.bytesize
|
|
@@ -80,7 +80,7 @@ module Omnizip
|
|
|
80
80
|
#
|
|
81
81
|
# @return [void]
|
|
82
82
|
def write_signature
|
|
83
|
-
raise "Volume not open" unless @io
|
|
83
|
+
raise Omnizip::IOError, "Volume not open" unless @io
|
|
84
84
|
|
|
85
85
|
# RAR5 signature: "Rar!\x1A\x07\x01\x00"
|
|
86
86
|
signature = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01,
|
|
@@ -92,7 +92,7 @@ module Omnizip
|
|
|
92
92
|
#
|
|
93
93
|
# @return [void]
|
|
94
94
|
def write_main_header
|
|
95
|
-
raise "Volume not open" unless @io
|
|
95
|
+
raise Omnizip::IOError, "Volume not open" unless @io
|
|
96
96
|
|
|
97
97
|
# Archive flags: every volume declares 0x0001; volumes
|
|
98
98
|
# after the first also carry the volume number field
|
|
@@ -114,7 +114,7 @@ module Omnizip
|
|
|
114
114
|
# @param compressed_data [String] Compressed file data
|
|
115
115
|
# @return [void]
|
|
116
116
|
def write_file_data(file_header, compressed_data)
|
|
117
|
-
raise "Volume not open" unless @io
|
|
117
|
+
raise Omnizip::IOError, "Volume not open" unless @io
|
|
118
118
|
|
|
119
119
|
@io.write(file_header.encode)
|
|
120
120
|
@io.write(compressed_data)
|
|
@@ -124,7 +124,7 @@ module Omnizip
|
|
|
124
124
|
#
|
|
125
125
|
# @return [void]
|
|
126
126
|
def write_end_header
|
|
127
|
-
raise "Volume not open" unless @io
|
|
127
|
+
raise Omnizip::IOError, "Volume not open" unless @io
|
|
128
128
|
|
|
129
129
|
# End-of-archive flags: 0x0001 marks a volume with more
|
|
130
130
|
# volumes following; the last volume ends with 0.
|
|
@@ -47,6 +47,11 @@ module Omnizip
|
|
|
47
47
|
entry.prefix = extract_string(
|
|
48
48
|
header_data, PREFIX_OFFSET, PREFIX_SIZE
|
|
49
49
|
)
|
|
50
|
+
# ustar stores long paths split across prefix + name;
|
|
51
|
+
# consumers see the joined path
|
|
52
|
+
unless entry.prefix.empty?
|
|
53
|
+
entry.name = "#{entry.prefix}/#{entry.name}"
|
|
54
|
+
end
|
|
50
55
|
end
|
|
51
56
|
|
|
52
57
|
# Verify checksum
|
|
@@ -69,7 +74,8 @@ module Omnizip
|
|
|
69
74
|
header = "\0" * HEADER_SIZE
|
|
70
75
|
|
|
71
76
|
# Write fields to header
|
|
72
|
-
|
|
77
|
+
name, prefix = split_long_name(entry.name, entry.prefix)
|
|
78
|
+
write_string(header, name, NAME_OFFSET, NAME_SIZE)
|
|
73
79
|
write_octal(header, entry.mode, MODE_OFFSET, MODE_SIZE)
|
|
74
80
|
write_octal(header, entry.uid, UID_OFFSET, UID_SIZE)
|
|
75
81
|
write_octal(header, entry.gid, GID_OFFSET, GID_SIZE)
|
|
@@ -87,7 +93,7 @@ module Omnizip
|
|
|
87
93
|
write_string(header, entry.gname, GNAME_OFFSET, GNAME_SIZE)
|
|
88
94
|
write_octal(header, entry.devmajor, DEVMAJOR_OFFSET, DEVMAJOR_SIZE)
|
|
89
95
|
write_octal(header, entry.devminor, DEVMINOR_OFFSET, DEVMINOR_SIZE)
|
|
90
|
-
write_string(header,
|
|
96
|
+
write_string(header, prefix, PREFIX_OFFSET, PREFIX_SIZE)
|
|
91
97
|
|
|
92
98
|
# Calculate and write checksum
|
|
93
99
|
checksum = Entry.calculate_checksum(header)
|
|
@@ -130,6 +136,41 @@ module Omnizip
|
|
|
130
136
|
field.strip.to_i(8)
|
|
131
137
|
end
|
|
132
138
|
|
|
139
|
+
# Split a path for the ustar name (100) + prefix (155)
|
|
140
|
+
# fields. Paths under 100 bytes pass through; longer paths
|
|
141
|
+
# split at a "/" boundary, preferring the longest prefix
|
|
142
|
+
# that keeps both halves in bounds. Raises when no ustar
|
|
143
|
+
# representation exists — silent truncation corrupts
|
|
144
|
+
# archives.
|
|
145
|
+
#
|
|
146
|
+
# @param path [String] Full entry path
|
|
147
|
+
# @param prefix [String] Explicit prefix (wins when given)
|
|
148
|
+
# @return [Array<String>] [name, prefix] pair
|
|
149
|
+
def self.split_long_name(path, prefix = nil)
|
|
150
|
+
path = path.to_s
|
|
151
|
+
prefix = prefix.to_s
|
|
152
|
+
return [path, prefix] if !prefix.empty? || path.bytesize < NAME_SIZE
|
|
153
|
+
|
|
154
|
+
best = nil
|
|
155
|
+
offset = -1
|
|
156
|
+
while (slash = path.index("/", offset + 1))
|
|
157
|
+
candidate_prefix = path[0...slash]
|
|
158
|
+
candidate_name = path[(slash + 1)..]
|
|
159
|
+
offset = slash
|
|
160
|
+
|
|
161
|
+
next if candidate_prefix.bytesize >= PREFIX_SIZE
|
|
162
|
+
next if candidate_name.bytesize >= NAME_SIZE
|
|
163
|
+
|
|
164
|
+
best = [candidate_name, candidate_prefix]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
return best if best
|
|
168
|
+
|
|
169
|
+
raise Omnizip::FormatError,
|
|
170
|
+
"Path too long for the ustar name+prefix fields " \
|
|
171
|
+
"(no PAX/GNU long-name support): #{path}"
|
|
172
|
+
end
|
|
173
|
+
|
|
133
174
|
# Write a string to header
|
|
134
175
|
#
|
|
135
176
|
# @param header [String] Header data
|
|
@@ -151,7 +151,7 @@ progress: nil)
|
|
|
151
151
|
#
|
|
152
152
|
# @raise [StandardError] if no files added
|
|
153
153
|
def validate_files!
|
|
154
|
-
raise "No files added to PAR2 set" if @files.empty?
|
|
154
|
+
raise ArgumentError, "No files added to PAR2 set" if @files.empty?
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
# Generate unique set ID for this PAR2 set
|
|
@@ -244,7 +244,7 @@ module Omnizip
|
|
|
244
244
|
|
|
245
245
|
# Ensure metadata is loaded
|
|
246
246
|
if file_list.nil? || file_list.empty?
|
|
247
|
-
raise "Internal error: file_list not populated in verifier"
|
|
247
|
+
raise Omnizip::Error, "Internal error: file_list not populated in verifier"
|
|
248
248
|
end
|
|
249
249
|
|
|
250
250
|
block_idx = 0
|
|
@@ -35,7 +35,7 @@ module Omnizip
|
|
|
35
35
|
# Find next logbase where gcd(65535, logbase) == 1
|
|
36
36
|
logbase += 1 while gcd(limit, logbase) != 1
|
|
37
37
|
|
|
38
|
-
raise "Too many input blocks" if logbase >= limit
|
|
38
|
+
raise ArgumentError, "Too many input blocks" if logbase >= limit
|
|
39
39
|
|
|
40
40
|
# Use antilog to convert logbase to base value
|
|
41
41
|
# This is the key: base = antilog[logbase]
|
|
@@ -109,7 +109,7 @@ recovery_exponents, bases)
|
|
|
109
109
|
num_rows.times do |pivot_row|
|
|
110
110
|
# Get pivot value from right matrix diagonal
|
|
111
111
|
pivot = right_matrix[pivot_row][pivot_row]
|
|
112
|
-
raise "Singular matrix at row #{pivot_row}" if pivot.zero?
|
|
112
|
+
raise Omnizip::Error, "Singular matrix at row #{pivot_row}" if pivot.zero?
|
|
113
113
|
|
|
114
114
|
# Scale pivot row to make pivot = 1
|
|
115
115
|
unless pivot == 1
|
|
@@ -117,7 +117,7 @@ total_inputs, block_size)
|
|
|
117
117
|
# @param recovery_idx [Integer] Recovery block index (0..recovery_count-1)
|
|
118
118
|
# @return [Integer] Galois field coefficient
|
|
119
119
|
def coefficient(output_idx, recovery_idx)
|
|
120
|
-
raise "Matrix not computed - call compute! first" unless @matrix
|
|
120
|
+
raise Omnizip::Error, "Matrix not computed - call compute! first" unless @matrix
|
|
121
121
|
|
|
122
122
|
# After transpose, indices are swapped: @matrix[recovery_idx][output_idx]
|
|
123
123
|
@matrix[recovery_idx][output_idx]
|
|
@@ -200,7 +200,7 @@ output_offset: 0)
|
|
|
200
200
|
|
|
201
201
|
num_rows.times do |pivot_row|
|
|
202
202
|
pivot = left_matrix[pivot_row][pivot_row]
|
|
203
|
-
raise "Singular matrix at row #{pivot_row}" if pivot.zero?
|
|
203
|
+
raise Omnizip::Error, "Singular matrix at row #{pivot_row}" if pivot.zero?
|
|
204
204
|
|
|
205
205
|
# Scale pivot row to make pivot = 1
|
|
206
206
|
unless pivot == 1
|
data/lib/omnizip/profile.rb
CHANGED
|
@@ -181,11 +181,11 @@ module Omnizip
|
|
|
181
181
|
|
|
182
182
|
private
|
|
183
183
|
|
|
184
|
-
# Depth-first search for the first regular file under +dir
|
|
184
|
+
# Depth-first search for the first regular file under +dir+,
|
|
185
|
+
# in sorted child order — :auto sampling must not depend on
|
|
186
|
+
# the OS's directory iteration order.
|
|
185
187
|
def first_file_within(dir)
|
|
186
|
-
::Dir.
|
|
187
|
-
next if [".", ".."].include?(entry)
|
|
188
|
-
|
|
188
|
+
::Dir.children(dir).sort.each do |entry|
|
|
189
189
|
full_path = ::File.join(dir, entry)
|
|
190
190
|
return full_path if ::File.file?(full_path)
|
|
191
191
|
|
data/lib/omnizip/version.rb
CHANGED
data/lib/omnizip/zip/file.rb
CHANGED
|
@@ -75,7 +75,7 @@ compression: :deflate, level: 6)
|
|
|
75
75
|
# Write data to current entry
|
|
76
76
|
# @param data [String] Data to write
|
|
77
77
|
def write(data)
|
|
78
|
-
raise "No entry started. Call put_next_entry first" unless @current_entry
|
|
78
|
+
raise Omnizip::IOError, "No entry started. Call put_next_entry first" unless @current_entry
|
|
79
79
|
|
|
80
80
|
@current_entry_data << data.b
|
|
81
81
|
self
|