omnizip 0.3.35 → 0.3.37
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 +67 -0
- data/docs/getting-started/basic-usage.adoc +14 -22
- data/docs/guides/advanced-features/encryption.adoc +16 -39
- data/docs/guides/advanced-features/index.adoc +16 -6
- data/docs/guides/advanced-features/parallel-processing.adoc +63 -165
- data/docs/guides/advanced-features/progress-tracking.adoc +65 -261
- data/docs/guides/advanced-features/streaming.adoc +43 -113
- data/docs/guides/compression-algorithms/bzip2.adoc +2 -1
- data/docs/guides/compression-algorithms/deflate.adoc +1 -7
- data/docs/guides/compression-algorithms/lzma.adoc +10 -14
- data/docs/guides/compression-algorithms/lzma2.adoc +34 -117
- data/docs/guides/compression-algorithms/ppmd.adoc +24 -27
- data/docs/guides/compression-algorithms/zstandard.adoc +85 -150
- data/docs/troubleshooting/index.adoc +7 -5
- data/lib/omnizip/algorithms/zstandard.rb +2 -3
- data/lib/omnizip/archive/builder.rb +48 -0
- data/lib/omnizip/archive/reader_session.rb +67 -0
- data/lib/omnizip/archive.rb +86 -0
- data/lib/omnizip/archive_handler.rb +1 -0
- data/lib/omnizip/archive_handlers/rar_handler.rb +50 -0
- data/lib/omnizip/archive_handlers/seven_zip_handler.rb +6 -2
- data/lib/omnizip/archive_handlers/tar_handler.rb +4 -0
- data/lib/omnizip/archive_handlers/zip_handler.rb +25 -2
- data/lib/omnizip/archive_handlers.rb +1 -0
- data/lib/omnizip/convenience.rb +91 -11
- data/lib/omnizip/formats/rar/block_parser.rb +8 -12
- data/lib/omnizip/formats/rar/constants.rb +3 -1
- data/lib/omnizip/formats/rar/header.rb +16 -6
- data/lib/omnizip/formats/rar/reader.rb +6 -2
- data/lib/omnizip/version.rb +1 -1
- data/lib/omnizip.rb +1 -0
- metadata +6 -2
|
@@ -5,8 +5,13 @@ module Omnizip
|
|
|
5
5
|
# Adapter that exposes the canonical +create+/+open+/+extract_to+
|
|
6
6
|
# /+list+ interface for the ZIP format. Wraps +Omnizip::Zip::File+.
|
|
7
7
|
class ZipHandler
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
# Zip::File has no archive-level compression options (method and
|
|
9
|
+
# level are per-entry in Zip::OutputStream), so extra keywords —
|
|
10
|
+
# e.g. profile-injected ones — are absorbed here.
|
|
11
|
+
def create(path, **_options, &block)
|
|
12
|
+
Omnizip::Zip::File.create(path) do |file|
|
|
13
|
+
block&.call(FileProxy.new(file))
|
|
14
|
+
end
|
|
10
15
|
end
|
|
11
16
|
|
|
12
17
|
def open(path, &block)
|
|
@@ -66,6 +71,24 @@ module Omnizip
|
|
|
66
71
|
def remove_entry(path, entry_name)
|
|
67
72
|
Omnizip::Zip::File.open(path) { |zip| zip.remove(entry_name) }
|
|
68
73
|
end
|
|
74
|
+
|
|
75
|
+
# Translates the generic +add(name, source_path)+ and
|
|
76
|
+
# +add_data(name, data)+ interface. +Zip::File#add_data+ is
|
|
77
|
+
# private, so in-memory content goes through the public block
|
|
78
|
+
# form +add(name) { data }+.
|
|
79
|
+
class FileProxy
|
|
80
|
+
def initialize(file)
|
|
81
|
+
@file = file
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def add(name, source_path = nil)
|
|
85
|
+
@file.add(name, source_path)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def add_data(name, data)
|
|
89
|
+
@file.add(name) { data }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
69
92
|
end
|
|
70
93
|
end
|
|
71
94
|
end
|
|
@@ -8,5 +8,6 @@ module Omnizip
|
|
|
8
8
|
autoload :ZipHandler, "omnizip/archive_handlers/zip_handler"
|
|
9
9
|
autoload :TarHandler, "omnizip/archive_handlers/tar_handler"
|
|
10
10
|
autoload :SevenZipHandler, "omnizip/archive_handlers/seven_zip_handler"
|
|
11
|
+
autoload :RarHandler, "omnizip/archive_handlers/rar_handler"
|
|
11
12
|
end
|
|
12
13
|
end
|
data/lib/omnizip/convenience.rb
CHANGED
|
@@ -40,13 +40,50 @@ module Omnizip
|
|
|
40
40
|
return output_path
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
Omnizip::ArchiveHandler.for(resolve_archive_format(output_path, format)).create(output_path) do |archive|
|
|
43
|
+
Omnizip::ArchiveHandler.for(resolve_archive_format(output_path, format)).create(output_path, **options) do |archive|
|
|
44
44
|
archive.add(::File.basename(input_path), input_path)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
output_path
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
# Decompress a single-file compressed stream (the counterpart of
|
|
51
|
+
# #compress_file). The format is resolved from the input
|
|
52
|
+
# extension: .gz, .bz2, .xz, .lzma, .lz.
|
|
53
|
+
#
|
|
54
|
+
# @param compressed_path [String] Path to the compressed file
|
|
55
|
+
# @param output_path [String] Path to write the decompressed data
|
|
56
|
+
# @return [String] +output_path+
|
|
57
|
+
def decompress_file(compressed_path, output_path)
|
|
58
|
+
require_archive!(compressed_path)
|
|
59
|
+
|
|
60
|
+
format_class = DECOMPRESSORS[::File.extname(compressed_path).downcase]
|
|
61
|
+
unless format_class
|
|
62
|
+
raise Omnizip::UnsupportedFormatError,
|
|
63
|
+
"decompress_file supports #{DECOMPRESSORS.keys.join(', ')}; " \
|
|
64
|
+
"use extract_archive for archive formats"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
case format_class
|
|
68
|
+
when :xz
|
|
69
|
+
::File.binwrite(output_path, Formats::Xz.decompress(compressed_path))
|
|
70
|
+
when :zstandard
|
|
71
|
+
zstd = Algorithms::Zstandard.new
|
|
72
|
+
::File.open(compressed_path, "rb") do |input_io|
|
|
73
|
+
::File.open(output_path, "wb") do |output_io|
|
|
74
|
+
zstd.decompress(input_io, output_io)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
::File.open(compressed_path, "rb") do |input_io|
|
|
79
|
+
::File.open(output_path, "wb") do |output_io|
|
|
80
|
+
format_class.decompress_stream(input_io, output_io)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
output_path
|
|
85
|
+
end
|
|
86
|
+
|
|
50
87
|
# Compress a directory into an archive.
|
|
51
88
|
#
|
|
52
89
|
# @param input_dir [String] Path to input directory
|
|
@@ -64,12 +101,23 @@ module Omnizip
|
|
|
64
101
|
raise ArgumentError, "Input is not a directory: #{input_dir}"
|
|
65
102
|
end
|
|
66
103
|
|
|
104
|
+
# A directory cannot be written through a single-file stream
|
|
105
|
+
# format; routing it to the default would silently produce a
|
|
106
|
+
# ZIP under a foreign extension.
|
|
107
|
+
if format == DEFAULT_FORMAT && single_file_handler(output_path)
|
|
108
|
+
raise Omnizip::UnsupportedFormatError,
|
|
109
|
+
"#{::File.extname(output_path).downcase} is a single-file " \
|
|
110
|
+
"stream format and cannot contain a directory; use an " \
|
|
111
|
+
"archive format (.zip/.tar/.7z) or compress entries " \
|
|
112
|
+
"individually"
|
|
113
|
+
end
|
|
114
|
+
|
|
67
115
|
if options[:profile]
|
|
68
116
|
first_file = find_first_file(input_dir)
|
|
69
117
|
apply_profile(first_file, options)
|
|
70
118
|
end
|
|
71
119
|
|
|
72
|
-
Omnizip::ArchiveHandler.for(resolve_archive_format(output_path, format)).create(output_path) do |archive|
|
|
120
|
+
Omnizip::ArchiveHandler.for(resolve_archive_format(output_path, format)).create(output_path, **options) do |archive|
|
|
73
121
|
add_directory_contents(archive, input_dir, "", recursive: recursive)
|
|
74
122
|
end
|
|
75
123
|
|
|
@@ -87,7 +135,7 @@ module Omnizip
|
|
|
87
135
|
def extract_archive(archive_path, output_dir, format: DEFAULT_FORMAT,
|
|
88
136
|
overwrite: false, **options)
|
|
89
137
|
require_archive!(archive_path)
|
|
90
|
-
Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format))
|
|
138
|
+
Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format, writing: false))
|
|
91
139
|
.extract_to(archive_path, output_dir,
|
|
92
140
|
overwrite: overwrite, **options)
|
|
93
141
|
end
|
|
@@ -102,7 +150,7 @@ module Omnizip
|
|
|
102
150
|
def list_archive(archive_path, format: DEFAULT_FORMAT, details: false,
|
|
103
151
|
**options)
|
|
104
152
|
require_archive!(archive_path)
|
|
105
|
-
Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format))
|
|
153
|
+
Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format, writing: false))
|
|
106
154
|
.list(archive_path, details: details, **options)
|
|
107
155
|
end
|
|
108
156
|
|
|
@@ -114,7 +162,7 @@ module Omnizip
|
|
|
114
162
|
# @return [String] Entry contents
|
|
115
163
|
def read_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT)
|
|
116
164
|
require_archive!(archive_path)
|
|
117
|
-
Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format)).read_entry(archive_path, entry_name)
|
|
165
|
+
Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format, writing: false)).read_entry(archive_path, entry_name)
|
|
118
166
|
end
|
|
119
167
|
|
|
120
168
|
# Add a file to an existing archive.
|
|
@@ -131,13 +179,14 @@ module Omnizip
|
|
|
131
179
|
raise Errno::ENOENT, "Source file not found: #{source_path}"
|
|
132
180
|
end
|
|
133
181
|
|
|
134
|
-
|
|
182
|
+
resolved = resolve_archive_format(archive_path, format)
|
|
183
|
+
handler = Omnizip::ArchiveHandler.for(resolved)
|
|
135
184
|
# allowed: handler is a registered duck; missing add_entry raises below
|
|
136
185
|
if handler.respond_to?(:add_entry)
|
|
137
186
|
handler.add_entry(archive_path, entry_name, source_path)
|
|
138
187
|
else
|
|
139
188
|
raise Omnizip::UnsupportedFormatError,
|
|
140
|
-
"Format #{
|
|
189
|
+
"Format #{resolved.inspect} does not support adding entries"
|
|
141
190
|
end
|
|
142
191
|
|
|
143
192
|
archive_path
|
|
@@ -151,11 +200,12 @@ module Omnizip
|
|
|
151
200
|
# @return [String] +archive_path+
|
|
152
201
|
def remove_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT)
|
|
153
202
|
require_archive!(archive_path)
|
|
154
|
-
|
|
203
|
+
resolved = resolve_archive_format(archive_path, format)
|
|
204
|
+
handler = Omnizip::ArchiveHandler.for(resolved)
|
|
155
205
|
# allowed: handler is a registered duck; missing remove_entry raises below
|
|
156
206
|
unless handler.respond_to?(:remove_entry)
|
|
157
207
|
raise Omnizip::UnsupportedFormatError,
|
|
158
|
-
"Format #{
|
|
208
|
+
"Format #{resolved.inspect} does not support removing entries"
|
|
159
209
|
end
|
|
160
210
|
|
|
161
211
|
handler.remove_entry(archive_path, entry_name)
|
|
@@ -186,6 +236,26 @@ module Omnizip
|
|
|
186
236
|
# honest behavior.
|
|
187
237
|
READ_ONLY_FORMAT_EXTENSIONS = [".rar", ".iso", ".cpio"].freeze
|
|
188
238
|
|
|
239
|
+
# Extensions whose format has a READ-ONLY handler: extraction and
|
|
240
|
+
# listing route to it, while creation keeps raising.
|
|
241
|
+
READ_ARCHIVE_FORMAT_EXTENSIONS = {
|
|
242
|
+
".rar" => :rar,
|
|
243
|
+
}.freeze
|
|
244
|
+
|
|
245
|
+
# Extension -> single-file decompressor (stream interface).
|
|
246
|
+
# The Gzip/Bzip2File/Xz classes take path or stream; use the
|
|
247
|
+
# ones exposing decompress_stream for symmetry.
|
|
248
|
+
# Xz exposes a path-based decompress rather than a stream one,
|
|
249
|
+
# so it is marked specially here.
|
|
250
|
+
DECOMPRESSORS = {
|
|
251
|
+
".gz" => Formats::Gzip,
|
|
252
|
+
".bz2" => Formats::Bzip2File,
|
|
253
|
+
".xz" => :xz,
|
|
254
|
+
".lzma" => Formats::LzmaAlone,
|
|
255
|
+
".lz" => Formats::Lzip,
|
|
256
|
+
".zst" => :zstandard,
|
|
257
|
+
}.freeze
|
|
258
|
+
|
|
189
259
|
SINGLE_FILE_COMPRESSORS = {
|
|
190
260
|
".gz" => lambda do |input, output, options|
|
|
191
261
|
Omnizip::Formats::Gzip.compress(input, output, options)
|
|
@@ -212,6 +282,14 @@ module Omnizip
|
|
|
212
282
|
end
|
|
213
283
|
end
|
|
214
284
|
end,
|
|
285
|
+
".zst" => lambda do |input, output, options|
|
|
286
|
+
::File.open(input, "rb") do |input_io|
|
|
287
|
+
::File.open(output, "wb") do |output_io|
|
|
288
|
+
Omnizip::Algorithms::Zstandard.new.compress(input_io, output_io,
|
|
289
|
+
options)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end,
|
|
215
293
|
}.freeze
|
|
216
294
|
|
|
217
295
|
# The single-file compressor matching the output extension, or nil.
|
|
@@ -223,12 +301,14 @@ module Omnizip
|
|
|
223
301
|
# Archive format for a path: an explicit +format+ wins unless it
|
|
224
302
|
# is the default; otherwise the extension routes. Known-but-
|
|
225
303
|
# unwritable extensions raise instead of receiving a mislabeled
|
|
226
|
-
# ZIP.
|
|
227
|
-
|
|
304
|
+
# ZIP. +writing:+ false (read operations) additionally routes
|
|
305
|
+
# extensions with read-only handlers.
|
|
306
|
+
def resolve_archive_format(path, format, writing: true)
|
|
228
307
|
return format unless format == DEFAULT_FORMAT
|
|
229
308
|
|
|
230
309
|
ext = ::File.extname(path).downcase
|
|
231
310
|
routed = ARCHIVE_FORMAT_EXTENSIONS[ext]
|
|
311
|
+
routed ||= READ_ARCHIVE_FORMAT_EXTENSIONS[ext] unless writing
|
|
232
312
|
return routed if routed
|
|
233
313
|
|
|
234
314
|
if READ_ONLY_FORMAT_EXTENSIONS.include?(ext)
|
|
@@ -121,6 +121,10 @@ module Omnizip
|
|
|
121
121
|
|
|
122
122
|
header_flags = read_vint(io)
|
|
123
123
|
|
|
124
|
+
# Optional ExtraAreaSize / DataSize precede the file fields
|
|
125
|
+
extra_size = header_flags.anybits?(RAR5_FLAG_EXTRA_AREA) ? read_vint(io) : 0
|
|
126
|
+
data_size = header_flags.anybits?(RAR5_FLAG_DATA_AREA) ? read_vint(io) : 0
|
|
127
|
+
|
|
124
128
|
# Read file header
|
|
125
129
|
file_flags = read_vint(io)
|
|
126
130
|
unpack_size = read_vint(io)
|
|
@@ -145,7 +149,7 @@ module Omnizip
|
|
|
145
149
|
|
|
146
150
|
# Set entry properties
|
|
147
151
|
entry.size = unpack_size
|
|
148
|
-
entry.compressed_size =
|
|
152
|
+
entry.compressed_size = data_size
|
|
149
153
|
entry.crc = crc
|
|
150
154
|
entry.host_os = host_os
|
|
151
155
|
entry.flags = file_flags
|
|
@@ -154,17 +158,9 @@ module Omnizip
|
|
|
154
158
|
entry.is_dir = file_flags.anybits?(RAR5_FLAG_IS_DIR)
|
|
155
159
|
entry.version = 5
|
|
156
160
|
|
|
157
|
-
#
|
|
158
|
-
if
|
|
159
|
-
|
|
160
|
-
io.read(extra_size) if extra_size.positive?
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# Read data area if present
|
|
164
|
-
if header_flags.anybits?(RAR5_FLAG_DATA_AREA)
|
|
165
|
-
data_size = read_vint(io)
|
|
166
|
-
io.read(data_size) if data_size.positive?
|
|
167
|
-
end
|
|
161
|
+
# Skip extra area, then compressed data
|
|
162
|
+
io.read(extra_size) if extra_size.positive?
|
|
163
|
+
io.read(data_size) if data_size.positive?
|
|
168
164
|
|
|
169
165
|
entry
|
|
170
166
|
end
|
|
@@ -58,7 +58,9 @@ module Omnizip
|
|
|
58
58
|
RAR5_FLAG_UNKNOWN_BLOCKS = 0x0004
|
|
59
59
|
RAR5_FLAG_DATA_INHERITED = 0x0008
|
|
60
60
|
RAR5_FLAG_CHILD_BLOCKS = 0x0010
|
|
61
|
-
|
|
61
|
+
# File-header-specific flags (FileFlags field): the directory
|
|
62
|
+
# bit is 0x0001 per the RAR5 spec.
|
|
63
|
+
RAR5_FLAG_IS_DIR = 0x0001
|
|
62
64
|
RAR5_FLAG_MULTI_VOLUME = 0x0001
|
|
63
65
|
|
|
64
66
|
# Compression methods
|
|
@@ -122,9 +122,11 @@ module Omnizip
|
|
|
122
122
|
#
|
|
123
123
|
# @param io [IO] Input stream
|
|
124
124
|
def parse_rar5_header(io)
|
|
125
|
-
# RAR5 uses variable-length integer encoding
|
|
125
|
+
# RAR5 uses variable-length integer encoding. Layout per the
|
|
126
|
+
# RAR 5.0 format: HeaderCRC32(4), Header size(vint), Header
|
|
127
|
+
# type(vint), Header flags(vint), then type-specific fields.
|
|
126
128
|
read_uint32(io)
|
|
127
|
-
read_vint(io)
|
|
129
|
+
read_vint(io) # header size (redundant: areas carry their own)
|
|
128
130
|
header_type = read_vint(io)
|
|
129
131
|
header_flags = read_vint(io)
|
|
130
132
|
|
|
@@ -133,12 +135,20 @@ module Omnizip
|
|
|
133
135
|
end
|
|
134
136
|
|
|
135
137
|
@flags = header_flags
|
|
136
|
-
@is_multi_volume = header_flags.anybits?(RAR5_FLAG_MULTI_VOLUME)
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
extra_size = 0
|
|
140
|
+
if header_flags.anybits?(RAR5_FLAG_EXTRA_AREA)
|
|
141
|
+
extra_size = read_vint(io)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Archive flags: 0x0001 volume, 0x0002 volume number
|
|
145
|
+
# present, 0x0004 solid, 0x0008 recovery, 0x0010 locked
|
|
146
|
+
archive_flags = read_vint(io)
|
|
147
|
+
@is_multi_volume = archive_flags.anybits?(0x0001)
|
|
148
|
+
@is_solid = archive_flags.anybits?(0x0004)
|
|
149
|
+
@is_locked = archive_flags.anybits?(0x0010)
|
|
150
|
+
read_vint(io) if archive_flags.anybits?(0x0002) # volume number
|
|
140
151
|
|
|
141
|
-
extra_size = read_vint(io)
|
|
142
152
|
io.read(extra_size) if extra_size.positive?
|
|
143
153
|
end
|
|
144
154
|
|
|
@@ -142,8 +142,12 @@ module Omnizip
|
|
|
142
142
|
# Detect recovery records
|
|
143
143
|
detect_recovery_records
|
|
144
144
|
|
|
145
|
-
# Parse entries
|
|
146
|
-
|
|
145
|
+
# Parse entries natively first (full metadata: sizes,
|
|
146
|
+
# directory flags); fall back to the external decompressor
|
|
147
|
+
# when native parsing yields nothing (e.g. encrypted
|
|
148
|
+
# headers).
|
|
149
|
+
parse_entries_from_header
|
|
150
|
+
parse_entries_with_decompressor if @entries.empty?
|
|
147
151
|
end
|
|
148
152
|
|
|
149
153
|
# Detect recovery records in archive
|
data/lib/omnizip/version.rb
CHANGED
data/lib/omnizip.rb
CHANGED
|
@@ -83,6 +83,7 @@ module Omnizip
|
|
|
83
83
|
autoload :Implementations, "omnizip/implementations"
|
|
84
84
|
autoload :ArchiveHandler, "omnizip/archive_handler"
|
|
85
85
|
autoload :ArchiveHandlers, "omnizip/archive_handlers"
|
|
86
|
+
autoload :Archive, "omnizip/archive"
|
|
86
87
|
autoload :Password, "omnizip/password"
|
|
87
88
|
autoload :Convenience, "omnizip/convenience"
|
|
88
89
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omnizip
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.37
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -319,8 +319,12 @@ files:
|
|
|
319
319
|
- lib/omnizip/algorithms/zstandard/sequences.rb
|
|
320
320
|
- lib/omnizip/algorithms/zstandard/sequences_encoder.rb
|
|
321
321
|
- lib/omnizip/algorithms/zstandard/xxhash.rb
|
|
322
|
+
- lib/omnizip/archive.rb
|
|
323
|
+
- lib/omnizip/archive/builder.rb
|
|
324
|
+
- lib/omnizip/archive/reader_session.rb
|
|
322
325
|
- lib/omnizip/archive_handler.rb
|
|
323
326
|
- lib/omnizip/archive_handlers.rb
|
|
327
|
+
- lib/omnizip/archive_handlers/rar_handler.rb
|
|
324
328
|
- lib/omnizip/archive_handlers/seven_zip_handler.rb
|
|
325
329
|
- lib/omnizip/archive_handlers/tar_handler.rb
|
|
326
330
|
- lib/omnizip/archive_handlers/zip_handler.rb
|