omnizip 0.3.47 → 0.3.49
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 +38 -0
- data/lib/omnizip/archive.rb +1 -1
- data/lib/omnizip/archive_handler.rb +3 -0
- data/lib/omnizip/archive_handlers/ole_handler.rb +37 -0
- data/lib/omnizip/archive_handlers/rpm_handler.rb +45 -0
- data/lib/omnizip/archive_handlers/seven_zip_handler.rb +5 -7
- data/lib/omnizip/archive_handlers/xar_handler.rb +49 -0
- data/lib/omnizip/archive_handlers.rb +3 -0
- data/lib/omnizip/buffer/seven_zip_bridge.rb +5 -1
- data/lib/omnizip/commands/archive_create_command.rb +88 -211
- data/lib/omnizip/commands/decompress_command.rb +1 -1
- data/lib/omnizip/convenience.rb +4 -1
- data/lib/omnizip/formats/rar/writer.rb +3 -1
- data/lib/omnizip/formats/seven_zip/writer.rb +13 -1
- data/lib/omnizip/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f2d2eb9632ecf11d24224648408b2ca0bb56ed99b3a0ad6b077edbf84930ad52
|
|
4
|
+
data.tar.gz: 243668f7a95b77377911efcc686dec8f5d43a47b625aca6a30795b4d4d66a49a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa7a60a3b4fba564f7b76786969699a446f46e72a7610065113c5fdcb02404b4d7ec3e9b6430d2ab48550ad19061341af5d29286786e1f833bb5b8a246735434
|
|
7
|
+
data.tar.gz: d2f4375f10f9922ca466d9c154d2bfbd42ccb3a376e58be025299af1d3dafdbcc6d43175dd3fe9d76c87057d7f067e1ff01900968982b8ed193833e9f7819405
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.48] - 2026-08-31
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- `omnizip archive create` routes through the Archive facade: the
|
|
14
|
+
command's two hand-munged format branches collapse into one
|
|
15
|
+
`Archive.create` call — the handler registry picks each format's
|
|
16
|
+
writer and applies its option semantics, and the command only
|
|
17
|
+
translates CLI vocabulary (414 -> 290 lines). Verified for 7z
|
|
18
|
+
(7zz byte-identical), RAR5, RAR4, volume splitting, and verbose
|
|
19
|
+
output.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- The 7z handler proxy dropped the Builder's explicit directory
|
|
23
|
+
entries (derive-from-paths no-op), so CLI-created archives lost
|
|
24
|
+
the `dir/` entries the previous writer path emitted. The proxy
|
|
25
|
+
maps name-only adds to `add_directory_entry`, which preserves the
|
|
26
|
+
trailing slash; the Buffer read proxy no longer double-slashes
|
|
27
|
+
such names.
|
|
28
|
+
- The facade denied RAR passwords (`PASSWORD_FORMATS` listed 7z
|
|
29
|
+
only) while the RAR5 writer produces unrar-decryptable encrypted
|
|
30
|
+
archives — `:rar` added (verified with `unrar -p`).
|
|
31
|
+
- RAR4 recovery guard crashed on explicit boolean options:
|
|
32
|
+
`false&.to_i` still calls `to_i` (safe navigation guards nil
|
|
33
|
+
only) — Numeric check now.
|
|
34
|
+
- RAR interop specs gate on the unrar command (through its resolved
|
|
35
|
+
path), not the gem wrapper: CI runners can carry the gem without
|
|
36
|
+
the binary on PATH.
|
|
37
|
+
|
|
38
|
+
## [0.3.47] - 2026-08-31
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
- `.rar` is a writable extension route: the RAR handler gained
|
|
42
|
+
create in 0.3.46, but the route table still classified `.rar` as
|
|
43
|
+
read-only — `Archive.create('a.rar')` without an explicit format
|
|
44
|
+
kept raising "cannot be written by the convenience API" while
|
|
45
|
+
`Archive.create(fmt: :rar)` worked. The route table matches the
|
|
46
|
+
handler now; `.cpio`/`.iso` stay read-only.
|
|
47
|
+
|
|
10
48
|
## [0.3.46] - 2026-08-31
|
|
11
49
|
|
|
12
50
|
### Added
|
data/lib/omnizip/archive.rb
CHANGED
|
@@ -68,6 +68,9 @@ module Omnizip
|
|
|
68
68
|
# file, which then self-registers at the bottom of the file.
|
|
69
69
|
LAZY_LOAD_TRIGGERS = {
|
|
70
70
|
zip: -> { Omnizip::ArchiveHandlers::ZipHandler },
|
|
71
|
+
rpm: -> { Omnizip::ArchiveHandlers::RpmHandler },
|
|
72
|
+
xar: -> { Omnizip::ArchiveHandlers::XarHandler },
|
|
73
|
+
ole: -> { Omnizip::ArchiveHandlers::OleHandler },
|
|
71
74
|
tar: -> { Omnizip::ArchiveHandlers::TarHandler },
|
|
72
75
|
seven_zip: -> { Omnizip::ArchiveHandlers::SevenZipHandler },
|
|
73
76
|
rar: -> { Omnizip::ArchiveHandlers::RarHandler },
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string: true
|
|
2
|
+
|
|
3
|
+
module Omnizip
|
|
4
|
+
module ArchiveHandlers
|
|
5
|
+
# Read-only adapter for OLE compound documents ( MSI installers,
|
|
6
|
+
# legacy Office binaries): stream listing, reading, and
|
|
7
|
+
# extraction. Stream names are listed exactly as stored — MSI
|
|
8
|
+
# installers mangle their table-stream names in a
|
|
9
|
+
# MSI-domain encoding this handler does not decode.
|
|
10
|
+
class OleHandler
|
|
11
|
+
def create(_path, **_options)
|
|
12
|
+
raise Omnizip::UnsupportedFormatError,
|
|
13
|
+
"compound documents cannot be created through the " \
|
|
14
|
+
"convenience API; use Omnizip::Formats::Ole directly"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def extract_to(path, output_dir, **_)
|
|
18
|
+
Omnizip::Formats::Ole.extract(path, output_dir)
|
|
19
|
+
Dir.glob(File.join(output_dir, "**", "*"))
|
|
20
|
+
.select { |f| File.file?(f) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def list(path, details: false, **_)
|
|
24
|
+
entries = Omnizip::Formats::Ole.list(path)
|
|
25
|
+
return entries unless details
|
|
26
|
+
|
|
27
|
+
entries.map { |name| { name: name, size: nil, directory: false } }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def read_entry(path, entry_name, **_)
|
|
31
|
+
Omnizip::Formats::Ole.read(path, entry_name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Omnizip::ArchiveHandler.register(:ole, Omnizip::ArchiveHandlers::OleHandler.new)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string: true
|
|
2
|
+
|
|
3
|
+
require "tmpdir"
|
|
4
|
+
|
|
5
|
+
module Omnizip
|
|
6
|
+
module ArchiveHandlers
|
|
7
|
+
# Read-only adapter for the RPM package format: extraction and
|
|
8
|
+
# listing over the CPIO payload; package metadata lives in
|
|
9
|
+
# Formats::Rpm.info. RPM writing exists at the format level but
|
|
10
|
+
# is not exposed through the convenience archive API.
|
|
11
|
+
class RpmHandler
|
|
12
|
+
def create(_path, **_options)
|
|
13
|
+
raise Omnizip::UnsupportedFormatError,
|
|
14
|
+
"rpm archives cannot be created through the convenience " \
|
|
15
|
+
"API; use Omnizip::Formats::Rpm::Writer directly"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def extract_to(path, output_dir, **_)
|
|
19
|
+
Omnizip::Formats::Rpm.extract(path, output_dir)
|
|
20
|
+
Dir.glob(File.join(output_dir, "**", "*"))
|
|
21
|
+
.select { |f| File.file?(f) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def list(path, details: false, **_)
|
|
25
|
+
entries = Omnizip::Formats::Rpm.list(path)
|
|
26
|
+
return entries unless details
|
|
27
|
+
|
|
28
|
+
entries.map { |name| { name: name, size: nil, directory: false } }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def read_entry(path, entry_name, **_)
|
|
32
|
+
Dir.mktmpdir("omnizip-rpm-entry") do |dir|
|
|
33
|
+
Omnizip::Formats::Rpm.extract(path, dir)
|
|
34
|
+
dest = File.join(dir, entry_name)
|
|
35
|
+
raise Errno::ENOENT, "Entry not found: #{entry_name}" unless
|
|
36
|
+
File.exist?(dest)
|
|
37
|
+
|
|
38
|
+
File.binread(dest)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
Omnizip::ArchiveHandler.register(:rpm, Omnizip::ArchiveHandlers::RpmHandler.new)
|
|
@@ -69,14 +69,12 @@ module Omnizip
|
|
|
69
69
|
@writer = writer
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
# Directory entries are a no-op: 7z archives derive the
|
|
73
|
-
# directory tree from the files' archive paths ("sub/f.txt"
|
|
74
|
-
# implies "sub/"), and the writer/reader handle slashed
|
|
75
|
-
# paths (verified against 7zz).
|
|
76
72
|
def add(name, source_path = nil)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
if source_path
|
|
74
|
+
@writer.add_file(source_path, name)
|
|
75
|
+
else
|
|
76
|
+
@writer.add_directory_entry(name)
|
|
77
|
+
end
|
|
80
78
|
end
|
|
81
79
|
|
|
82
80
|
def add_data(name, data)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string: true
|
|
2
|
+
|
|
3
|
+
require "tmpdir"
|
|
4
|
+
|
|
5
|
+
module Omnizip
|
|
6
|
+
module ArchiveHandlers
|
|
7
|
+
# Read-only adapter for the XAR archive format: extraction and
|
|
8
|
+
# listing over the XML TOC. XAR writing exists at the format
|
|
9
|
+
# level but is not exposed through the convenience archive API.
|
|
10
|
+
class XarHandler
|
|
11
|
+
def create(_path, **_options)
|
|
12
|
+
raise Omnizip::UnsupportedFormatError,
|
|
13
|
+
"xar archives cannot be created through the convenience " \
|
|
14
|
+
"API; use Omnizip::Formats::Xar.create directly"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def extract_to(path, output_dir, **_)
|
|
18
|
+
Omnizip::Formats::Xar.extract(path, output_dir)
|
|
19
|
+
Dir.glob(File.join(output_dir, "**", "*"))
|
|
20
|
+
.select { |f| File.file?(f) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def list(path, details: false, **_)
|
|
24
|
+
entries = Omnizip::Formats::Xar.list(path)
|
|
25
|
+
if details
|
|
26
|
+
entries.map do |e|
|
|
27
|
+
{ name: e.name, size: e.size,
|
|
28
|
+
directory: false }
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
entries.map(&:name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def read_entry(path, entry_name, **_)
|
|
36
|
+
Dir.mktmpdir("omnizip-xar-entry") do |dir|
|
|
37
|
+
Omnizip::Formats::Xar.extract(path, dir)
|
|
38
|
+
dest = File.join(dir, entry_name)
|
|
39
|
+
raise Errno::ENOENT, "Entry not found: #{entry_name}" unless
|
|
40
|
+
File.exist?(dest)
|
|
41
|
+
|
|
42
|
+
File.binread(dest)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Omnizip::ArchiveHandler.register(:xar, Omnizip::ArchiveHandlers::XarHandler.new)
|
|
@@ -10,6 +10,9 @@ module Omnizip
|
|
|
10
10
|
autoload :SevenZipHandler, "omnizip/archive_handlers/seven_zip_handler"
|
|
11
11
|
autoload :RarHandler, "omnizip/archive_handlers/rar_handler"
|
|
12
12
|
autoload :CpioHandler, "omnizip/archive_handlers/cpio_handler"
|
|
13
|
+
autoload :RpmHandler, "omnizip/archive_handlers/rpm_handler"
|
|
14
|
+
autoload :XarHandler, "omnizip/archive_handlers/xar_handler"
|
|
15
|
+
autoload :OleHandler, "omnizip/archive_handlers/ole_handler"
|
|
13
16
|
autoload :IsoHandler, "omnizip/archive_handlers/iso_handler"
|
|
14
17
|
end
|
|
15
18
|
end
|
|
@@ -123,7 +123,11 @@ module Omnizip
|
|
|
123
123
|
@entry = entry
|
|
124
124
|
@reader = reader
|
|
125
125
|
@tmp = tmp
|
|
126
|
-
@name = entry.is_dir
|
|
126
|
+
@name = if entry.is_dir
|
|
127
|
+
entry.name.end_with?("/") ? entry.name : "#{entry.name}/"
|
|
128
|
+
else
|
|
129
|
+
entry.name
|
|
130
|
+
end
|
|
127
131
|
@size = entry.size
|
|
128
132
|
end
|
|
129
133
|
|
|
@@ -34,6 +34,15 @@ module Omnizip
|
|
|
34
34
|
# @param output_file [String] Path to output archive
|
|
35
35
|
# @param inputs [Array<String>] Paths to files/directories to archive
|
|
36
36
|
# @return [void]
|
|
37
|
+
# Execute the archive create command.
|
|
38
|
+
#
|
|
39
|
+
# Routes through the Archive facade: the handler registry
|
|
40
|
+
# picks each format's writer and applies its option semantics,
|
|
41
|
+
# so this command only translates CLI vocabulary and reports.
|
|
42
|
+
#
|
|
43
|
+
# @param output_file [String] Path to output archive
|
|
44
|
+
# @param inputs [Array<String>] Paths to files/directories to archive
|
|
45
|
+
# @return [void]
|
|
37
46
|
def run(output_file, *inputs)
|
|
38
47
|
validate_inputs(output_file, inputs)
|
|
39
48
|
|
|
@@ -44,220 +53,126 @@ module Omnizip
|
|
|
44
53
|
opts = apply_profile(first_file, opts)
|
|
45
54
|
end
|
|
46
55
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if format == "rar"
|
|
51
|
-
create_rar_archive(output_file, inputs, opts)
|
|
52
|
-
else
|
|
53
|
-
create_7z_archive(output_file, inputs, opts)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
def detect_format(filename)
|
|
60
|
-
case File.extname(filename).downcase
|
|
61
|
-
when ".rar" then "rar"
|
|
62
|
-
else "7z"
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def create_rar_archive(output_file, inputs, opts)
|
|
67
|
-
version = opts[:rar_version] || 5
|
|
68
|
-
compression = (opts[:rar_compression] || "store").to_sym
|
|
69
|
-
level = opts[:level] || 3
|
|
70
|
-
include_mtime = opts[:include_mtime] || false
|
|
71
|
-
include_crc32 = opts[:include_crc32] || false
|
|
72
|
-
solid = opts[:solid] || false
|
|
73
|
-
multi_volume = opts[:multi_volume] || false
|
|
74
|
-
volume_size = opts[:volume_size]
|
|
75
|
-
volume_naming = opts[:volume_naming] || "part"
|
|
76
|
-
password = opts[:password]
|
|
77
|
-
kdf_iterations = opts[:kdf_iterations] || 262_144
|
|
78
|
-
recovery = opts[:recovery] || false
|
|
79
|
-
recovery_percent = opts[:recovery_percent] || 5
|
|
56
|
+
format = (opts[:format] || detect_format(output_file)).to_sym
|
|
57
|
+
handler_opts = handler_options(format, opts)
|
|
80
58
|
verbose = opts[:verbose] || false
|
|
81
59
|
|
|
82
|
-
|
|
83
|
-
CliOutputFormatter.verbose_puts(
|
|
84
|
-
"Creating RAR#{version} archive: #{output_file}",
|
|
85
|
-
true,
|
|
86
|
-
)
|
|
87
|
-
CliOutputFormatter.verbose_puts(
|
|
88
|
-
"Compression: #{compression}, Level: #{level}",
|
|
89
|
-
true,
|
|
90
|
-
)
|
|
91
|
-
CliOutputFormatter.verbose_puts(
|
|
92
|
-
"Include mtime: #{include_mtime}, Include CRC32: #{include_crc32}",
|
|
93
|
-
true,
|
|
94
|
-
)
|
|
95
|
-
if solid
|
|
96
|
-
CliOutputFormatter.verbose_puts(
|
|
97
|
-
"Solid compression: enabled",
|
|
98
|
-
true,
|
|
99
|
-
)
|
|
100
|
-
end
|
|
101
|
-
if multi_volume && volume_size
|
|
102
|
-
CliOutputFormatter.verbose_puts(
|
|
103
|
-
"Multi-volume: enabled (size: #{volume_size}, naming: #{volume_naming})",
|
|
104
|
-
true,
|
|
105
|
-
)
|
|
106
|
-
end
|
|
107
|
-
if password
|
|
108
|
-
CliOutputFormatter.verbose_puts(
|
|
109
|
-
"Encryption: enabled (AES-256-CBC, KDF iterations: #{kdf_iterations})",
|
|
110
|
-
true,
|
|
111
|
-
)
|
|
112
|
-
end
|
|
113
|
-
if recovery
|
|
114
|
-
CliOutputFormatter.verbose_puts(
|
|
115
|
-
"PAR2 recovery: enabled (redundancy: #{recovery_percent}%)",
|
|
116
|
-
true,
|
|
117
|
-
)
|
|
118
|
-
end
|
|
119
|
-
end
|
|
60
|
+
announce(output_file, format, handler_opts, opts, verbose)
|
|
120
61
|
|
|
121
62
|
start_time = Time.now
|
|
122
63
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
writer_opts = {
|
|
126
|
-
version: version,
|
|
127
|
-
compression: compression,
|
|
128
|
-
level: level,
|
|
129
|
-
include_mtime: include_mtime,
|
|
130
|
-
include_crc32: include_crc32,
|
|
131
|
-
solid: solid,
|
|
132
|
-
multi_volume: multi_volume,
|
|
133
|
-
volume_size: volume_size,
|
|
134
|
-
volume_naming: volume_naming,
|
|
135
|
-
password: password,
|
|
136
|
-
kdf_iterations: kdf_iterations,
|
|
137
|
-
recovery: recovery,
|
|
138
|
-
recovery_percent: recovery_percent,
|
|
139
|
-
}.compact
|
|
140
|
-
|
|
141
|
-
result_files = Omnizip::Formats::Rar.create(output_file,
|
|
142
|
-
writer_opts) do |rar|
|
|
64
|
+
Omnizip::Archive.create(output_file, format: format,
|
|
65
|
+
**handler_opts) do |archive|
|
|
143
66
|
inputs.each do |input|
|
|
144
67
|
if File.directory?(input)
|
|
145
68
|
CliOutputFormatter.verbose_puts(
|
|
146
69
|
"Adding directory: #{input}",
|
|
147
70
|
verbose,
|
|
148
71
|
)
|
|
149
|
-
|
|
150
|
-
# name, matching the .7z creation behavior
|
|
151
|
-
rar.add_directory(input, File.dirname(input))
|
|
72
|
+
archive.add_directory(input)
|
|
152
73
|
else
|
|
153
74
|
CliOutputFormatter.verbose_puts(
|
|
154
75
|
"Adding file: #{input}",
|
|
155
76
|
verbose,
|
|
156
77
|
)
|
|
157
|
-
|
|
78
|
+
archive.add_file(input)
|
|
158
79
|
end
|
|
159
80
|
end
|
|
160
81
|
end
|
|
161
82
|
|
|
162
83
|
elapsed = Time.now - start_time
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
files = result_files.is_a?(Array) ? result_files : [result_files]
|
|
166
|
-
files.find { |f| f.end_with?(".rar") } || files.first
|
|
167
|
-
archive_size = files.sum { |f| File.size(f) }
|
|
84
|
+
archive_size = calculate_archive_size(output_file,
|
|
85
|
+
handler_opts[:volume_size])
|
|
168
86
|
|
|
169
87
|
if verbose
|
|
170
88
|
puts ""
|
|
171
89
|
puts "Archive created successfully"
|
|
172
|
-
if
|
|
173
|
-
puts "Files created: #{files.size}"
|
|
174
|
-
puts " Archive volumes: #{files.count { |f| f.end_with?('.rar') }}"
|
|
175
|
-
if recovery
|
|
176
|
-
puts " PAR2 files: #{files.count { |f| f.include?('.par2') }}"
|
|
177
|
-
end
|
|
90
|
+
if handler_opts[:volume_size]
|
|
178
91
|
puts "Total size: #{format_bytes(archive_size)}"
|
|
92
|
+
puts "Volumes: #{count_volumes(output_file)}"
|
|
179
93
|
else
|
|
180
94
|
puts "Archive size: #{format_bytes(archive_size)}"
|
|
181
95
|
end
|
|
182
96
|
puts "Time elapsed: #{elapsed.round(2)}s"
|
|
183
|
-
elsif files.size == 1
|
|
184
|
-
puts "Created: #{files.first}"
|
|
185
97
|
else
|
|
186
|
-
puts "Created: #{
|
|
187
|
-
f.end_with?('.rar')
|
|
188
|
-
end} volumes)"
|
|
98
|
+
puts "Created: #{output_file}"
|
|
189
99
|
end
|
|
190
100
|
end
|
|
191
101
|
|
|
192
|
-
|
|
193
|
-
algorithm = (opts[:algorithm] || "lzma2").to_sym
|
|
194
|
-
level = opts[:level] || 5
|
|
195
|
-
solid = opts.fetch(:solid, true)
|
|
196
|
-
verbose = opts[:verbose] || false
|
|
197
|
-
filters = parse_filters(opts[:filters])
|
|
198
|
-
volume_size = parse_volume_size(opts[:volume_size])
|
|
199
|
-
password = opts[:password]
|
|
200
|
-
encrypt_headers = opts[:encrypt_headers] || false
|
|
201
|
-
preserve_ntfs_streams = opts[:preserve_ntfs_streams] || false
|
|
102
|
+
private
|
|
202
103
|
|
|
203
|
-
|
|
104
|
+
def detect_format(filename)
|
|
105
|
+
case File.extname(filename).downcase
|
|
106
|
+
when ".rar" then :rar
|
|
107
|
+
else :seven_zip
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Translate the format-agnostic CLI vocabulary into the option
|
|
112
|
+
# keywords each format's writer understands; nil values drop
|
|
113
|
+
def handler_options(format, opts)
|
|
114
|
+
case format
|
|
115
|
+
when :rar
|
|
116
|
+
rar_options(opts)
|
|
117
|
+
else
|
|
118
|
+
seven_zip_options(opts)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def rar_options(opts)
|
|
123
|
+
{
|
|
124
|
+
version: opts[:rar_version],
|
|
125
|
+
compression: opts[:rar_compression]&.to_sym,
|
|
126
|
+
level: opts[:level],
|
|
127
|
+
include_mtime: opts[:include_mtime],
|
|
128
|
+
include_crc32: opts[:include_crc32],
|
|
129
|
+
solid: opts[:solid],
|
|
130
|
+
multi_volume: opts[:multi_volume],
|
|
131
|
+
volume_size: opts[:volume_size],
|
|
132
|
+
volume_naming: opts[:volume_naming],
|
|
133
|
+
password: opts[:password],
|
|
134
|
+
kdf_iterations: opts[:kdf_iterations],
|
|
135
|
+
recovery: opts[:recovery],
|
|
136
|
+
recovery_percent: opts[:recovery_percent],
|
|
137
|
+
}.compact
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def seven_zip_options(opts)
|
|
141
|
+
{
|
|
142
|
+
algorithm: (opts[:algorithm] || "lzma2").to_sym,
|
|
143
|
+
level: opts[:level] || 5,
|
|
144
|
+
solid: opts.fetch(:solid, true),
|
|
145
|
+
filters: parse_filters(opts[:filters]),
|
|
146
|
+
volume_size: parse_volume_size(opts[:volume_size]),
|
|
147
|
+
password: opts[:password],
|
|
148
|
+
encrypt_headers: opts[:encrypt_headers] || false,
|
|
149
|
+
}.compact
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def announce(output_file, format, handler_opts, _opts, verbose)
|
|
153
|
+
return unless verbose
|
|
154
|
+
|
|
155
|
+
CliOutputFormatter.verbose_puts(
|
|
156
|
+
"Creating archive: #{output_file}",
|
|
157
|
+
true,
|
|
158
|
+
)
|
|
159
|
+
CliOutputFormatter.verbose_puts(
|
|
160
|
+
"Format: #{format == :rar ? "RAR#{handler_opts[:version] || 5}" : '7z'}, " \
|
|
161
|
+
"Algorithm: #{handler_opts[:algorithm] || handler_opts[:compression]}, " \
|
|
162
|
+
"Level: #{handler_opts[:level]}, Solid: #{handler_opts[:solid]}",
|
|
163
|
+
true,
|
|
164
|
+
)
|
|
165
|
+
if handler_opts[:volume_size]
|
|
204
166
|
CliOutputFormatter.verbose_puts(
|
|
205
|
-
"
|
|
167
|
+
"Volume size: #{format_bytes(handler_opts[:volume_size])}",
|
|
206
168
|
true,
|
|
207
169
|
)
|
|
170
|
+
end
|
|
171
|
+
if handler_opts[:password]
|
|
208
172
|
CliOutputFormatter.verbose_puts(
|
|
209
|
-
"
|
|
210
|
-
"Solid: #{solid}",
|
|
173
|
+
"Encryption: enabled",
|
|
211
174
|
true,
|
|
212
175
|
)
|
|
213
|
-
if volume_size
|
|
214
|
-
CliOutputFormatter.verbose_puts(
|
|
215
|
-
"Volume size: #{format_bytes(volume_size)}",
|
|
216
|
-
true,
|
|
217
|
-
)
|
|
218
|
-
end
|
|
219
|
-
unless filters.empty?
|
|
220
|
-
CliOutputFormatter.verbose_puts(
|
|
221
|
-
"Filters: #{filters.join(', ')}",
|
|
222
|
-
true,
|
|
223
|
-
)
|
|
224
|
-
end
|
|
225
|
-
if encrypt_headers
|
|
226
|
-
CliOutputFormatter.verbose_puts(
|
|
227
|
-
"Header encryption: enabled",
|
|
228
|
-
true,
|
|
229
|
-
)
|
|
230
|
-
end
|
|
231
|
-
if preserve_ntfs_streams && Omnizip::Platform.supports_ntfs_streams?
|
|
232
|
-
CliOutputFormatter.verbose_puts(
|
|
233
|
-
"NTFS streams: preserving",
|
|
234
|
-
true,
|
|
235
|
-
)
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
start_time = Time.now
|
|
240
|
-
|
|
241
|
-
create_7z_archive_writer(output_file, inputs, algorithm, level, solid,
|
|
242
|
-
filters, volume_size, password, encrypt_headers,
|
|
243
|
-
preserve_ntfs_streams, verbose)
|
|
244
|
-
|
|
245
|
-
elapsed = Time.now - start_time
|
|
246
|
-
|
|
247
|
-
archive_size = calculate_archive_size(output_file, volume_size)
|
|
248
|
-
|
|
249
|
-
if verbose
|
|
250
|
-
puts ""
|
|
251
|
-
puts "Archive created successfully"
|
|
252
|
-
if volume_size
|
|
253
|
-
puts "Total size: #{format_bytes(archive_size)}"
|
|
254
|
-
puts "Volumes: #{count_volumes(output_file)}"
|
|
255
|
-
else
|
|
256
|
-
puts "Archive size: #{format_bytes(archive_size)}"
|
|
257
|
-
end
|
|
258
|
-
puts "Time elapsed: #{elapsed.round(2)}s"
|
|
259
|
-
else
|
|
260
|
-
puts "Created: #{output_file}"
|
|
261
176
|
end
|
|
262
177
|
end
|
|
263
178
|
|
|
@@ -289,44 +204,6 @@ module Omnizip
|
|
|
289
204
|
filter_str.split(",").map(&:strip).map(&:to_sym)
|
|
290
205
|
end
|
|
291
206
|
|
|
292
|
-
def create_7z_archive_writer(output_file, inputs, algorithm, level, solid,
|
|
293
|
-
filters, volume_size, password, encrypt_headers,
|
|
294
|
-
_preserve_ntfs_streams, verbose)
|
|
295
|
-
writer_opts = {
|
|
296
|
-
algorithm: algorithm,
|
|
297
|
-
level: level,
|
|
298
|
-
solid: solid,
|
|
299
|
-
filters: filters,
|
|
300
|
-
}
|
|
301
|
-
writer_opts[:volume_size] = volume_size if volume_size
|
|
302
|
-
writer_opts[:password] = password if password
|
|
303
|
-
writer_opts[:encrypt_headers] = encrypt_headers if encrypt_headers
|
|
304
|
-
|
|
305
|
-
writer = Formats::SevenZip::Writer.new(output_file, writer_opts)
|
|
306
|
-
|
|
307
|
-
inputs.each do |input|
|
|
308
|
-
if File.directory?(input)
|
|
309
|
-
CliOutputFormatter.verbose_puts(
|
|
310
|
-
"Adding directory: #{input}",
|
|
311
|
-
verbose,
|
|
312
|
-
)
|
|
313
|
-
writer.add_directory(input)
|
|
314
|
-
else
|
|
315
|
-
CliOutputFormatter.verbose_puts(
|
|
316
|
-
"Adding file: #{input}",
|
|
317
|
-
verbose,
|
|
318
|
-
)
|
|
319
|
-
writer.add_file(input)
|
|
320
|
-
end
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
CliOutputFormatter.verbose_puts("Writing archive...", verbose)
|
|
324
|
-
writer.write
|
|
325
|
-
rescue StandardError => e
|
|
326
|
-
raise Omnizip::CompressionError,
|
|
327
|
-
"Failed to create archive: #{e.message}"
|
|
328
|
-
end
|
|
329
|
-
|
|
330
207
|
def format_bytes(bytes)
|
|
331
208
|
units = %w[B KB MB GB]
|
|
332
209
|
size = bytes.to_f
|
data/lib/omnizip/convenience.rb
CHANGED
|
@@ -236,7 +236,7 @@ module Omnizip
|
|
|
236
236
|
# creation raises truthfully instead (the format-level writers
|
|
237
237
|
# exist for CPIO/ISO but are not part of the archive API; RAR
|
|
238
238
|
# moved to the writable routes when its handler gained create).
|
|
239
|
-
READ_ONLY_FORMAT_EXTENSIONS = [
|
|
239
|
+
READ_ONLY_FORMAT_EXTENSIONS = %w[.iso .cpio .rpm .xar .msi].freeze
|
|
240
240
|
|
|
241
241
|
# Extensions whose format has a READ-ONLY handler: extraction and
|
|
242
242
|
# listing route to it, while creation keeps raising.
|
|
@@ -244,6 +244,9 @@ module Omnizip
|
|
|
244
244
|
".rar" => :rar,
|
|
245
245
|
".cpio" => :cpio,
|
|
246
246
|
".iso" => :iso,
|
|
247
|
+
".rpm" => :rpm,
|
|
248
|
+
".xar" => :xar,
|
|
249
|
+
".msi" => :ole,
|
|
247
250
|
}.freeze
|
|
248
251
|
|
|
249
252
|
# Extension -> single-file decompressor (stream interface).
|
|
@@ -184,7 +184,9 @@ module Omnizip
|
|
|
184
184
|
warn "RAR4 writer ignores volume_size: not implemented " \
|
|
185
185
|
"(no corresponding flag is written)"
|
|
186
186
|
end
|
|
187
|
-
|
|
187
|
+
recovery = @options[:recovery]
|
|
188
|
+
return unless recovery.is_a?(Numeric) && recovery.positive?
|
|
189
|
+
return if recovery == true
|
|
188
190
|
|
|
189
191
|
warn "RAR4 writer ignores recovery: not implemented " \
|
|
190
192
|
"(no corresponding flag is written)"
|
|
@@ -37,6 +37,16 @@ module Omnizip
|
|
|
37
37
|
}.merge(options)
|
|
38
38
|
@collector = FileCollector.new
|
|
39
39
|
@entries = []
|
|
40
|
+
|
|
41
|
+
# Preprocessing filters are decode-side only: the write
|
|
42
|
+
# path does not apply them, so say so instead of silently
|
|
43
|
+
# ignoring the option
|
|
44
|
+
filters = @options[:filters]
|
|
45
|
+
return unless filters && !filters.empty?
|
|
46
|
+
|
|
47
|
+
warn "omnizip: the 7z writer does not apply filters " \
|
|
48
|
+
"(#{Array(filters).join(', ')}); writing unfiltered data. " \
|
|
49
|
+
"Filters apply when reading archives that contain them."
|
|
40
50
|
end
|
|
41
51
|
|
|
42
52
|
def add_file(file_path, archive_path = nil)
|
|
@@ -75,7 +85,9 @@ module Omnizip
|
|
|
75
85
|
# @return [self]
|
|
76
86
|
def add_directory_entry(archive_path)
|
|
77
87
|
entry = Models::FileEntry.new
|
|
78
|
-
|
|
88
|
+
# Trailing slash is preserved: explicit directory entries
|
|
89
|
+
# carry it ("testdir/"), matching the collector's tree walk
|
|
90
|
+
entry.name = archive_path
|
|
79
91
|
entry.source_path = nil
|
|
80
92
|
entry.size = 0
|
|
81
93
|
entry.is_dir = true
|
data/lib/omnizip/version.rb
CHANGED
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.49
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -327,9 +327,12 @@ files:
|
|
|
327
327
|
- lib/omnizip/archive_handlers.rb
|
|
328
328
|
- lib/omnizip/archive_handlers/cpio_handler.rb
|
|
329
329
|
- lib/omnizip/archive_handlers/iso_handler.rb
|
|
330
|
+
- lib/omnizip/archive_handlers/ole_handler.rb
|
|
330
331
|
- lib/omnizip/archive_handlers/rar_handler.rb
|
|
332
|
+
- lib/omnizip/archive_handlers/rpm_handler.rb
|
|
331
333
|
- lib/omnizip/archive_handlers/seven_zip_handler.rb
|
|
332
334
|
- lib/omnizip/archive_handlers/tar_handler.rb
|
|
335
|
+
- lib/omnizip/archive_handlers/xar_handler.rb
|
|
333
336
|
- lib/omnizip/archive_handlers/zip_handler.rb
|
|
334
337
|
- lib/omnizip/buffer.rb
|
|
335
338
|
- lib/omnizip/buffer/memory_archive.rb
|