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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -0
  3. data/docs/getting-started/basic-usage.adoc +14 -22
  4. data/docs/guides/advanced-features/encryption.adoc +16 -39
  5. data/docs/guides/advanced-features/index.adoc +16 -6
  6. data/docs/guides/advanced-features/parallel-processing.adoc +63 -165
  7. data/docs/guides/advanced-features/progress-tracking.adoc +65 -261
  8. data/docs/guides/advanced-features/streaming.adoc +43 -113
  9. data/docs/guides/compression-algorithms/bzip2.adoc +2 -1
  10. data/docs/guides/compression-algorithms/deflate.adoc +1 -7
  11. data/docs/guides/compression-algorithms/lzma.adoc +10 -14
  12. data/docs/guides/compression-algorithms/lzma2.adoc +34 -117
  13. data/docs/guides/compression-algorithms/ppmd.adoc +24 -27
  14. data/docs/guides/compression-algorithms/zstandard.adoc +85 -150
  15. data/docs/troubleshooting/index.adoc +7 -5
  16. data/lib/omnizip/algorithms/zstandard.rb +2 -3
  17. data/lib/omnizip/archive/builder.rb +48 -0
  18. data/lib/omnizip/archive/reader_session.rb +67 -0
  19. data/lib/omnizip/archive.rb +86 -0
  20. data/lib/omnizip/archive_handler.rb +1 -0
  21. data/lib/omnizip/archive_handlers/rar_handler.rb +50 -0
  22. data/lib/omnizip/archive_handlers/seven_zip_handler.rb +6 -2
  23. data/lib/omnizip/archive_handlers/tar_handler.rb +4 -0
  24. data/lib/omnizip/archive_handlers/zip_handler.rb +25 -2
  25. data/lib/omnizip/archive_handlers.rb +1 -0
  26. data/lib/omnizip/convenience.rb +91 -11
  27. data/lib/omnizip/formats/rar/block_parser.rb +8 -12
  28. data/lib/omnizip/formats/rar/constants.rb +3 -1
  29. data/lib/omnizip/formats/rar/header.rb +16 -6
  30. data/lib/omnizip/formats/rar/reader.rb +6 -2
  31. data/lib/omnizip/version.rb +1 -1
  32. data/lib/omnizip.rb +1 -0
  33. metadata +6 -2
@@ -49,29 +49,33 @@ Zstandard (zstd) is a modern, fast compression algorithm developed by Facebook t
49
49
 
50
50
  == Basic Usage
51
51
 
52
- === Create Zstandard Archive
52
+ === Compress a File (.zst)
53
53
 
54
54
  [source,ruby]
55
55
  ----
56
- # Create with Zstandard compression
57
- Omnizip::Archive.create('fast-backup.7z', format: :seven_zip) do |archive|
58
- archive.compression = :zstandard
59
- archive.level = 3 # Default level
60
- archive.add_directory('data/')
61
- end
56
+ # Writes a real Zstandard frame (validated by the system zstd CLI)
57
+ Omnizip.compress_file('logfile.log', 'logfile.log.zst')
58
+
59
+ # Compression level (generic 0-9 scale; values above 9 pass through
60
+ # as native zstd levels 1-22)
61
+ Omnizip.compress_file('data.bin', 'data.bin.zst', level: 9)
62
+
63
+ Omnizip.decompress_file('logfile.log.zst', 'logfile.log')
62
64
  ----
63
65
 
64
- === Quick Compression
66
+ === Zstandard Entries in ZIP
67
+
68
+ ZIP supports Zstandard as a per-entry method (method 93). Requires a
69
+ modern reader (e.g. 7-Zip 21+):
65
70
 
66
71
  [source,ruby]
67
72
  ----
