omnizip 0.4.9-arm-linux → 0.4.11-arm-linux

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop-oss.yml +814 -0
  3. data/.rubocop.yml +15 -1
  4. data/.rubocop_todo.yml +7 -1
  5. data/CHANGELOG.md +17 -0
  6. data/Gemfile +23 -1
  7. data/README.adoc +101 -1282
  8. data/docs/compatibility.adoc +1 -2
  9. data/docs/lychee.toml +1 -7
  10. data/lib/omnizip/algorithms/deflate/constants.rb +5 -3
  11. data/lib/omnizip/implementations/rust/archive.rb +9 -41
  12. data/lib/omnizip/implementations/rust/ffi_library.rb +127 -0
  13. data/lib/omnizip/implementations/rust/fiddle_library.rb +144 -0
  14. data/lib/omnizip/implementations/rust/library.rb +39 -104
  15. data/lib/omnizip/implementations/rust.rb +2 -0
  16. data/lib/omnizip/version.rb +1 -1
  17. data/lib/tasks/docs.rake +146 -0
  18. data/readme-docs/rar-archives.adoc +505 -0
  19. data/scripts/generate_rar_oracle_fixtures.rb +177 -0
  20. data/vendor/libomnizip_ffi.so +0 -0
  21. metadata +21 -74
  22. data/TODO.refactor/00-overview.md +0 -67
  23. data/TODO.refactor/01-registry-base-class.md +0 -81
  24. data/TODO.refactor/02-error-hierarchy.md +0 -57
  25. data/TODO.refactor/03-autoload-entry-point.md +0 -69
  26. data/TODO.refactor/04-filter-base-consolidation.md +0 -49
  27. data/TODO.refactor/05-send-private-methods.md +0 -64
  28. data/TODO.refactor/06-instance-variable-access.md +0 -69
  29. data/TODO.refactor/07-respond-to-replacement.md +0 -237
  30. data/TODO.refactor/08-spec-doubles.md +0 -27
  31. data/TODO.refactor/09-cli-shared-module.md +0 -35
  32. data/TODO.refactor/10-format-detector-ocp.md +0 -34
  33. data/TODO.refactor/11-convenience-decoupling.md +0 -38
  34. data/TODO.refactor/12-thread-safety.md +0 -8
  35. data/TODO.refactor/13-lutaml-model-migration.md +0 -158
  36. data/TODO.refactor/14-add-missing-specs.md +0 -25
  37. data/docs/.github/workflows/docs.yml +0 -142
  38. data/docs/.gitignore +0 -21
  39. data/docs/Gemfile +0 -13
  40. data/docs/README.md +0 -101
  41. data/docs/_config.yml +0 -112
  42. data/docs/comparison/feature-matrix.adoc +0 -694
  43. data/docs/comparison/index.adoc +0 -113
  44. data/docs/comparison/vs-7zip.adoc +0 -309
  45. data/docs/comparison/vs-peazip.adoc +0 -77
  46. data/docs/comparison/vs-rubyzip.adoc +0 -342
  47. data/docs/comparison/vs-winrar.adoc +0 -100
  48. data/docs/concepts/index.adoc +0 -129
  49. data/docs/developer/architecture.adoc +0 -256
  50. data/docs/developer/contributing.adoc +0 -158
  51. data/docs/developer/index.adoc +0 -25
  52. data/docs/developer/testing.adoc +0 -212
  53. data/docs/getting-started/basic-usage.adoc +0 -263
  54. data/docs/getting-started/index.adoc +0 -42
  55. data/docs/getting-started/installation.adoc +0 -138
  56. data/docs/getting-started/quick-start.adoc +0 -183
  57. data/docs/getting-started/your-first-archive.adoc +0 -219
  58. data/docs/guides/advanced-features/encryption.adoc +0 -277
  59. data/docs/guides/advanced-features/index.adoc +0 -59
  60. data/docs/guides/advanced-features/parallel-processing.adoc +0 -144
  61. data/docs/guides/advanced-features/progress-tracking.adoc +0 -124
  62. data/docs/guides/advanced-features/streaming.adoc +0 -142
  63. data/docs/guides/archive-formats/gzip-format.adoc +0 -105
  64. data/docs/guides/archive-formats/index.adoc +0 -160
  65. data/docs/guides/archive-formats/ole-format.adoc +0 -151
  66. data/docs/guides/archive-formats/rar-format.adoc +0 -104
  67. data/docs/guides/archive-formats/rar5.adoc +0 -521
  68. data/docs/guides/archive-formats/rpm-format.adoc +0 -108
  69. data/docs/guides/archive-formats/seven-zip-format.adoc +0 -35
  70. data/docs/guides/archive-formats/tar-format.adoc +0 -106
  71. data/docs/guides/archive-formats/xz-format.adoc +0 -118
  72. data/docs/guides/archive-formats/zip-format.adoc +0 -35
  73. data/docs/guides/compression-algorithms/bzip2.adoc +0 -114
  74. data/docs/guides/compression-algorithms/deflate.adoc +0 -313
  75. data/docs/guides/compression-algorithms/index.adoc +0 -190
  76. data/docs/guides/compression-algorithms/lzma.adoc +0 -394
  77. data/docs/guides/compression-algorithms/lzma2.adoc +0 -244
  78. data/docs/guides/compression-algorithms/ppmd.adoc +0 -313
  79. data/docs/guides/compression-algorithms/zstandard.adoc +0 -296
  80. data/docs/guides/creating-archives.adoc +0 -348
  81. data/docs/guides/extracting-archives.adoc +0 -53
  82. data/docs/guides/format-conversion.adoc +0 -64
  83. data/docs/guides/index.adoc +0 -49
  84. data/docs/guides/migration-rubyzip.adoc +0 -217
  85. data/docs/guides/parity-archives.adoc +0 -605
  86. data/docs/guides/performance-tuning.adoc +0 -88
  87. data/docs/index.adoc +0 -228
  88. data/docs/reference/api/overview.adoc +0 -187
  89. data/docs/reference/cli/compress-command.adoc +0 -114
  90. data/docs/reference/cli/overview.adoc +0 -140
  91. data/docs/reference/index.adoc +0 -26
  92. data/docs/resources/faq.adoc +0 -185
  93. data/docs/resources/quick-reference.adoc +0 -222
  94. data/docs/troubleshooting/index.adoc +0 -207
