omnizip 0.4.2 → 0.4.3
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 +7 -0
- data/lib/omnizip/algorithms/deflate.rb +8 -2
- data/lib/omnizip/algorithms/deflate64.rb +9 -4
- data/lib/omnizip/algorithms/zstandard.rb +4 -2
- data/lib/omnizip/backends.rb +13 -2
- data/lib/omnizip/formats/gzip.rb +27 -18
- data/lib/omnizip/formats/xz.rb +9 -3
- data/lib/omnizip/implementations/rust/library.rb +11 -2
- 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: 34bab2534b75e9ca958c00130c6b5962dbd5422bd6e0f4683c8e756415d9961b
|
|
4
|
+
data.tar.gz: 9089d88fbe8ce8bda17fecd2a21454db2daa23c86e930b69cf8bb86a495b1ce0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d92441da2c757c65e1e5865abd446cd2a296adafcdf56d5dabfc00f6c792fd6f9b8f3e51dda0231ce5d8829eaf4b81662cfdadc13cdbc9495c73d088968c6c81
|
|
7
|
+
data.tar.gz: 30591da9a2e0af6922a49596133f98e5f05bf30e927b698e4b413c1f9f0d1907ab6f0f9a0cc32eff385772b7445aff36e271066690c85e3a134dd040e8185591
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
- Rust tier wiring for every accelerated codec: zstd, deflate,
|
|
12
|
+
deflate64 (both zlib-framed), gzip, xz, lzip, and lzma-alone now
|
|
13
|
+
route decode through the Rust backend when it loads, each pinned by
|
|
14
|
+
a cross-implementation differential. A Rust failure falls back to
|
|
15
|
+
the Ruby core — auto mode is never worse than pure Ruby.
|
|
16
|
+
|
|
10
17
|
### Fixed
|
|
11
18
|
- RAR recovery-record repair fails loudly instead of pretending:
|
|
12
19
|
`recover_with_reed_solomon` returned nil and `recover_with_xor`
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
3
5
|
# Copyright (C) 2025 Ribose Inc.
|
|
4
6
|
#
|
|
5
7
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
@@ -78,8 +80,12 @@ module Omnizip
|
|
|
78
80
|
# @return [void]
|
|
79
81
|
def decompress(input_stream, output_stream, _options = nil)
|
|
80
82
|
output_stream.set_encoding(Encoding::BINARY)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
compressed = input_stream.read
|
|
84
|
+
# The encoder's streams carry the zlib wrapper (RFC 1950) —
|
|
85
|
+
# the Rust "zlib" name speaks exactly that framing.
|
|
86
|
+
decompressed = Backends.decompress("zlib", compressed, Backends::UNKNOWN_LENGTH) do
|
|
87
|
+
Decoder.new(StringIO.new(compressed)).decode_stream
|
|
88
|
+
end
|
|
83
89
|
output_stream.write(decompressed)
|
|
84
90
|
end
|
|
85
91
|
|
|
@@ -73,10 +73,15 @@ module Omnizip
|
|
|
73
73
|
output.set_encoding(Encoding::BINARY)
|
|
74
74
|
output.binmode
|
|
75
75
|
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
# The streams this algorithm produces/consumes are zlib
|
|
77
|
+
# containers (RFC 1950); the Rust "zlib" name speaks exactly
|
|
78
|
+
# that, so the tier swap is byte-transparent.
|
|
79
|
+
decompressed = Backends.decompress("zlib", compressed, Backends::UNKNOWN_LENGTH) do
|
|
80
|
+
inflater = Zlib::Inflate.new(Zlib::MAX_WBITS)
|
|
81
|
+
result = inflater.inflate(compressed)
|
|
82
|
+
inflater.close
|
|
83
|
+
result
|
|
84
|
+
end
|
|
80
85
|
|
|
81
86
|
# Force binary encoding to match original data
|
|
82
87
|
decompressed.force_encoding(Encoding::BINARY)
|
|
@@ -98,8 +98,10 @@ module Omnizip
|
|
|
98
98
|
# @return [void]
|
|
99
99
|
def decompress(input_stream, output_stream, _options = nil)
|
|
100
100
|
output_stream.set_encoding(Encoding::BINARY)
|
|
101
|
-
|
|
102
|
-
decompressed =
|
|
101
|
+
compressed = input_stream.read
|
|
102
|
+
decompressed = Backends.decompress("zstd", compressed, Backends::UNKNOWN_LENGTH) do
|
|
103
|
+
Decoder.new(StringIO.new(compressed)).decode_stream
|
|
104
|
+
end
|
|
103
105
|
output_stream.write(decompressed)
|
|
104
106
|
end
|
|
105
107
|
|
data/lib/omnizip/backends.rb
CHANGED
|
@@ -62,12 +62,23 @@ module Omnizip
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
# Decompress through the tier-selected backend (output-
|
|
65
|
-
# invariant: :auto prefers Rust whenever loadable).
|
|
65
|
+
# invariant: :auto prefers Rust whenever loadable). A Rust
|
|
66
|
+
# failure falls back to the Ruby core — the Ruby readers are
|
|
67
|
+
# the tolerant superset (legacy container variants, malformed
|
|
68
|
+
# inputs they historically accept), so :auto must never be
|
|
69
|
+
# WORSE than pure Ruby. Only the forced `rust` mode propagates.
|
|
66
70
|
def decompress(codec, data, expected_len, &ruby_core)
|
|
67
71
|
backend = Backends.for(codec, :decode)
|
|
68
72
|
return yield if backend == RubyBackend
|
|
69
73
|
|
|
70
|
-
|
|
74
|
+
begin
|
|
75
|
+
backend.decompress(codec, data, expected_len)
|
|
76
|
+
rescue Implementations::Rust::Error => e
|
|
77
|
+
raise if ENV.fetch("OMNIZIP_BACKEND", "auto") == "rust"
|
|
78
|
+
|
|
79
|
+
warn "omnizip: rust #{codec} decode failed (#{e.message}); using ruby" if ENV["OMNIZIP_BACKEND_DEBUG"]
|
|
80
|
+
yield
|
|
81
|
+
end
|
|
71
82
|
end
|
|
72
83
|
|
|
73
84
|
private
|
data/lib/omnizip/formats/gzip.rb
CHANGED
|
@@ -91,18 +91,15 @@ module Omnizip
|
|
|
91
91
|
# Write GZIP header
|
|
92
92
|
write_header(output_io, mtime, original_name)
|
|
93
93
|
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
# RFC 1952 member: header + RAW deflate + CRC32/ISIZE footer.
|
|
95
|
+
# The old implementation layered zlib's own GZIP wrapper
|
|
96
|
+
# under this header (a second 1F 8B magic) — the gem's
|
|
97
|
+
# reader tolerated it, but every standard gzip tool
|
|
98
|
+
# rejected the output. Raw-window mode emits bare DEFLATE.
|
|
99
|
+
deflate = Zlib::Deflate.new(level, -Zlib::MAX_WBITS)
|
|
100
|
+
output_io.write(deflate.deflate(input_data, Zlib::FINISH))
|
|
100
101
|
deflate.close
|
|
101
102
|
|
|
102
|
-
# Extract just the compressed data (remove zlib's GZIP wrapper)
|
|
103
|
-
# We'll write our own footer
|
|
104
|
-
output_io.write(compressed[0...-8]) if compressed.bytesize > 8
|
|
105
|
-
|
|
106
103
|
# Write GZIP footer
|
|
107
104
|
write_footer(output_io, input_data)
|
|
108
105
|
end
|
|
@@ -113,15 +110,27 @@ module Omnizip
|
|
|
113
110
|
# @param output_io [IO] Output stream
|
|
114
111
|
# @return [Hash] Metadata (original_name, mtime)
|
|
115
112
|
def decompress_stream(input_io, output_io)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
113
|
+
data = input_io.read
|
|
114
|
+
|
|
115
|
+
# Metadata comes from the header either way (cheap parse).
|
|
116
|
+
metadata = read_header(StringIO.new(data))
|
|
117
|
+
|
|
118
|
+
# The tier gets the WHOLE member (header + deflate + trailer)
|
|
119
|
+
# — the Rust "gzip" name decodes the container end-to-end.
|
|
120
|
+
decompressed = Backends.decompress("gzip", data, Backends::UNKNOWN_LENGTH) do
|
|
121
|
+
body = StringIO.new(data)
|
|
122
|
+
read_header(body) # skip past the header
|
|
123
|
+
# Legacy double-header members (written before the RFC
|
|
124
|
+
# 1952 fix) carry zlib's own 10-byte GZIP header here;
|
|
125
|
+
# standard members carry bare DEFLATE.
|
|
126
|
+
rest = body.read
|
|
127
|
+
rest = rest.byteslice(10..) if rest.byteslice(0, 2) == "\x1F\x8B".b
|
|
128
|
+
inflate = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
|
129
|
+
result = inflate.inflate(rest)
|
|
130
|
+
inflate.close
|
|
131
|
+
result
|
|
132
|
+
end
|
|
123
133
|
|
|
124
|
-
# Write decompressed data
|
|
125
134
|
output_io.write(decompressed)
|
|
126
135
|
|
|
127
136
|
metadata
|
data/lib/omnizip/formats/xz.rb
CHANGED
|
@@ -89,11 +89,17 @@ module Omnizip
|
|
|
89
89
|
|
|
90
90
|
case format
|
|
91
91
|
when :lz
|
|
92
|
-
|
|
92
|
+
Backends.decompress("lzip", data, Backends::UNKNOWN_LENGTH) do
|
|
93
|
+
decode_lzip(data)
|
|
94
|
+
end
|
|
93
95
|
when :lzma_alone
|
|
94
|
-
|
|
96
|
+
Backends.decompress("lzma-alone", data, Backends::UNKNOWN_LENGTH) do
|
|
97
|
+
decode_lzma_alone(data)
|
|
98
|
+
end
|
|
95
99
|
when :xz
|
|
96
|
-
|
|
100
|
+
Backends.decompress("xz", data, Backends::UNKNOWN_LENGTH) do
|
|
101
|
+
decode_xz_stream(data)
|
|
102
|
+
end
|
|
97
103
|
else
|
|
98
104
|
raise FormatError, "Unknown LZMA format: cannot detect valid format"
|
|
99
105
|
end
|
|
@@ -16,7 +16,13 @@ module Omnizip
|
|
|
16
16
|
# built; compress/decompress are pure C calls).
|
|
17
17
|
class Library
|
|
18
18
|
# Codecs the cdylib dispatches by name.
|
|
19
|
-
|
|
19
|
+
# Names the cdylib dispatches: the codec-level acceleration surface
|
|
20
|
+
# (decode tier by default; encode only under forced rust mode —
|
|
21
|
+
# RUST_ENCODE_IDENTICAL gates the auto tier).
|
|
22
|
+
CODECS = %w[
|
|
23
|
+
bzip2 zstd lzma xz lzma-alone lzip
|
|
24
|
+
deflate deflate64 zlib gzip
|
|
25
|
+
].freeze
|
|
20
26
|
|
|
21
27
|
class << self
|
|
22
28
|
# The shared handle, or nil when the cdylib cannot load.
|
|
@@ -36,7 +42,10 @@ module Omnizip
|
|
|
36
42
|
# specs exercising fallback/absence paths need a clean
|
|
37
43
|
# slate; production never calls this).
|
|
38
44
|
def forget!
|
|
39
|
-
|
|
45
|
+
# Remove the ivar entirely: assigning nil would leave it
|
|
46
|
+
# defined, and `instance`'s `defined?` guard would return
|
|
47
|
+
# the nil forever without ever re-opening the library.
|
|
48
|
+
remove_instance_variable(:@instance) if defined?(@instance)
|
|
40
49
|
nil
|
|
41
50
|
end
|
|
42
51
|
|
data/lib/omnizip/version.rb
CHANGED