68
- # Fast compression for temporary archives
69
- Omnizip.compress_file(
70
- 'logfile.log',
71
- 'logs.7z',
72
- compression: :zstandard,
73
- level: 1 # Fastest compression
74
- )
73
+ require 'omnizip/zip/output_stream'
74
+
75
+ Omnizip::Zip::OutputStream.open('fast.zip') do |zip|
76
+ zip.put_next_entry('data.bin', compression: :zstandard, level: 3)
77
+ zip.write(data)
78
+ end
75
79
  ----
76
80
 
77
81
  == Compression Levels
@@ -106,109 +110,63 @@ Zstandard offers levels 1-22 with fine-grained control:
106
110
  .Level Performance Example
107
111
  [source,ruby]
108
112
  ----
109
- # Compare different Zstandard levels
110
- [1, 3, 10, 19].each do |level|
113
+ # Compare different Zstandard levels (native zstd scale)
114
+ [1, 12, 19].each do |level|
111
115
  time = Benchmark.measure do
112
- Omnizip.compress_file('data.bin', "zstd-#{level}.7z",
113
- compression: :zstandard,
114
- level: level
115
- )
116
+ Omnizip.compress_file('data.bin', "zstd-#{level}.zst", level: level)
116
117
  end
117
118
 
118
- size = File.size("zstd-#{level}.7z")
119
+ size = File.size("zstd-#{level}.zst")
119
120
  puts "Level #{level}: #{size / 1024} KB in #{time.real.round(3)}s"
120
121
  end
121
-
122
- # Output:
123
- # Level 1: 145 KB in 0.052s (fastest)
124
- # Level 3: 128 KB in 0.089s (default)
125
- # Level 10: 105 KB in 0.425s (better compression)
126
- # Level 19: 98 KB in 2.156s (approaching LZMA quality)
127
122
  ----
128
123
 
129
124
  == Performance Comparison
130
125
 
131
- === Speed vs Deflate
132
-
133
- [cols="2,1,1,1"]
134
- |===
135
- |Operation |Deflate |Zstandard |Improvement
136
-
137
- |Compress 100 MB
138
- |10s
139
- |3s
140
- |3.3x faster
141
-
142
- |Decompress 100 MB
143
- |5s
144
- |1.2s
145
- |4.2x faster
146
-
147
- |Compression Ratio
148
- |68%
149
- |72%
150
- |4% better
151
- |===
152
-
153
- === Speed vs LZMA
154
-
155
- [cols="2,1,1,1"]
156
- |===
157
- |Operation |LZMA |Zstandard |Trade-off
158
-
159
- |Compress 100 MB
160
- |60s
161
- |3s
162
- |20x faster, slightly worse ratio
163
-
164
- |Decompress 100 MB
165
- |8s
166
- |1.2s
167
- |6.7x faster
168
-
169
- |Compression Ratio
170
- |78%
171
- |72%
172
- |LZMA ~6% better
173
- |===
126
+ No benchmark tables here: Omnizip's Zstandard is a pure Ruby
127
+ implementation, so absolute throughput differs substantially from
128
+ the C library. The algorithmic trade-offs hold — faster than LZMA
129
+ per level, better ratio than Deflate — but measure on your own
130
+ workloads.
174
131
 
175
132
  == Advanced Features
176
133
 
177
- === Dictionary Training
134
+ === Dictionary Compression
178
135
 
179
- Train custom dictionaries for specific data types:
136
+ A raw dictionary (shared prefix) dramatically improves ratios on
137
+ small, repetitive inputs. Dictionary *training* is not implemented;
138
+ prepare dictionary content yourself and load it with +from_raw+:
180
139
 
181
140
  [source,ruby]
182
141
  ----