@@ -483,7 +483,7 @@ Zip::File.open('archive.zip') do |zip|
483
483
  end
484
484
  ----
485
485
 
486
- See link:guides/migration-rubyzip.html[Complete Migration Guide]
486
+ See the migration notes in the Rubyzip compatibility sections of link:../readme-docs/api-usage.adoc[API usage]
487
487
 
488
488
  === From 7-Zip CLI to Omnizip API
489
489
 
@@ -574,6 +574,5 @@ This makes Omnizip ideal for Ruby applications needing advanced compression beyo
574
574
  == See Also
575
575
 
576
576
  * link:getting-started/installation.html[Installation Guide]
577
- * link:guides/migration-rubyzip.html[Migration from Rubyzip]
578
577
  * link:getting-started/quick-start.html[Quick Start Tutorial]
579
578
  * link:reference/index.html[API Reference]
data/docs/lychee.toml CHANGED
@@ -9,12 +9,9 @@ max_cache_age = "1d"
9
9
  # Check both source files and built site
10
10
  include_verbatim = true
11
11
 
12
- # Recursively check all files
13
- recursive = true
14
-
15
12
  # File types to check
16
13
  include = [
17
- "_site/**/*.html"
14
+ "site/**/*.html"
18
15
  ]
19
16
 
20
17
  # Excluded paths
@@ -46,9 +43,6 @@ user_agent = "lychee/omnizip-docs-link-checker"
46
43
  # Check HTTP, HTTPS, and file:// schemes
