omnizip 0.3.36 → 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 +59 -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 +85 -7
- 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 +5 -1
|
@@ -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.
|
|
@@ -188,6 +236,26 @@ module Omnizip
|
|
|
188
236
|
# honest behavior.
|
|
189
237
|
READ_ONLY_FORMAT_EXTENSIONS = [".rar", ".iso", ".cpio"].freeze
|
|
190
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
|
+
|
|
191
259
|
SINGLE_FILE_COMPRESSORS = {
|
|
192
260
|
".gz" => lambda do |input, output, options|
|
|
193
261
|
Omnizip::Formats::Gzip.compress(input, output, options)
|
|
@@ -214,6 +282,14 @@ module Omnizip
|
|
|
214
282
|
end
|
|
215
283
|
end
|
|
216
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,
|
|
217
293
|
}.freeze
|
|
218
294
|
|
|
219
295
|
# The single-file compressor matching the output extension, or nil.
|
|
@@ -225,12 +301,14 @@ module Omnizip
|
|
|
225
301
|
# Archive format for a path: an explicit +format+ wins unless it
|
|
226
302
|
# is the default; otherwise the extension routes. Known-but-
|
|
227
303
|
# unwritable extensions raise instead of receiving a mislabeled
|
|
228
|
-
# ZIP.
|
|
229
|
-
|
|
304
|
+
# ZIP. +writing:+ false (read operations) additionally routes
|
|
305
|
+
# extensions with read-only handlers.
|
|
306
|
+
def resolve_archive_format(path, format, writing: true)
|
|
230
307
|
return format unless format == DEFAULT_FORMAT
|
|
231
308
|
|
|
232
309
|
ext = ::File.extname(path).downcase
|
|
233
310
|
routed = ARCHIVE_FORMAT_EXTENSIONS[ext]
|
|
311
|
+
routed ||= READ_ARCHIVE_FORMAT_EXTENSIONS[ext] unless writing
|
|
234
312
|
return routed if routed
|
|
235
313
|
|
|
236
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,7 +1,7 @@
|
|
|
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.
|
|
@@ -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
|