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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48ed1a8b9fa1d9c51d338c611430fbd78f12de0a9d5d9de3ebff23ac3b9b767c
4
- data.tar.gz: 6b173fab258e290030227c869b703631023c621699e810753259b14ef613ef34
3
+ metadata.gz: 34bab2534b75e9ca958c00130c6b5962dbd5422bd6e0f4683c8e756415d9961b
4
+ data.tar.gz: 9089d88fbe8ce8bda17fecd2a21454db2daa23c86e930b69cf8bb86a495b1ce0
5
5
  SHA512:
6
- metadata.gz: 07fba2b58cca8f0c0429042e4a2813d5618158cb8231625ad1e6f5c32d1bc532d45a8bb952a3087d6274862647dbab0480aca8c96561f24c5a3debf2a1f7dd63
7
- data.tar.gz: 8422215bde30fa926b235b20dddbeba195573a82d1678ea98d541be6e37e6e14c7a6ae444e53b1a4505733a6420a4d9d3a53cec3605c751c5bf6eac49678ef86
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
- decoder = Decoder.new(input_stream)
82
- decompressed = decoder.decode_stream
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
- # Use Zlib::Inflate with maximum window size
77
- inflater = Zlib::Inflate.new(Zlib::MAX_WBITS)
78
- decompressed = inflater.inflate(compressed)
79
- inflater.close
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
- decoder = Decoder.new(input_stream)
102
- decompressed = decoder.decode_stream
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
 
@@ -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
- backend.decompress(codec, data, expected_len)
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
@@ -91,18 +91,15 @@ module Omnizip
91
91
  # Write GZIP header
92
92
  write_header(output_io, mtime, original_name)
93
93
 
94
- # Compress data with Deflate
95
- deflate = Zlib::Deflate.new(
96
- level,
97
- Zlib::MAX_WBITS + 16, # Use GZIP wrapper
98
- )
99
- compressed = deflate.deflate(input_data, Zlib::FINISH)
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
- # Read and parse header
117
- metadata = read_header(input_io)
118
-
119
- # Decompress with Deflate
120
- inflate = Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
121
- decompressed = inflate.inflate(input_io.read)
122
- inflate.close
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
@@ -89,11 +89,17 @@ module Omnizip
89
89
 
90
90
  case format
91
91
  when :lz
92
- decode_lzip(data)
92
+ Backends.decompress("lzip", data, Backends::UNKNOWN_LENGTH) do
93
+ decode_lzip(data)
94
+ end
93
95
  when :lzma_alone
94
- decode_lzma_alone(data)
96
+ Backends.decompress("lzma-alone", data, Backends::UNKNOWN_LENGTH) do
97
+ decode_lzma_alone(data)
98
+ end
95
99
  when :xz
96
- decode_xz_stream(data)
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
- CODECS = %w[bzip2 zstd lzma xz].freeze
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
- @instance = nil if defined?(@instance)
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnizip
4
- VERSION = "0.4.2"
4
+ VERSION = "0.4.3"
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.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.