47
44
  scheme = ["https", "http", "file"]
48
45
 
49
- # Include file:// URLs for local link checking
50
- include_file = true
51
-
52
46
  # Handle different link types
53
47
  include_mail = false # Don't check mailto: links
54
48
 
@@ -13,11 +13,13 @@ module Omnizip
13
13
  BEST_COMPRESSION = Zlib::BEST_COMPRESSION
14
14
  DEFAULT_COMPRESSION = Zlib::DEFAULT_COMPRESSION
15
15
 
16
- # Compression strategies
16
+ # Compression strategies. RLE and FIXED are CRuby-only Zlib
17
+ # constants (JRuby's zlib omits them — found by the JRuby CI
18
+ # leg); the numeric values are fixed by the zlib spec.
17
19
  FILTERED = Zlib::FILTERED
18
20
  HUFFMAN_ONLY = Zlib::HUFFMAN_ONLY
19
- RLE = Zlib::RLE
20
- FIXED = Zlib::FIXED
21
+ RLE = Zlib.const_defined?(:RLE) ? Zlib::RLE : 3
22
+ FIXED = Zlib.const_defined?(:FIXED) ? Zlib::FIXED : 4
21
23
  DEFAULT_STRATEGY = Zlib::DEFAULT_STRATEGY
22
24
 
23
25
  # Buffer size for streaming operations
@@ -7,64 +7,45 @@ module Omnizip
7
7
  # format (auto-detected), list entries, read entry bytes. This is
8
8
  # the archive-level tier surface — the Ruby core remains the
9
9
  # fallback (the cdylib unavailable ⇒ these raise LibraryMissing
10
- # and callers fall back).
10
+ # and callers fall back). Binding-agnostic: dispatches through
11
+ # whichever backend loaded (Fiddle on MRI, ffi on
12
+ # JRuby/TruffleRuby).
11
13
  class Archive
12
14
  class LibraryMissing < StandardError; end
13
15
 
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
16
  def initialize(data, password: nil)
19
17
  lib = Library.instance
20
18
  raise LibraryMissing, "omnizip-ffi cdylib not loaded" unless lib
21
19
 
22
20
  @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
21
+ @handle = @lib.arch_open(data, password)
34
22
  end
35
23
 
36
24
  def entry_count
37
- @funcs[:arch_count].call(@handle)
25
+ @lib.arch_count(@handle)
38
26
  end
39
27
 
40
28
  def entry_names