183
- # Train dictionary on sample data
184
- training_data = Dir.glob('samples/*.json').map { |f| File.read(f) }
185
- dictionary = Omnizip::Zstandard.train_dictionary(training_data, size: 100.kilobytes)
186
-
187
- # Use trained dictionary for better compression
188
- Omnizip.compress_file('data.json', 'output.7z',
189
- compression: :zstandard,
190
- level: 5,
191
- dictionary: dictionary
142
+ dict = Omnizip::Algorithms::Zstandard::Dictionary.from_raw(
143
+ 42, File.binread('shared-samples.bin')
192
144
  )
193
145
 
194
- # Can improve compression 10-30% for similar data
146
+ zstd = Omnizip::Algorithms::Zstandard.new
147
+ File.open('data.json', 'rb') do |input|
148
+ File.open('data.json.zstd', 'wb') do |output|
149
+ zstd.compress_with_dict(input, output, dict)
150
+ end
151
+ end
152
+
153
+ File.open('data.json.zstd', 'rb') do |input|
154
+ File.open('data.restored', 'wb') do |output|
155
+ zstd.decompress_with_dict(input, output, dict)
156
+ end
157
+ end
195
158
  ----
196
159
 
197
- === Streaming with Zstandard
160
+ === Streaming
198
161
 
199
- Excellent streaming performance:
162
+ The codec is stream-oriented:
200
163
 
201
164
  [source,ruby]
202
165
  ----
203
- # Stream compression for real-time data
204
- Omnizip::OutputStream.open('stream.7z', format: :seven_zip) do |stream|
205
- stream.compression = :zstandard
206
- stream.level = 3 # Fast for real-time
207
-
208
- # Process log entries as they arrive
209
- log_stream.each do |log_entry|
210
- stream.put_next_entry("log_#{log_entry.id}.txt")
211
- stream.write(log_entry.content)
166
+ zstd = Omnizip::AlgorithmRegistry.get(:zstandard).new
167
+ File.open('input.log', 'rb') do |input|
168
+ File.open('input.log.zst', 'wb') do |output|
169
+ zstd.compress(input, output, level: 3)
212
170
  end
213
171
  end
214
172
  ----
@@ -222,15 +180,8 @@ Perfect for compressing logs quickly:
222
180
  [source,ruby]
223
181
  ----
224
182
  # Compress today's logs rapidly
225
- log_files = Dir.glob('/var/log/app/*.log')
226
-
227
- Omnizip.compress_files(
228
- log_files,
229
- "logs_#{Date.today}.7z",
230
- compression: :zstandard,
231
- level: 5 # Fast with good compression
232
- ) do |progress|
233
- puts "Compressing logs: #{progress.percentage}%"
183
+ Dir.glob('/var/log/app/*.log').each do |log|
184
+ Omnizip.compress_file(log, "#{log}.zst", level: 3)
234
185
  end
235
186
  ----
236
187
 
@@ -240,47 +191,37 @@ Compress API responses on-the-fly:
240
191
 
241
192
  [source,ruby]
242
193
  ----
194
+ require 'stringio'
195
+
243
196
  class ExportController < ApplicationController
244
197
  def download
245
- # Generate export data
246
- data = generate_export_data
247
-
248
- # Compress quickly with Zstandard
249
- compressed = Omnizip::Buffer.create(:seven_zip) do |archive|
250
- archive.compression = :zstandard
251
- archive.level = 3 # Fast compression
252
-
253
- data.each do |filename, content|
254
- archive.add(filename, content)
255
- end
256
- end
257
-
258
- send_data compressed.string,
259
- filename: 'export.7z',
260
- type: 'application/x-7z-compressed'
198
+ data = generate_export_data # a String
199
+
200
+ zstd = Omnizip::AlgorithmRegistry.get(:zstandard).new
201
+ output = StringIO.new
202
+ zstd.compress(StringIO.new(data), output, level: 3)
203
+
204
+ send_data output.string,
205
+ filename: 'export.zst',
206
+ type: 'application/zstd'
261
207
  end
262
208
  end
263
209
  ----
264
210
 
265
211
  === Database Dumps
266
212
 
267
- Compress database backups efficiently:
213
+ Stream a dump into a .zst frame:
268
214
 
269
215
  [source,ruby]
270
216
  ----
271
- # Stream database dump to compressed archive
217
+ # Stream database dump to compressed file
272
218
  require 'open3'
273
219
 
274
- Omnizip::OutputStream.open('db-backup.7z', format: :seven_zip) do |stream|
275
- stream.compression = :zstandard
276
- stream.level = 7
277
- stream.put_next_entry('database.sql')
278
-
220
+ zstd = Omnizip::AlgorithmRegistry.get(:zstandard).new
221
+ File.open('db-backup.sql.zst', 'wb') do |output|
279
222
  # Stream pg_dump output directly
280
- Open3.popen3('pg_dump mydb') do |stdin, stdout, stderr, wait_thr|
281
- while chunk = stdout.read(65536)
282
- stream.write(chunk)
283
- end
223
+ Open3.popen3('pg_dump mydb') do |_stdin, stdout, _stderr, _wait_thr|
224
+ zstd.compress(stdout, output, level: 7)
284
225
  end
285
226
  end
286
227
  ----
@@ -324,34 +265,28 @@ Zstandard is memory efficient:
324
265
 
325
266
  [source,bash]
326
267
  ----
327
- # Create Zstandard-compressed archive
328
- $ omnizip archive create backup.7z data/ \
329
- --compression zstandard \
330
- --level 5
331
-
332
- # Fast compression
333
- $ omnizip archive create logs.7z logs/ \
334
- --compression zstandard \
335
- --level 1 \
336
- --verbose
337
- Algorithm: Zstandard, Level: 1
338
- Compression speed: 185 MB/s
339
- Original: 500 MB
340
- Compressed: 145 MB (71% reduction)
341
- Time: 2.7s
268
+ $ omnizip compress data.bin data.bin.zst --algorithm zstandard
269
+
270
+ # The output is a standard Zstandard frame, readable by the system
271
+ # zstd CLI
342
272
  ----
343
273
 
344
274
  == Technical Details
345
275
 
346
276
  **Zstandard Features**:
347
277
 
348
- * **Fast Entropy Encoding**: Finite State Entropy (FSE) for speed
278
+ * **Fast Entropy Encoding**: Finite State Entropy (FSE)
349
279
  * **Adaptive Window**: Dynamically sized based on level
350
- * **Dictionary Compression**: Optional pre-trained dictionaries
351
- * **Multi-Threading**: Native parallel compression support
352
- * **Seekable Format**: Optional frame indexes for random access
280
+ * **Dictionary Compression**: Raw-dictionary priming (entropy-table
281
+ preloading from RFC dictionaries is not implemented)
282
+ * **Frames**: Interoperable with the reference zstd CLI (verified
283
+ both directions)
284
+
285
+ **Implementation**: Pure Ruby, part of Omnizip. No native binding.
353
286
 
354
- **Implementation**: Uses zstd-ruby gem (native binding)
287
+ **Not available**: Zstandard is not offered as a 7z coder method;
288
+ in 7z archives use LZMA2/PPMd/BZip2. Multi-threading is not
289
+ implemented (single-threaded encoder).
355
290
 
356
291
  == See Also
357
292
 
@@ -121,13 +121,15 @@ omnizip compress input.txt output.lzma --level 3
121
121
  omnizip compress input.txt output.zst --algorithm zstandard
122
122
  ----
123
123
 
124
- 3. Enable parallel processing:
124
+ 3. Run independent archives in parallel processes (see the
125
+ link:../guides/advanced-features/parallel-processing.html[Parallel Processing guide]):
125
126
  [source,ruby]
126
127
  ----
127
- Omnizip.compress_file('input.txt', 'output.lzma',
128
- parallel: true,
129
- threads: 4
130
- )
128
+ dirs = %w[data_a data_b]
129
+ pids = dirs.map do |dir|
130
+ Process.fork { Omnizip.compress_directory(dir, "#{dir}.7z") }
131
+ end
132
+ pids.each { |pid| Process.wait(pid) }
131
133
  ----
132
134
 
133
135
  ==== Slow Decompression
@@ -34,9 +34,8 @@ module Omnizip
34
34
  # - Dictionary support for small data compression
35
35
  # - Streaming and frame modes
36
36
  #
37
- # This implementation uses the zstd-ruby gem which provides Ruby bindings
38
- # to the official Zstandard C library. This ensures compatibility with
39
- # the reference implementation and excellent performance.
37
+ # Pure Ruby implementation of the Zstandard format, interoperable
38
+ # with the reference zstd CLI in both directions.
40
39
  #
41
40
  # Zstandard is particularly effective for:
42
41
  # - Real-time compression needs
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ module Archive
5
+ # Block argument of +Archive.create+. Translates the documented
6
+ # +add_file+/+add_directory+/+add_data+ vocabulary onto the
7
+ # handler proxy's +add(name, source_path)+ / +add_data+ calls.
8
+ class Builder
9
+ def initialize(proxy)
10
+ @proxy = proxy
11
+ end
12
+
13
+ # Add a file from disk, optionally under a different archive
14
+ # path: +add_file('report.pdf', 'reports/report.pdf')+.
15
+ def add_file(file_path, archive_path = nil)
16
+ @proxy.add(archive_path || ::File.basename(file_path), file_path)
17
+ end
18
+
19
+ # Add a directory tree under its own name:
20
+ # +add_directory('photos/')+ stores +photos/x.jpg+.
21
+ def add_directory(dir_path)
22
+ prefix = ::File.basename(::File.expand_path(dir_path))
23
+ @proxy.add("#{prefix}/")
24
+ walk(dir_path, prefix)
25
+ end
26
+
27
+ # Add in-memory content at +archive_path+.
28
+ def add_data(archive_path, data)
29
+ @proxy.add_data(archive_path, data)
30
+ end
31
+
32
+ private
33
+
34
+ def walk(dir, prefix)
35
+ ::Dir.children(dir).sort.each do |child|
36
+ full = ::File.join(dir, child)
37
+ name = "#{prefix}/#{child}"
38
+ if ::File.directory?(full)
39
+ @proxy.add("#{name}/")
40
+ walk(full, name)
41
+ else
42
+ @proxy.add(name, full)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Omnizip
6
+ module Archive
7
+ # Block argument (and return value) of +Archive.open+. Delegates
8
+ # per operation to the resolved ArchiveHandler, which opens and
9
+ # closes the underlying reader on every call — the session itself
10
+ # holds no resources.
11
+ class ReaderSession
12
+ def initialize(handler, path, options = {})
13
+ @handler = handler
14
+ @path = path
15
+ @options = options
16
+ end
17
+
18
+ # Archive metadata records (see Archive::Entry).
19
+ def entries
20
+ @handler.list(@path, details: true, **@options).map do |h|
21
+ Entry.new(name: h[:name], size: h[:size], directory: h[:directory],
22
+ mtime: h[:mtime] || h[:time])
23
+ end
24
+ end
25
+
26
+ def each_entry(&block)
27
+ entries.each(&block)
28
+ end
29
+
30
+ # Contents of a single entry, without extraction.
31
+ def read(entry_name)
32
+ @handler.read_entry(@path, entry_name, **@options)
33
+ end
34
+
35
+ # Extract one entry to an explicit destination path.
36
+ def extract(entry_name, dest_path)
37
+ entry = entries.find { |e| e.name == entry_name }
38
+ unless entry
39
+ raise Errno::ENOENT, "Entry not found: #{entry_name}"
40
+ end
41
+
42
+ if entry.directory?
43
+ ::FileUtils.mkdir_p(dest_path)
44
+ return dest_path
45
+ end
46
+
47
+ ::FileUtils.mkdir_p(::File.dirname(dest_path))
48
+ ::File.binwrite(dest_path, read(entry_name))
49
+ dest_path
50
+ end
51
+
52
+ def extract_all(output_dir)
53
+ @handler.extract_to(@path, output_dir, **@options)
54
+ end
55
+
56
+ # Extract every entry whose name matches +pattern+ under
57
+ # +output_dir+, preserving archive-relative paths.
58
+ def extract_matching(pattern, output_dir)
59
+ ::FileUtils.mkdir_p(output_dir)
60
+ entries.select do |e|
61
+ !e.directory? && ::File.fnmatch?(pattern, e.name,
62
+ ::File::FNM_PATHNAME)
63
+ end.map { |e| extract(e.name, ::File.join(output_dir, e.name)) }
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ # High-level block-style archive facade over the registered
5
+ # ArchiveHandlers. This is the API the guides document:
6
+ #
7
+ # Omnizip::Archive.create('backup.7z', format: :seven_zip,
8
+ # level: 9) do |archive|
9
+ # archive.add_file('report.pdf', 'reports/report.pdf')
10
+ # archive.add_directory('photos/')
11
+ # archive.add_data('notes/readme.txt', "generated\n")
12
+ # end
13
+ #
14
+ # Omnizip::Archive.open('backup.7z') do |archive|
15
+ # archive.entries.each { |e| puts "#{e.name} (#{e.size})" }
16
+ # archive.read('notes/readme.txt')
17
+ # archive.extract_all('output/')
18
+ # end
19
+ #
20
+ # The format is resolved from the output path's extension when
21
+ # +format:+ is not given explicitly, exactly like the convenience
22
+ # methods. Passwords are supported for 7z only (ZIP writing and
23
+ # reading have no encryption support); asking for one elsewhere
24
+ # raises rather than silently producing an unprotected archive.
25
+ module Archive
26
+ autoload :Builder, "omnizip/archive/builder"
27
+ autoload :ReaderSession, "omnizip/archive/reader_session"
28
+
29
+ # Metadata record yielded by ReaderSession#entries. The +size+
30
+ # member intentionally shadows Struct#size: the documented
31
+ # contract is entry size in bytes, not the member count.
32
+ # rubocop:disable-next Lint/StructNewOverride -- documented byte size
33
+ Entry = Struct.new(:name, :size, :directory, :mtime, keyword_init: true) do
34
+ def directory?
35
+ directory
36
+ end
37
+ end
38
+
39
+ PASSWORD_FORMATS = [:seven_zip].freeze
40
+ private_constant :PASSWORD_FORMATS
41
+
42
+ class << self
43
+ # Create an archive, yielding a Builder that translates the
44
+ # documented +add_file+/+add_directory+/+add_data+ calls onto
45
+ # the resolved handler. Returns the archive path.
46
+ def create(path, format: Convenience::DEFAULT_FORMAT, **options, &block)
47
+ resolved = Omnizip.resolve_archive_format(path, format)
48
+ check_password_support!(resolved, options[:password])
49
+
50
+ Omnizip::ArchiveHandler.for(resolved).create(path, **options) do |proxy|
51
+ block&.call(Builder.new(proxy))
52
+ end
53
+ path
54
+ end
55
+
56
+ # Open an archive for reading, yielding a ReaderSession. Without
57
+ # a block, returns the session. An explicit +password:+ wins
58
+ # over ENV['OMNIZIP_PASSWORD'].
59
+ def open(path, password: nil, **options, &block)
60
+ resolved = Omnizip.resolve_archive_format(
61
+ path, Convenience::DEFAULT_FORMAT, writing: false
62
+ )
63
+ pw = password || ENV.fetch("OMNIZIP_PASSWORD", nil)
64
+ check_password_support!(resolved, pw)
65
+
66
+ options[:password] = pw if pw
67
+ session = ReaderSession.new(
68
+ Omnizip::ArchiveHandler.for(resolved), path, options
69
+ )
70
+ return session unless block
71
+
72
+ yield session
73
+ end
74
+
75
+ private
76
+
77
+ def check_password_support!(resolved, password)
78
+ return unless password && !PASSWORD_FORMATS.include?(resolved)
79
+
80
+ raise Omnizip::UnsupportedFormatError,
81
+ "password is only supported for #{PASSWORD_FORMATS.join(', ')}; " \
82
+ "#{resolved.inspect} has no encryption support"
83
+ end
84
+ end
85
+ end
86
+ end
@@ -70,6 +70,7 @@ module Omnizip
70
70
  zip: -> { Omnizip::ArchiveHandlers::ZipHandler },
71
71
  tar: -> { Omnizip::ArchiveHandlers::TarHandler },
72
72
  seven_zip: -> { Omnizip::ArchiveHandlers::SevenZipHandler },
73
+ rar: -> { Omnizip::ArchiveHandlers::RarHandler },
73
74
  }.freeze
