omnizip 0.4.4 → 0.4.6
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 +23 -0
- data/lib/omnizip/algorithm.rb +15 -2
- data/lib/omnizip/algorithms/ppmd7.rb +33 -9
- data/lib/omnizip/algorithms/ppmd8.rb +36 -0
- data/lib/omnizip/algorithms/ppmd_base.rb +6 -2
- data/lib/omnizip/archive_handlers/zip_handler.rb +11 -0
- data/lib/omnizip/backends.rb +21 -1
- data/lib/omnizip/formats/zip/reader.rb +7 -2
- data/lib/omnizip/implementations/rust/archive.rb +93 -0
- data/lib/omnizip/implementations/rust/library.rb +31 -3
- data/lib/omnizip/implementations/rust.rb +1 -0
- data/lib/omnizip/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7dc6e02021b3915af5f65944e4d734bd3f95a5624b2a01c30100b6bbc36d5856
|
|
4
|
+
data.tar.gz: 998838c2bd3df94ced6617b9d199a0f2eab552e332d285bdc6819860fef50324
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f02b277ef180e076b5e0deb3847ed4cdf3f2a83abf829095779cdbfbdbc811891c8d9db9e000fb071c1d3bc164bd3cf4f031b8e19207b7d6e48232a21bf573dc
|
|
7
|
+
data.tar.gz: 4d20378f2973be45cc1cb691894b9c54916c1f5546d45a64a5143feaf8cabf0e053d8d604b8f30b6534d123ad12084021d9815b82945f78f5bfacd64f4daef96
|
data/CHANGELOG.md
CHANGED
|
@@ -7,12 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Fixed
|
|
11
|
+
- PPMd works for the first time: the pure-Ruby ppmd7 core cannot
|
|
12
|
+
decode its own output (root-only decoder, 100-symbol cap — proven)
|
|
13
|
+
and ppmd8 raised NotImplementedError; both now route through the
|
|
14
|
+
Rust tier with param-carrying codec names.
|
|
15
|
+
- The `StringIO#to_s` corruption trap: StringIO has no `#to_s`
|
|
16
|
+
(Kernel's returns the INSPECT string), so IO-coercion paths fed
|
|
17
|
+
`#<StringIO:0x…>` text to codecs. Fixed at the source
|
|
18
|
+
(`Algorithm.to_input_io`), together with the mirror
|
|
19
|
+
`prepare_output` bug that silently discarded StringIO outputs.
|
|
20
|
+
- ZIP entry extraction's raw deflate now rides the tier.
|
|
21
|
+
|
|
10
22
|
### Added
|
|
11
23
|
- Rust tier wiring for every accelerated codec: zstd, deflate,
|
|
12
24
|
deflate64 (both zlib-framed), gzip, xz, lzip, and lzma-alone now
|
|
13
25
|
route decode through the Rust backend when it loads, each pinned by
|
|
14
26
|
a cross-implementation differential. A Rust failure falls back to
|
|
15
27
|
the Ruby core — auto mode is never worse than pure Ruby.
|
|
28
|
+
- Archive-level acceleration: `Implementations::Rust::Archive` binds
|
|
29
|
+
the cdylib's whole-archive surface (open, list entries, read entry)
|
|
30
|
+
over every multi-entry format — zip, tar, cpio, 7z, rar3/4/5, iso,
|
|
31
|
+
rpm, xar, ole, encrypted archives included. `ZipHandler#list` and
|
|
32
|
+
`#read_entry` route through it first (auto mode; any Rust error or
|
|
33
|
+
a missing cdylib falls back to the pure-Ruby path). The tier's
|
|
34
|
+
600-mutant fuzz gate also caught a Rust decompression bomb
|
|
35
|
+
(truncated deflate streams decoded an endless phantom bit-stream;
|
|
36
|
+
fixed upstream in omnizip 0.21.108).
|
|
37
|
+
- `Library#bind` / `Library#take_buffer` seams the archive surface
|
|
38
|
+
rides (Fiddle symbol binding + copy-out-and-free).
|
|
16
39
|
|
|
17
40
|
### Fixed
|
|
18
41
|
- RAR recovery-record repair fails loudly instead of pretending:
|
data/lib/omnizip/algorithm.rb
CHANGED
|
@@ -35,6 +35,19 @@ module Omnizip
|
|
|
35
35
|
@filter = nil
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
class << self
|
|
39
|
+
# Coerce arbitrary input to a binary readable IO. The trap this
|
|
40
|
+
# replaces: StringIO has no #to_s (Kernel#to_s returns the
|
|
41
|
+
# INSPECT string), so StringIO.new(io.to_s) fed "#<StringIO:...>"
|
|
42
|
+
# text to the codec. Anything readable passes through; Strings
|
|
43
|
+
# wrap directly.
|
|
44
|
+
def to_input_io(data)
|
|
45
|
+
return data if data.respond_to?(:read) # allowed: IO-duck detection — StringIO must pass through (its #to_s is the INSPECT string)
|
|
46
|
+
|
|
47
|
+
::StringIO.new(data.to_s.b)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
38
51
|
# Set a preprocessing filter for this algorithm.
|
|
39
52
|
#
|
|
40
53
|
# The filter will be applied before compression and reversed after
|
|
@@ -95,7 +108,7 @@ module Omnizip
|
|
|
95
108
|
# @return [String] Compressed bytes
|
|
96
109
|
def compress(data, **options)
|
|
97
110
|
instance = new(options)
|
|
98
|
-
input =
|
|
111
|
+
input = to_input_io(data)
|
|
99
112
|
output = ::StringIO.new
|
|
100
113
|
output.set_encoding(Encoding::BINARY)
|
|
101
114
|
instance.compress(input, output, options)
|
|
@@ -110,7 +123,7 @@ module Omnizip
|
|
|
110
123
|
# @return [String] Decompressed bytes
|
|
111
124
|
def decompress(data, **options)
|
|
112
125
|
instance = new(options)
|
|
113
|
-
input =
|
|
126
|
+
input = to_input_io(data)
|
|
114
127
|
output = ::StringIO.new
|
|
115
128
|
output.set_encoding(Encoding::BINARY)
|
|
116
129
|
instance.decompress(input, output, options)
|
|
@@ -70,9 +70,22 @@ module Omnizip
|
|
|
70
70
|
def compress(input, output, options = {})
|
|
71
71
|
input = prepare_input(input)
|
|
72
72
|
output = prepare_output(output)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
data = input.read
|
|
74
|
+
order = options[:model_order] || PPMd7::Model::DEFAULT_ORDER
|
|
75
|
+
mem = options[:mem_size] || PPMd7::Model::DEFAULT_MEM_SIZE
|
|
76
|
+
|
|
77
|
+
# The tier is the authority: Rust's PPMd (the reference-grade
|
|
78
|
+
# port) frames order/size in-band; params travel in the codec
|
|
79
|
+
# name. The pure-Ruby core cannot round-trip its own output
|
|
80
|
+
# beyond ~100 bytes (its decoder reads root-only) — it stays
|
|
81
|
+
# only as the no-dylib fallback.
|
|
82
|
+
output.write(
|
|
83
|
+
Backends.compress("ppmd7:o#{order}:m#{mem}", data, order) do
|
|
84
|
+
encoder = PPMd7::Encoder.new(output, options)
|
|
85
|
+
encoder.encode_stream(StringIO.new(data))
|
|
86
|
+
nil
|
|
87
|
+
end,
|
|
88
|
+
)
|
|
76
89
|
end
|
|
77
90
|
|
|
78
91
|
# Decompress data using PPMd7
|
|
@@ -86,10 +99,16 @@ module Omnizip
|
|
|
86
99
|
def decompress(input, output, options = {})
|
|
87
100
|
input = prepare_input(input)
|
|
88
101
|
output = prepare_output(output)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
compressed = input.read
|
|
103
|
+
order = options[:model_order] || PPMd7::Model::DEFAULT_ORDER
|
|
104
|
+
mem = options[:mem_size] || PPMd7::Model::DEFAULT_MEM_SIZE
|
|
105
|
+
|
|
106
|
+
result = Backends.decompress(
|
|
107
|
+
"ppmd7:o#{order}:m#{mem}", compressed, Backends::UNKNOWN_LENGTH
|
|
108
|
+
) do
|
|
109
|
+
decoder = PPMd7::Decoder.new(StringIO.new(compressed), options)
|
|
110
|
+
decoder.decode_stream
|
|
111
|
+
end
|
|
93
112
|
output.write(result)
|
|
94
113
|
end
|
|
95
114
|
|
|
@@ -100,7 +119,9 @@ module Omnizip
|
|
|
100
119
|
# @param input [IO, String] Input data
|
|
101
120
|
# @return [IO] IO object ready for reading
|
|
102
121
|
def prepare_input(input)
|
|
103
|
-
|
|
122
|
+
# StringIO is not an ::IO, and its #to_s is Kernel's (the
|
|
123
|
+
# INSPECT string) — pass anything readable straight through.
|
|
124
|
+
return input if input.respond_to?(:read) # allowed: IO-duck detection — StringIO must pass through (its #to_s is the INSPECT string)
|
|
104
125
|
|
|
105
126
|
StringIO.new(input.to_s)
|
|
106
127
|
end
|
|
@@ -110,7 +131,10 @@ module Omnizip
|
|
|
110
131
|
# @param output [IO, String, nil] Output destination
|
|
111
132
|
# @return [IO] IO object ready for writing
|
|
112
133
|
def prepare_output(output)
|
|
113
|
-
|
|
134
|
+
# StringIO is NOT an ::IO subclass — the old check discarded
|
|
135
|
+
# it for a fresh buffer, silently dropping every class-level
|
|
136
|
+
# compress's output.
|
|
137
|
+
return output if output.is_a?(IO) || output.is_a?(StringIO)
|
|
114
138
|
|
|
115
139
|
StringIO.new(String.new(encoding: Encoding::BINARY))
|
|
116
140
|
end
|
|
@@ -32,6 +32,42 @@ module Omnizip
|
|
|
32
32
|
#
|
|
33
33
|
# This implementation follows the PPMd8 specification from 7-Zip.
|
|
34
34
|
class PPMd8 < PPMdBase
|
|
35
|
+
# The tier is the authority: Rust's PPMd8 is the real
|
|
36
|
+
# implementation (the pure-Ruby core raises NotImplementedError
|
|
37
|
+
# for both directions — it never shipped). Parameters travel in
|
|
38
|
+
# the codec name.
|
|
39
|
+
def compress(input, output, options = {})
|
|
40
|
+
input = prepare_input(input)
|
|
41
|
+
output = prepare_output(output)
|
|
42
|
+
data = input.read
|
|
43
|
+
order = options[:model_order] || PPMd8::Model::DEFAULT_ORDER
|
|
44
|
+
mem = options[:mem_size] || PPMd8::Model::DEFAULT_MEM_SIZE
|
|
45
|
+
|
|
46
|
+
output.write(
|
|
47
|
+
Backends.compress("ppmd8:o#{order}:m#{mem}", data, order) do
|
|
48
|
+
super(input, output, options) if false
|
|
49
|
+
raise NotImplementedError,
|
|
50
|
+
"PPMd8 compression is not yet fully implemented"
|
|
51
|
+
end,
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def decompress(input, output, options = {})
|
|
56
|
+
input = prepare_input(input)
|
|
57
|
+
output = prepare_output(output)
|
|
58
|
+
compressed = input.read
|
|
59
|
+
order = options[:model_order] || PPMd8::Model::DEFAULT_ORDER
|
|
60
|
+
mem = options[:mem_size] || PPMd8::Model::DEFAULT_MEM_SIZE
|
|
61
|
+
|
|
62
|
+
result = Backends.decompress(
|
|
63
|
+
"ppmd8:o#{order}:m#{mem}", compressed, Backends::UNKNOWN_LENGTH
|
|
64
|
+
) do
|
|
65
|
+
raise NotImplementedError,
|
|
66
|
+
"PPMd8 decompression is not yet fully implemented"
|
|
67
|
+
end
|
|
68
|
+
output.write(result)
|
|
69
|
+
end
|
|
70
|
+
|
|
35
71
|
# Nested classes - autoloaded
|
|
36
72
|
autoload :Constants, "omnizip/algorithms/ppmd8/constants"
|
|
37
73
|
autoload :RestorationMethod, "omnizip/algorithms/ppmd8/restoration_method"
|
|
@@ -119,7 +119,9 @@ module Omnizip
|
|
|
119
119
|
# @param input [IO, String] Input data
|
|
120
120
|
# @return [IO] IO object ready for reading
|
|
121
121
|
def prepare_input(input)
|
|
122
|
-
|
|
122
|
+
# StringIO is not an ::IO, and its #to_s is Kernel's (the
|
|
123
|
+
# INSPECT string) — pass anything readable straight through.
|
|
124
|
+
return input if input.respond_to?(:read) # allowed: IO-duck detection — StringIO must pass through (its #to_s is the INSPECT string)
|
|
123
125
|
|
|
124
126
|
StringIO.new(input.to_s)
|
|
125
127
|
end
|
|
@@ -129,7 +131,9 @@ module Omnizip
|
|
|
129
131
|
# @param output [IO, String, nil] Output destination
|
|
130
132
|
# @return [IO] IO object ready for writing
|
|
131
133
|
def prepare_output(output)
|
|
132
|
-
|
|
134
|
+
# StringIO is not an ::IO subclass — the old check discarded
|
|
135
|
+
# it for a fresh buffer, silently dropping the output.
|
|
136
|
+
return output if output.is_a?(IO) || output.is_a?(StringIO)
|
|
133
137
|
|
|
134
138
|
StringIO.new(String.new(encoding: Encoding::BINARY))
|
|
135
139
|
end
|
|
@@ -48,6 +48,14 @@ module Omnizip
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def list(path, details: false, **_)
|
|
51
|
+
# Whole-archive tier (names-only path): the Rust archive
|
|
52
|
+
# surface lists any supported format; the Ruby reader stays
|
|
53
|
+
# the fallback and the details path's source.
|
|
54
|
+
unless details
|
|
55
|
+
tier_names = Omnizip::Backends.archive_entry_names(path)
|
|
56
|
+
return tier_names if tier_names
|
|
57
|
+
end
|
|
58
|
+
|
|
51
59
|
reader = Formats::Zip::Reader.new(path)
|
|
52
60
|
return reader.entries.map(&:filename) unless details
|
|
53
61
|
|
|
@@ -65,6 +73,9 @@ module Omnizip
|
|
|
65
73
|
end
|
|
66
74
|
|
|
67
75
|
def read_entry(path, entry_name)
|
|
76
|
+
tier = Omnizip::Backends.archive_read_entry(path, entry_name)
|
|
77
|
+
return tier unless tier.nil?
|
|
78
|
+
|
|
68
79
|
Formats::Zip::Reader.new(path).read_entry(entry_name)
|
|
69
80
|
end
|
|
70
81
|
|
data/lib/omnizip/backends.rb
CHANGED
|
@@ -50,7 +50,7 @@ module Omnizip
|
|
|
50
50
|
return backend_ruby if mode == "ruby"
|
|
51
51
|
|
|
52
52
|
return backend_ruby unless Implementations::Rust::Library.available?
|
|
53
|
-
return backend_ruby unless Implementations::Rust::Library
|
|
53
|
+
return backend_ruby unless Implementations::Rust::Library.supports?(codec)
|
|
54
54
|
|
|
55
55
|
backend_rust
|
|
56
56
|
end
|
|
@@ -94,6 +94,26 @@ module Omnizip
|
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
# Whole-archive tier helpers: nil means "Rust unavailable or
|
|
98
|
+
# entry not found — caller falls back to the Ruby reader".
|
|
99
|
+
def archive_entry_names(path)
|
|
100
|
+
Omnizip::Implementations::Rust::Archive.open(File.binread(path)) { |a| return a.entry_names }
|
|
101
|
+
rescue Omnizip::Implementations::Rust::Archive::LibraryMissing, Omnizip::Implementations::Rust::Error, StandardError
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# rubocop:disable-next Lint/ReturnInVoidContext
|
|
106
|
+
def archive_read_entry(path, entry_name)
|
|
107
|
+
Omnizip::Implementations::Rust::Archive.open(File.binread(path)) do |a|
|
|
108
|
+
idx = a.entry_names.index(entry_name)
|
|
109
|
+
return nil if idx.nil?
|
|
110
|
+
|
|
111
|
+
return a.read_entry(idx)
|
|
112
|
+
end
|
|
113
|
+
rescue Omnizip::Implementations::Rust::Archive::LibraryMissing, Omnizip::Implementations::Rust::Error, StandardError
|
|
114
|
+
nil
|
|
115
|
+
end
|
|
116
|
+
|
|
97
117
|
private
|
|
98
118
|
|
|
99
119
|
def backend_rust
|
|
@@ -320,8 +320,13 @@ dereference_links: false)
|
|
|
320
320
|
# Decompress using Deflate
|
|
321
321
|
def decompress_deflate(data)
|
|
322
322
|
require "zlib"
|
|
323
|
-
# ZIP uses raw deflate without zlib wrapper
|
|
324
|
-
|
|
323
|
+
# ZIP uses raw deflate without zlib wrapper — the Rust tier
|
|
324
|
+
# speaks exactly that ("deflate"); Zlib remains the fallback.
|
|
325
|
+
Omnizip::Backends.decompress(
|
|
326
|
+
"deflate", data, Omnizip::Backends::UNKNOWN_LENGTH
|
|
327
|
+
) do
|
|
328
|
+
Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(data)
|
|
329
|
+
end
|
|
325
330
|
rescue StandardError => e
|
|
326
331
|
raise Omnizip::DecompressionError,
|
|
327
332
|
"Deflate decompression failed: #{e.message}"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Omnizip
|
|
4
|
+
module Implementations
|
|
5
|
+
module Rust
|
|
6
|
+
# Whole-archive acceleration over the cdylib: open any supported
|
|
7
|
+
# format (auto-detected), list entries, read entry bytes. This is
|
|
8
|
+
# the archive-level tier surface — the Ruby core remains the
|
|
9
|
+
# fallback (the cdylib unavailable ⇒ these raise LibraryMissing
|
|
10
|
+
# and callers fall back).
|
|
11
|
+
class Archive
|
|
12
|
+
class LibraryMissing < StandardError; end
|
|
13
|
+
|
|
14
|
+
# Built lazily inside #initialize: the Fiddle::TYPE_* constants
|
|
15
|
+
# only resolve after a successful require, and Ruby 4.0 moved
|
|
16
|
+
# fiddle out of the default gems (same reason
|
|
17
|
+
# Library.function_table is a method, not a constant).
|
|
18
|
+
def initialize(data, password: nil)
|
|
19
|
+
lib = Library.instance
|
|
20
|
+
raise LibraryMissing, "omnizip-ffi cdylib not loaded" unless lib
|
|
21
|
+
|
|
22
|
+
@lib = lib
|
|
23
|
+
funcs = function_table
|
|
24
|
+
@funcs = funcs.each_with_object({}) do |(key, (name, args, ret)), h|
|
|
25
|
+
h[key] = lib.bind(name, args, ret)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
pw = password&.to_s
|
|
29
|
+
pw_ptr = pw && !pw.empty? ? Fiddle::Pointer[pw] : nil
|
|
30
|
+
@handle = @funcs[:arch_open].call(Fiddle::Pointer[data], data.bytesize, pw_ptr)
|
|
31
|
+
return unless @handle.null?
|
|
32
|
+
|
|
33
|
+
raise Error, @lib.last_error
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def entry_count
|
|
37
|
+
@funcs[:arch_count].call(@handle)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def entry_names
|
|
41
|
+
(0...entry_count).map do |i|
|
|
42
|
+
ptr = @funcs[:arch_entry_name].call(@handle, i)
|
|
43
|
+
next nil if ptr.null?
|
|
44
|
+
|
|
45
|
+
ptr.to_s
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def entry_size(index)
|
|
50
|
+
@funcs[:arch_entry_size].call(@handle, index)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Read one entry's uncompressed bytes.
|
|
54
|
+
#
|
|
55
|
+
# @return [String] binary
|
|
56
|
+
def read_entry(index)
|
|
57
|
+
out_len = [0].pack("Q")
|
|
58
|
+
ptr = @funcs[:arch_read_entry].call(@handle, index, out_len)
|
|
59
|
+
raise Error, @lib.last_error if ptr.null?
|
|
60
|
+
|
|
61
|
+
@lib.take_buffer(ptr, out_len.unpack1("Q"))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def close
|
|
65
|
+
return unless @handle
|
|
66
|
+
|
|
67
|
+
@funcs[:arch_close].call(@handle)
|
|
68
|
+
@handle = nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.open(data, password: nil)
|
|
72
|
+
archive = new(data, password: password)
|
|
73
|
+
yield archive
|
|
74
|
+
ensure
|
|
75
|
+
archive&.close
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def function_table
|
|
81
|
+
{
|
|
82
|
+
arch_open: ["ozip_arch_open", %i[voidp size_t voidp], Fiddle::TYPE_VOIDP],
|
|
83
|
+
arch_count: ["ozip_arch_count", [:voidp], Fiddle::TYPE_SIZE_T],
|
|
84
|
+
arch_entry_name: ["ozip_arch_entry_name", %i[voidp size_t], Fiddle::TYPE_VOIDP],
|
|
85
|
+
arch_entry_size: ["ozip_arch_entry_size", %i[voidp size_t], Fiddle::TYPE_VOIDP],
|
|
86
|
+
arch_read_entry: ["ozip_arch_read_entry", %i[voidp size_t voidp], Fiddle::TYPE_VOIDP],
|
|
87
|
+
arch_close: ["ozip_arch_close", [:voidp], Fiddle::TYPE_VOID],
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -21,7 +21,7 @@ module Omnizip
|
|
|
21
21
|
# authority; Ruby is the fallback).
|
|
22
22
|
CODECS = %w[
|
|
23
23
|
bzip2 zstd lzma xz lzma-alone lzip
|
|
24
|
-
deflate deflate64 zlib gzip
|
|
24
|
+
deflate deflate64 zlib gzip ppmd7 ppmd8
|
|
25
25
|
].freeze
|
|
26
26
|
|
|
27
27
|
class << self
|
|
@@ -38,6 +38,13 @@ module Omnizip
|
|
|
38
38
|
!instance.nil?
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
# Whether the cdylib dispatches this codec name — exact, or
|
|
42
|
+
# a param-carrying prefix ("ppmd7:o6:m16777216" under
|
|
43
|
+
# "ppmd7").
|
|
44
|
+
def supports?(name)
|
|
45
|
+
CODECS.any? { |c| name == c || name.start_with?("#{c}:") }
|
|
46
|
+
end
|
|
47
|
+
|
|
41
48
|
# Drop the cached handle so resolution reruns (test seam:
|
|
42
49
|
# specs exercising fallback/absence paths need a clean
|
|
43
50
|
# slate; production never calls this).
|
|
@@ -79,7 +86,7 @@ module Omnizip
|
|
|
79
86
|
funcs = function_table.each_with_object({}) do |(key, (name, args, ret)), h|
|
|
80
87
|
h[key] = Fiddle::Function.new(handle[name], args, ret)
|
|
81
88
|
end
|
|
82
|
-
new(funcs)
|
|
89
|
+
new(funcs, handle)
|
|
83
90
|
rescue StandardError => e # includes Fiddle::DLError
|
|
84
91
|
warn "omnizip: rust backend unavailable (#{e.message}); using pure Ruby" if ENV["OMNIZIP_BACKEND"] == "rust"
|
|
85
92
|
nil
|
|
@@ -113,8 +120,9 @@ module Omnizip
|
|
|
113
120
|
end
|
|
114
121
|
end
|
|
115
122
|
|
|
116
|
-
def initialize(functions)
|
|
123
|
+
def initialize(functions, handle = nil)
|
|
117
124
|
@functions = functions.freeze
|
|
125
|
+
@handle = handle
|
|
118
126
|
end
|
|
119
127
|
|
|
120
128
|
def compress(codec, data, level)
|
|
@@ -129,11 +137,31 @@ module Omnizip
|
|
|
129
137
|
take(ptr, out_len.unpack1("Q"))
|
|
130
138
|
end
|
|
131
139
|
|
|
140
|
+
# Bind one extra C symbol from the loaded cdylib (the archive
|
|
141
|
+
# surface uses this; Fiddle::Function is immutable once built).
|
|
142
|
+
def bind(symbol, args, ret)
|
|
143
|
+
handle = instance_handle
|
|
144
|
+
raise Error, "cdylib not loaded" if handle.nil?
|
|
145
|
+
|
|
146
|
+
Fiddle::Function.new(handle[symbol.to_s], args, ret)
|
|
147
|
+
end
|
|
148
|
+
|
|
132
149
|
def last_error
|
|
133
150
|
ptr = @functions[:last_error].call
|
|
134
151
|
ptr.null? ? "unknown error" : ptr.to_s
|
|
135
152
|
end
|
|
136
153
|
|
|
154
|
+
# The raw Fiddle::Handle (for extra symbol binds).
|
|
155
|
+
def instance_handle
|
|
156
|
+
@handle
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Copy out + free one returned buffer (public seam for the
|
|
160
|
+
# archive surface).
|
|
161
|
+
def take_buffer(ptr, len)
|
|
162
|
+
take(ptr, len)
|
|
163
|
+
end
|
|
164
|
+
|
|
137
165
|
private
|
|
138
166
|
|
|
139
167
|
def call(name, codec, input, input_len, a, out_len)
|
data/lib/omnizip/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omnizip
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -601,6 +601,7 @@ files:
|
|
|
601
601
|
- lib/omnizip/implementations/base/lzma_encoder_base.rb
|
|
602
602
|
- lib/omnizip/implementations/base/state_machine_base.rb
|
|
603
603
|
- lib/omnizip/implementations/rust.rb
|
|
604
|
+
- lib/omnizip/implementations/rust/archive.rb
|
|
604
605
|
- lib/omnizip/implementations/rust/library.rb
|
|
605
606
|
- lib/omnizip/implementations/seven_zip.rb
|
|
606
607
|
- lib/omnizip/implementations/seven_zip/lzma.rb
|