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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22e232b5a4b1ddbbece5ce1b6052ea90a9e76a120e8d246cf03ae0b568d4ba73
4
- data.tar.gz: af66a96e666d25e75196b68b317e2f34a37f2fa3e0da2d79d8f2d67d2a6d86a2
3
+ metadata.gz: f2d2eb9632ecf11d24224648408b2ca0bb56ed99b3a0ad6b077edbf84930ad52
4
+ data.tar.gz: 243668f7a95b77377911efcc686dec8f5d43a47b625aca6a30795b4d4d66a49a
5
5
  SHA512:
6
- metadata.gz: ed6a8cd7c2613912ed4c50e914cd1a4184253e6b66325741649d2790c9b0650546f6066354a28956b98e2f0a7c17853a56a0723308877a93043b55979270a2c1
7
- data.tar.gz: f6ad381f14bfd3a621584747bd71f544adacafc6d8241261f6bf7c553a92763ca0fef2165d4a65895f0ff54231b071e6f98903d87caf7125bfe229c372afc489
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
@@ -36,7 +36,7 @@ module Omnizip
36
36
  end
37
37
  end
38
38
 
39
- PASSWORD_FORMATS = [:seven_zip].freeze
39
+ PASSWORD_FORMATS = %i[seven_zip rar].freeze
40
40
  private_constant :PASSWORD_FORMATS
41
41
 
42
42
  class << self
@@ -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
- return if source_path.nil?
78
-
79
- @writer.add_file(source_path, name)
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 ? "#{entry.name}/" : entry.name
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
- # Determine format from extension or --format option
48
- format = opts[:format] || detect_format(output_file)
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
- if verbose
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
- # Format-agnostic options; Formats::Rar applies the
124
- # version-specific semantics
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
- # Strip the parent so the tree lands under its own
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
- rar.add_file(input)
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
- # Handle result based on whether recovery or multi-volume is enabled
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 files.size > 1
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: #{files.size} files (#{files.count do |f|
187
- f.end_with?('.rar')
188
- end} volumes)"
98
+ puts "Created: #{output_file}"
189
99
  end
190
100
  end
191
101
 
192
- def create_7z_archive(output_file, inputs, opts)
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
- if verbose
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
- "Creating archive: #{output_file}",
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
- "Algorithm: #{algorithm}, Level: #{level}, " \
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
@@ -80,7 +80,7 @@ module Omnizip
80
80
  end
81
81
  end
82
82
 
83
- ARCHIVE_EXTENSIONS = %w[.zip .7z .tar .rar .cpio .iso].freeze
83
+ ARCHIVE_EXTENSIONS = %w[.zip .7z .tar .rar .cpio .iso .rpm .xar .msi].freeze
84
84
 
85
85
  private
86
86
 
@@ -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 = [".iso", ".cpio"].freeze
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
- return unless @options[:recovery]&.to_i&.positive?
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
- entry.name = archive_path.chomp("/")
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnizip
4
- VERSION = "0.3.47"
4
+ VERSION = "0.3.49"
5
5
  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.47
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