41
29
  (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
30
+ @lib.arch_entry_name(@handle, i)
46
31
  end
47
32
  end
48
33
 
49
34
  def entry_size(index)
50
- @funcs[:arch_entry_size].call(@handle, index)
35
+ @lib.arch_entry_size(@handle, index)
51
36
  end
52
37
 
53
38
  # Read one entry's uncompressed bytes.
54
39
  #
55
40
  # @return [String] binary
56
41
  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"))
42
+ @lib.arch_read_entry(@handle, index)
62
43
  end
63
44
 
64
45
  def close
65
46
  return unless @handle
66
47
 
67
- @funcs[:arch_close].call(@handle)
48
+ @lib.arch_close(@handle)
68
49
  @handle = nil
69
50
  end
70
51
 
@@ -74,19 +55,6 @@ module Omnizip
74
55
  ensure
75
56
  archive&.close
76
57
  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
58
  end
91
59
  end
92
60
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ module Implementations
5
+ module Rust
6
+ # The ffi-gem binding of the cdylib's C ABI — the JRuby /
7
+ # TruffleRuby acceleration path (leptris-ruby's model): those
8
+ # engines ship no Fiddle, so the Fiddle tier cannot load.
9
+ # Mirrors FiddleLibrary's public surface exactly, so the Library
10
+ # facade and the Archive class are binding-agnostic.
11
+ class FfiLibrary
12
+ attr_reader :mod
13
+
14
+ # Attach via a fresh module: FFI::Library is an extend-time
15
+ # DSL — one module per loaded image, like one Fiddle::Handle.
16
+ def self.attach(path)
17
+ mod = Module.new do
18
+ extend ::FFI::Library
19
+
20
+ ffi_lib path.to_s
21
+ end
22
+ mod.attach_function :ozip_compress,
23
+ %i[string buffer_in size_t int pointer], :pointer
24
+ mod.attach_function :ozip_decompress,
25
+ %i[string buffer_in size_t size_t pointer], :pointer
26
+ mod.attach_function :ozip_last_error, [], :pointer
27
+ mod.attach_function :ozip_free, %i[pointer size_t], :void
28
+ mod.attach_function :ozip_version, [], :pointer
29
+ mod.attach_function :ozip_arch_open,
30
+ %i[buffer_in size_t string], :pointer
31
+ mod.attach_function :ozip_arch_count, [:pointer], :size_t
32
+ mod.attach_function :ozip_arch_entry_name,
33
+ %i[pointer size_t], :pointer
34
+ mod.attach_function :ozip_arch_entry_size,
35
+ %i[pointer size_t], :uint64
36
+ mod.attach_function :ozip_arch_read_entry,
37
+ %i[pointer size_t pointer], :pointer
38
+ mod.attach_function :ozip_arch_close, [:pointer], :void
39
+ mod
40
+ end
41
+
42
+ def initialize(path)
43
+ @mod = self.class.attach(path)
44
+ end
45
+
46
+ def compress(codec, data, level)
47
+ out_len = ::FFI::MemoryPointer.new(:size_t)
48
+ ptr = mod.ozip_compress(codec, data, data.bytesize, level, out_len)
49
+ raise Rust::Error, last_error if ptr.null?
50
+
51
+ take(ptr, out_len.read_ulong)
52
+ end
53
+
54
+ def decompress(codec, data, expected_len)
55
+ out_len = ::FFI::MemoryPointer.new(:size_t)
56
+ ptr = mod.ozip_decompress(codec, data, data.bytesize,
57
+ expected_len, out_len)
58
+ raise Rust::Error, last_error if ptr.null?
59
+
60
+ take(ptr, out_len.read_ulong)
61
+ end
62
+
63
+ def last_error
64
+ ptr = mod.ozip_last_error
65
+ ptr.null? ? "unknown error" : ptr.read_string
66
+ end
67
+
68
+ # Copy out + free one returned buffer: the buffer belongs to
69
+ # the Rust allocator and must never outlive ozip_free.
70
+ def take(ptr, len)
71
+ s = ptr.read_string(len)
72
+ mod.ozip_free(ptr, len)
73
+ s
74
+ end
75
+
76
+ def take_buffer(ptr, len)
77
+ take(ptr, len)
78
+ end
79
+
80
+ # The Rust release this cdylib was built from, or nil when the
81
+ # symbol is absent (dylibs older than 0.21.109).
82
+ def dylib_version
83
+ ptr = mod.ozip_version
84
+ ptr.null? ? nil : ptr.read_string
85
+ rescue ::FFI::NotFoundError
86
+ nil
87
+ end
88
+
89
+ # ---- whole-archive surface (ArchHandle) ----
90
+ # FFI auto-converts a Ruby String for a :pointer argument and
91
+ # nil to NULL, and the handle travels as a naked pointer.
92
+
93
+ def arch_open(data, password)
94
+ ptr = mod.ozip_arch_open(data, data.bytesize, password)
95
+ raise Rust::Error, last_error if ptr.null?
96
+
97
+ ptr
98
+ end
99
+
100
+ def arch_count(handle)
101
+ mod.ozip_arch_count(handle)
102
+ end
103
+
104
+ def arch_entry_name(handle, index)
105
+ ptr = mod.ozip_arch_entry_name(handle, index)
106
+ ptr.null? ? nil : ptr.read_string
107
+ end
108
+
109
+ def arch_entry_size(handle, index)
110
+ mod.ozip_arch_entry_size(handle, index)
111
+ end
112
+
113
+ def arch_read_entry(handle, index)
114
+ out_len = ::FFI::MemoryPointer.new(:size_t)
115
+ ptr = mod.ozip_arch_read_entry(handle, index, out_len)
116
+ raise Rust::Error, last_error if ptr.null?
117
+
118
+ take(ptr, out_len.read_ulong)
119
+ end
120
+
121
+ def arch_close(handle)
122
+ mod.ozip_arch_close(handle)
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ module Implementations
5
+ module Rust
6
+ # The Fiddle binding of the cdylib's C ABI — the MRI fast path
7
+ # (stdlib, zero gems). One public surface shared with
8
+ # FfiLibrary: codec methods, whole-archive methods, error and
9
+ # version accessors — the binding Library's facade and the
10
+ # Archive class dispatch through.
11
+ class FiddleLibrary
12
+ attr_reader :handle
13
+
14
+ # The shared function table (Fiddle::TYPE_* constants resolve
15
+ # only after a successful require — same reason Library keeps
16
+ # this out of the class body).
17
+ FUNCTION_TABLE = lambda {
18
+ {
19
+ last_error: ["ozip_last_error", [], Fiddle::TYPE_VOIDP],
20
+ free: ["ozip_free", %i[voidp size_t], Fiddle::TYPE_VOID],
21
+ compress: [
22
+ "ozip_compress",
23
+ %i[voidp voidp size_t int voidp],
24
+ Fiddle::TYPE_VOIDP,
25
+ ],
26
+ decompress: [
27
+ "ozip_decompress",
28
+ %i[voidp voidp size_t size_t voidp],
29
+ Fiddle::TYPE_VOIDP,
30
+ ],
31
+ }
32
+ }
33
+
34
+ FIDDLE_ARG_TYPES = {
35
+ pointer: ->(v) { v.nil? ? nil : Fiddle::Pointer[v] },
36
+ }.freeze
37
+ FIDDLE_RET_TYPES = {
38
+ pointer: Fiddle::TYPE_VOIDP,
39
+ void: Fiddle::TYPE_VOID,
40
+ size_t: Fiddle::TYPE_SIZE_T,
41
+ uint64: Fiddle::TYPE_VOIDP,
42
+ }.freeze
43
+
44
+ def initialize(path)
45
+ @handle = Fiddle.dlopen(path.to_s)
46
+ @funcs = FUNCTION_TABLE.call.each_with_object({}) do |(key, (name, args, ret)), h|
47
+ h[key] = Fiddle::Function.new(@handle[name], args, ret)
48
+ end
49
+ end
50
+
51
+ def compress(codec, data, level)
52
+ out_len = [0].pack("Q")
53
+ ptr = @funcs[:compress].call(codec, data, data.bytesize, level, out_len)
54
+ raise Rust::Error, last_error if ptr.null?
55
+
56
+ take(ptr, out_len.unpack1("Q"))
57
+ end
58
+
59
+ def decompress(codec, data, expected_len)
60
+ out_len = [0].pack("Q")
61
+ ptr = @funcs[:decompress].call(codec, data, data.bytesize, expected_len, out_len)
62
+ raise Rust::Error, last_error if ptr.null?
63
+
64
+ take(ptr, out_len.unpack1("Q"))
65
+ end
66
+
67
+ def last_error
68
+ ptr = @funcs[:last_error].call
69
+ ptr.null? ? "unknown error" : ptr.to_s
70
+ end
71
+
72
+ # Copy out + free one returned buffer: the buffer belongs to
73
+ # the Rust allocator and must never outlive ozip_free.
74
+ def take(ptr, len)
75
+ s = ptr.to_s(len)
76
+ @funcs[:free].call(ptr, len)
77
+ s
78
+ end
79
+
80
+ def take_buffer(ptr, len)
81
+ take(ptr, len)
82
+ end
83
+
84
+ # The Rust release this cdylib was built from, or nil when the
85
+ # symbol is absent (dylibs older than 0.21.109).
86
+ def dylib_version
87
+ func = Fiddle::Function.new(@handle["ozip_version"], [], Fiddle::TYPE_VOIDP)
88
+ ptr = func.call
89
+ ptr.null? ? nil : ptr.to_s
90
+ rescue StandardError
91
+ nil
92
+ end
93
+
94
+ # ---- whole-archive surface (ArchHandle) ----
95
+
96
+ def arch_open(data, password)
97
+ pw = password&.to_s
98
+ pw_ptr = pw && !pw.empty? ? Fiddle::Pointer[pw] : nil
99
+ ptr = Fiddle::Function.new(
100
+ @handle["ozip_arch_open"],
101
+ %i[voidp size_t voidp],
102
+ Fiddle::TYPE_VOIDP,
103
+ ).call(Fiddle::Pointer[data], data.bytesize, pw_ptr)
104
+ raise Rust::Error, last_error if ptr.null?
105
+
106
+ ptr
107
+ end
108
+
109
+ def arch_count(handle)
110
+ fn = Fiddle::Function.new(@handle["ozip_arch_count"], [:voidp], Fiddle::TYPE_SIZE_T)
111
+ fn.call(handle)
112
+ end
113
+
114
+ def arch_entry_name(handle, index)
115
+ ptr = Fiddle::Function.new(
116
+ @handle["ozip_arch_entry_name"], %i[voidp size_t], Fiddle::TYPE_VOIDP
117
+ ).call(handle, index)
118
+ ptr.null? ? nil : ptr.to_s
119
+ end
120
+
121
+ def arch_entry_size(handle, index)
122
+ Fiddle::Function.new(
123
+ @handle["ozip_arch_entry_size"], %i[voidp size_t], Fiddle::TYPE_VOIDP
124
+ ).call(handle, index)
125
+ end
126
+
127
+ def arch_read_entry(handle, index)
128
+ out_len = [0].pack("Q")
129
+ ptr = Fiddle::Function.new(
130
+ @handle["ozip_arch_read_entry"], %i[voidp size_t voidp], Fiddle::TYPE_VOIDP
131
+ ).call(handle, index, out_len)
132
+ raise Rust::Error, last_error if ptr.null?
133
+
134
+ take(ptr, out_len.unpack1("Q"))
135
+ end
136
+
137
+ def arch_close(handle)
138
+ Fiddle::Function.new(@handle["ozip_arch_close"], [:voidp], Fiddle::TYPE_VOID)
139
+ .call(handle)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -11,23 +11,32 @@ end
11
11
  module Omnizip
12
12
  module Implementations
13
13
  module Rust
14
- # The loaded cdylib: one handle, lazily opened, thread-safe
15
- # after initialization (Fiddle::Function is immutable once
16
- # built; compress/decompress are pure C calls).
14
+ # The loaded cdylib binding: one handle, lazily opened. The
15
+ # BINDING LAYER is chosen at load (leptris-ruby's model):
16
+ #
17
+ # 1. Fiddle (MRI stdlib — the zero-gem fast path)
18
+ # 2. the ffi gem (JRuby / TruffleRuby ship it bundled; MRI
19
+ # without fiddle — Ruby 4.0 without the gem — uses it too)
20
+ # 3. unavailable (pure-Ruby core everywhere)
21
+ #
22
+ # OMNIZIP_BINDING=fiddle|ffi forces a layer (spec seam; also the
23
+ # way to prove the ffi path on MRI). `instance` returns a
24
+ # FiddleLibrary or FfiLibrary — both expose the identical
25
+ # surface (compress / decompress / last_error / take_buffer /
26
+ # dylib_version / arch_*), so callers are binding-agnostic.
17
27
  class Library
18
- # Codecs the cdylib dispatches by name.
19
- # Names the cdylib dispatches: the codec-level acceleration
20
- # surface (both directions by default — Rust is the
21
- # authority; Ruby is the fallback).
28
+ # Codecs the cdylib dispatches by name: the codec-level
29
+ # acceleration surface (both directions by default — Rust is
30
+ # the authority; Ruby is the fallback).
22
31
  CODECS = %w[
23
32
  bzip2 zstd lzma xz lzma-alone lzip
24
33
  deflate deflate64 zlib gzip ppmd7 ppmd8
25
34
  ].freeze
26
35
 
27
36
  class << self
28
- # The shared handle, or nil when the cdylib cannot load.
29
- # Load failures are cached: a missing library is a stable
30
- # condition, not something to re-attempt per call.
37
+ # The shared binding, or nil when no layer can load. Load
38
+ # failures are cached per layer: a missing library is a
39
+ # stable condition, not something to re-attempt per call.
31
40
  def instance
32
41
  return @instance if defined?(@instance)
33
42
 
@@ -38,6 +47,13 @@ module Omnizip
38
47
  !instance.nil?
39
48
  end
40
49
 
50
+ # Which binding loaded: "fiddle", "ffi", or nil.
51
+ def binding_layer
52
+ return nil if instance.nil?
53
+
54
+ instance.class.name.end_with?("FfiLibrary") ? "ffi" : "fiddle"
55
+ end
56
+
41
57
  # Whether the cdylib dispatches this codec name — exact, or
42
58
  # a param-carrying prefix ("ppmd7:o6:m16777216" under
43
59
  # "ppmd7").
@@ -45,7 +61,7 @@ module Omnizip
45
61
  CODECS.any? { |c| name == c || name.start_with?("#{c}:") }
46
62
  end
47
63
 
48
- # Drop the cached handle so resolution reruns (test seam:
64
+ # Drop the cached binding so resolution reruns (test seam:
49
65
  # specs exercising fallback/absence paths need a clean
50
66
  # slate; production never calls this).
51
67
  def forget!
@@ -56,39 +72,12 @@ module Omnizip
56
72
  nil
57
73
  end
58
74
 
59
- # Built lazily: the Fiddle::TYPE_* constants only resolve
60
- # after a successful require; environments without fiddle
61
- # (Ruby 4.0 without the gem) never touch this table.
62
- def function_table
63
- {
64
- last_error: ["ozip_last_error", [], Fiddle::TYPE_VOIDP],
65
- free: ["ozip_free", %i[voidp size_t], Fiddle::TYPE_VOID],
66
- compress: [
67
- "ozip_compress",
68
- %i[voidp voidp size_t int voidp],
69
- Fiddle::TYPE_VOIDP,
70
- ],
71
- decompress: [
72
- "ozip_decompress",
73
- %i[voidp voidp size_t size_t voidp],
74
- Fiddle::TYPE_VOIDP,
75
- ],
76
- }
77
- end
78
-
79
75
  # The Rust release the loaded cdylib was built from (e.g.
80
- # "0.21.108"), read through the ozip_version symbol — the
76
+ # "0.21.112"), read through the ozip_version symbol — the
81
77
  # platform-gem smoke gate asserts it matches. nil when the
82
78
  # library is absent or predates the symbol.
83
79
  def dylib_version
84
- handle = instance&.instance_handle
85
- return nil if handle.nil?
86
-
87
- func = Fiddle::Function.new(handle["ozip_version"], [], Fiddle::TYPE_VOIDP)
88
- ptr = func.call
89
- ptr.null? ? nil : ptr.to_s
90
- rescue StandardError
91
- nil
80
+ instance&.dylib_version
92
81
  end
93
82
 
94
83
  # Resolve the cdylib path. Precedence:
@@ -133,12 +122,17 @@ module Omnizip
133
122
  path = resolve_path
134
123
  return nil if path.nil?
135
124
 
136
- handle = Fiddle.dlopen(path.to_s)
137
- funcs = function_table.each_with_object({}) do |(key, (name, args, ret)), h|
138
- h[key] = Fiddle::Function.new(handle[name], args, ret)
125
+ forced = ENV.fetch("OMNIZIP_BINDING", nil)
126
+ if forced != "ffi" && defined?(Fiddle)
127
+ return FiddleLibrary.new(path)
139
128
  end
140
- new(funcs, handle)
141
- rescue StandardError => e # includes Fiddle::DLError
129
+
130
+ # The ffi path: JRuby/TruffleRuby bundle the gem; MRI
131
+ # needs it installed (it is a runtime dependency). A
132
+ # forced layer must not silently fall back to another.
133
+ require "ffi"
134
+ FfiLibrary.new(path)
135
+ rescue LoadError, StandardError => e # Fiddle::DLError, FFI::LoadError, ...
142
136
  warn "omnizip: rust backend unavailable (#{e.message}); using pure Ruby" if ENV["OMNIZIP_BACKEND"] == "rust"
143
137
  nil
144
138
  end
@@ -152,65 +146,6 @@ module Omnizip
152
146
  end
153
147
  end
154
148
  end
155
-
156
- def initialize(functions, handle = nil)
157
- @functions = functions.freeze
158
- @handle = handle
159
- end
160
-
161
- def compress(codec, data, level)
162
- out_len = [0].pack("Q")
163
- ptr = call(:compress, codec, data, data.bytesize, level, out_len)
164
- take(ptr, out_len.unpack1("Q"))
165
- end
166
-
167
- def decompress(codec, data, expected_len)
168
- out_len = [0].pack("Q")
169
- ptr = call(:decompress, codec, data, data.bytesize, expected_len, out_len)
170
- take(ptr, out_len.unpack1("Q"))
171
- end
172
-
173
- # Bind one extra C symbol from the loaded cdylib (the archive
174
- # surface uses this; Fiddle::Function is immutable once built).
175
- def bind(symbol, args, ret)
176
- handle = instance_handle
177
- raise Error, "cdylib not loaded" if handle.nil?
178
-
179
- Fiddle::Function.new(handle[symbol.to_s], args, ret)
180
- end
181
-
182
- def last_error
183
- ptr = @functions[:last_error].call
184
- ptr.null? ? "unknown error" : ptr.to_s
185
- end
186
-
187
- # The raw Fiddle::Handle (for extra symbol binds).
188
- def instance_handle
189
- @handle
190
- end
191
-
192
- # Copy out + free one returned buffer (public seam for the
193
- # archive surface).
194
- def take_buffer(ptr, len)
195
- take(ptr, len)
196
- end
197
-
198
- private
199
-
200
- def call(name, codec, input, input_len, a, out_len)
201
- ptr = @functions[name].call(codec, input, input_len, a, out_len)
202
- raise Error, last_error if ptr.null?
203
-
204
- ptr
205
- end
206
-
207
- # Copy out + free in one breath: the buffer belongs to the
208
- # Rust allocator and must never outlive ozip_free.
209
- def take(ptr, len)
210
- string = ptr.to_s(len)
211
- @functions[:free].call(ptr, len)
212
- string
213
- end
214
149
  end
215
150
 
216
151
  # Raised when a codec call fails; #message carries the Rust
@@ -20,6 +20,8 @@ module Omnizip
20
20
  # fallback on any Rust error.
21
21
  module Rust
22
22
  autoload :Library, "omnizip/implementations/rust/library"
23
+ autoload :FiddleLibrary, "omnizip/implementations/rust/fiddle_library"
24
+ autoload :FfiLibrary, "omnizip/implementations/rust/ffi_library"
23
25
  autoload :Archive, "omnizip/implementations/rust/archive"
24
26
  end
25
27
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnizip
4
- VERSION = "0.4.9"
4
+ VERSION = "0.4.11"
5
5
  end