74
75
  private_constant :LAZY_LOAD_TRIGGERS
75
76
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+
5
+ module Omnizip
6
+ module ArchiveHandlers
7
+ # Read-only adapter for the RAR format. RAR compression is
8
+ # patented software (see link:README[RAR write support]); this
9
+ # handler exposes extraction/listing/reading only, so the
10
+ # convenience API and the Archive facade can operate on existing
11
+ # archives truthfully instead of raising on every operation.
12
+ class RarHandler
13
+ def create(_path, **_options)
14
+ raise Omnizip::UnsupportedFormatError,
15
+ "RAR archives cannot be created (patented format); " \
16
+ "Omnizip reads RAR archives only"
17
+ end
18
+
19
+ def extract_to(path, output_dir, password: nil, **_)
20
+ Omnizip::Formats::Rar::Decompressor.extract(path, output_dir,
21
+ password: password)
22
+ end
23
+
24
+ def list(path, details: false, **_)
25
+ reader = Omnizip::Formats::Rar::Reader.new(path).open
26
+ entries = reader.list_files
27
+ if details
28
+ entries.map do |e|
29
+ { name: e.name, size: e.size, directory: e.is_dir,
30
+ mtime: e.mtime }
31
+ end
32
+ else
33
+ entries.map(&:name)
34
+ end
35
+ end
36
+
37
+ def read_entry(path, entry_name, password: nil, **_)
38
+ Dir.mktmpdir("omnizip-rar-entry") do |dir|
39
+ dest = File.join(dir, "entry")
40
+ Omnizip::Formats::Rar::Decompressor.extract_entry(
41
+ path, entry_name, dest, password: password
42
+ )
43
+ File.binread(dest)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ Omnizip::ArchiveHandler.register(:rar, Omnizip::ArchiveHandlers::RarHandler.new)
@@ -8,8 +8,8 @@ module Omnizip
8
8
  # Creates a .7z archive. The yielded proxy only supports
9
9
  # +add(name, source_path)+ for files — directory entries are not
10
10
  # part of the single-file convenience flow.
11
- def create(path, &block)
12
- Omnizip::Formats::SevenZip.create(path) do |writer|
11
+ def create(path, **options, &block)
12
+ Omnizip::Formats::SevenZip.create(path, options) do |writer|
13
13
  block&.call(FileProxy.new(writer))
14
14
  end
15
15
  end
@@ -73,6 +73,10 @@ module Omnizip
73
73
 
74
74
  @writer.add_file(source_path, name)
75
75
  end
76
+
77
+ def add_data(name, data)
78
+ @writer.add_data(name, data)
79
+ end
76
80
  end
77
81
  end
78
82
  end
@@ -50,6 +50,10 @@ module Omnizip
50
50
 
51
51
  @writer.add(name, source_path)
52
52
  end
53
+
54
+ def add_data(name, data)
55
+ @writer.add_data(name, data)
56
+ end
53
57
  end
54
58
 
55
59
  def read_entry(path, entry_name)