omnizip 0.4.4 → 0.4.5
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 +12 -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/backends.rb +1 -1
- data/lib/omnizip/formats/zip/reader.rb +7 -2
- data/lib/omnizip/implementations/rust/library.rb +8 -1
- data/lib/omnizip/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a55c37209e986f7e4499e8a485163728e9c8f0dff506e1aa852672a9585653f
|
|
4
|
+
data.tar.gz: ba0b5437026feed4c1fe4d0ab0146f868386ebe2284ec7b08b45c7260930d582
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 13d25032be26ccd096718846ac74126fd14ab3e2729d1e9ed366a3c431815b69c0817e33092f214eea4ed041e41e2eebe070ac92862bd54c70b0379017281637
|
|
7
|
+
data.tar.gz: 8e68e9a4a0d7f489be9956baf6f2d18bb82d5cc0db123adaf689ab73aefca1dcb44c0172a8e774c0cbcfbcca2927c9f40b62da184678588e084642b3b86cd7eb
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ 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
|
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
|
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
|
|
@@ -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}"
|
|
@@ -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).
|
data/lib/omnizip/version.rb
CHANGED