lz4-native-ruby 1.0.0 → 1.0.1
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/CLAUDE.md +292 -0
- data/LICENSE +55 -21
- data/README.md +109 -15
- data/{vendor/lz4/lib → ext/lz4_native}/Makefile +29 -24
- data/{vendor/lz4/lib → ext/lz4_native}/README.md +1 -1
- data/ext/lz4_native/extconf.rb +33 -0
- data/{vendor/lz4/lib → ext/lz4_native}/liblz4.pc.in +1 -0
- data/{vendor/lz4/lib → ext/lz4_native}/lz4.c +26 -23
- data/{vendor/lz4/lib → ext/lz4_native}/lz4.h +11 -9
- data/ext/lz4_native/lz4_native.c +442 -0
- data/ext/lz4_native/lz4file.c +362 -0
- data/{vendor/lz4/lib → ext/lz4_native}/lz4file.h +32 -9
- data/{vendor/lz4/lib → ext/lz4_native}/lz4frame.c +50 -21
- data/{vendor/lz4/lib → ext/lz4_native}/lz4frame.h +48 -28
- data/{vendor/lz4/lib → ext/lz4_native}/lz4frame_static.h +1 -1
- data/{vendor/lz4/lib → ext/lz4_native}/lz4hc.c +123 -60
- data/{vendor/lz4/lib → ext/lz4_native}/lz4hc.h +1 -1
- data/lib/lz4_native/lz4_native.so +0 -0
- data/lib/lz4_native/version.rb +3 -0
- data/lib/lz4_native.rb +47 -0
- data/test/test_helper.rb +4 -0
- data/test/test_lz4_basic.rb +100 -0
- data/test/test_lz4frame.rb +129 -0
- data/test/test_lz4hc.rb +75 -0
- metadata +50 -43
- data/ext/lz4/extconf.rb +0 -12
- data/ext/lz4/lz4_ext.c +0 -230
- data/lib/lz4/lz4_ext.so +0 -0
- data/lib/lz4/version.rb +0 -3
- data/lib/lz4.rb +0 -60
- data/vendor/lz4/lib/lz4file.c +0 -341
- /data/{vendor/lz4/lib → ext/lz4_native}/LICENSE +0 -0
- /data/{vendor/lz4/lib → ext/lz4_native}/dll/example/Makefile +0 -0
- /data/{vendor/lz4/lib → ext/lz4_native}/dll/example/README.md +0 -0
- /data/{vendor/lz4/lib → ext/lz4_native}/dll/example/fullbench-dll.sln +0 -0
- /data/{vendor/lz4/lib → ext/lz4_native}/dll/example/fullbench-dll.vcxproj +0 -0
- /data/{vendor/lz4/lib → ext/lz4_native}/liblz4-dll.rc.in +0 -0
- /data/{vendor/lz4/lib → ext/lz4_native}/xxhash.c +0 -0
- /data/{vendor/lz4/lib → ext/lz4_native}/xxhash.h +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class TestLZ4Frame < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
@test_data = "Hello, World! " * 100
|
|
6
|
+
@large_data = "A" * 100000
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_version
|
|
10
|
+
version = LZ4Native::LZ4Frame.version
|
|
11
|
+
assert version.is_a?(Integer)
|
|
12
|
+
assert version > 0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_compression_level_max
|
|
16
|
+
max_level = LZ4Native::LZ4Frame.compression_level_max
|
|
17
|
+
assert max_level.is_a?(Integer)
|
|
18
|
+
assert max_level >= 12
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_compress_decompress_frame_default
|
|
22
|
+
compressed = LZ4Native::LZ4Frame.compress_frame(@test_data)
|
|
23
|
+
assert compressed.is_a?(String)
|
|
24
|
+
assert compressed.bytesize > 0
|
|
25
|
+
|
|
26
|
+
decompressed = LZ4Native::LZ4Frame.decompress_frame(compressed)
|
|
27
|
+
assert_equal @test_data, decompressed
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_compress_decompress_frame_large_data
|
|
31
|
+
compressed = LZ4Native::LZ4Frame.compress_frame(@large_data)
|
|
32
|
+
assert compressed.bytesize < @large_data.bytesize
|
|
33
|
+
|
|
34
|
+
decompressed = LZ4Native::LZ4Frame.decompress_frame(compressed)
|
|
35
|
+
assert_equal @large_data, decompressed
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_frame_with_block_size_max64kb
|
|
39
|
+
compressed = LZ4Native::LZ4Frame.compress_frame(@test_data, block_size: :max64KB)
|
|
40
|
+
decompressed = LZ4Native::LZ4Frame.decompress_frame(compressed)
|
|
41
|
+
assert_equal @test_data, decompressed
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_frame_with_block_size_max256kb
|
|
45
|
+
compressed = LZ4Native::LZ4Frame.compress_frame(@test_data, block_size: :max256KB)
|
|
46
|
+
decompressed = LZ4Native::LZ4Frame.decompress_frame(compressed)
|
|
47
|
+
assert_equal @test_data, decompressed
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_frame_with_block_size_max1mb
|
|
51
|
+
compressed = LZ4Native::LZ4Frame.compress_frame(@test_data, block_size: :max1MB)
|
|
52
|
+
decompressed = LZ4Native::LZ4Frame.decompress_frame(compressed)
|
|
53
|
+
assert_equal @test_data, decompressed
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_frame_with_block_size_max4mb
|
|
57
|
+
compressed = LZ4Native::LZ4Frame.compress_frame(@test_data, block_size: :max4MB)
|
|
58
|
+
decompressed = LZ4Native::LZ4Frame.decompress_frame(compressed)
|
|
59
|
+
assert_equal @test_data, decompressed
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_frame_with_block_mode_linked
|
|
63
|
+
compressed = LZ4Native::LZ4Frame.compress_frame(@test_data, block_mode: :linked)
|
|
64
|
+
decompressed = LZ4Native::LZ4Frame.decompress_frame(compressed)
|
|
65
|
+
assert_equal @test_data, decompressed
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_frame_with_block_mode_independent
|
|
69
|
+
compressed = LZ4Native::LZ4Frame.compress_frame(@test_data, block_mode: :independent)
|
|
70
|
+
decompressed = LZ4Native::LZ4Frame.decompress_frame(compressed)
|
|
71
|
+
assert_equal @test_data, decompressed
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_frame_with_checksum
|
|
75
|
+
compressed = LZ4Native::LZ4Frame.compress_frame(@test_data, checksum: true)
|
|
76
|
+
decompressed = LZ4Native::LZ4Frame.decompress_frame(compressed)
|
|
77
|
+
assert_equal @test_data, decompressed
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_frame_with_compression_levels
|
|
81
|
+
[0, 1, 5, 9, 12].each do |level|
|
|
82
|
+
compressed = LZ4Native::LZ4Frame.compress_frame(@test_data, compression_level: level)
|
|
83
|
+
decompressed = LZ4Native::LZ4Frame.decompress_frame(compressed)
|
|
84
|
+
assert_equal @test_data, decompressed
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_frame_with_multiple_options
|
|
89
|
+
options = {
|
|
90
|
+
block_size: :max256KB,
|
|
91
|
+
block_mode: :independent,
|
|
92
|
+
checksum: true,
|
|
93
|
+
compression_level: 9
|
|
94
|
+
}
|
|
95
|
+
compressed = LZ4Native::LZ4Frame.compress_frame(@test_data, options)
|
|
96
|
+
decompressed = LZ4Native::LZ4Frame.decompress_frame(compressed)
|
|
97
|
+
assert_equal @test_data, decompressed
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_high_level_compress_frame
|
|
101
|
+
compressed = LZ4Native.compress_frame(@test_data)
|
|
102
|
+
assert compressed.is_a?(String)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_high_level_decompress_frame
|
|
106
|
+
compressed = LZ4Native.compress_frame(@test_data)
|
|
107
|
+
decompressed = LZ4Native.decompress_frame(compressed)
|
|
108
|
+
assert_equal @test_data, decompressed
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def test_high_level_compress_frame_with_options
|
|
112
|
+
compressed = LZ4Native.compress_frame(@test_data, checksum: true, compression_level: 9)
|
|
113
|
+
decompressed = LZ4Native.decompress_frame(compressed)
|
|
114
|
+
assert_equal @test_data, decompressed
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_decompress_corrupt_frame
|
|
118
|
+
corrupt_data = "this is not a valid lz4 frame"
|
|
119
|
+
assert_raises(LZ4Native::FrameError) do
|
|
120
|
+
LZ4Native::LZ4Frame.decompress_frame(corrupt_data)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_empty_data
|
|
125
|
+
compressed = LZ4Native::LZ4Frame.compress_frame("")
|
|
126
|
+
decompressed = LZ4Native::LZ4Frame.decompress_frame(compressed)
|
|
127
|
+
assert_equal "", decompressed
|
|
128
|
+
end
|
|
129
|
+
end
|
data/test/test_lz4hc.rb
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class TestLZ4HC < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
@test_data = "Hello, World! " * 100
|
|
6
|
+
@large_data = "The quick brown fox jumps over the lazy dog. " * 1000
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_compress_default_level
|
|
10
|
+
compressed = LZ4Native::LZ4HC.compress(@test_data)
|
|
11
|
+
assert compressed.is_a?(String)
|
|
12
|
+
assert compressed.bytesize < @test_data.bytesize
|
|
13
|
+
assert compressed.bytesize > 0
|
|
14
|
+
|
|
15
|
+
# Should be decompressible with standard LZ4
|
|
16
|
+
decompressed = LZ4Native::LZ4.decompress_safe(compressed, @test_data.bytesize * 2)
|
|
17
|
+
assert_equal @test_data, decompressed
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_compress_specific_levels
|
|
21
|
+
[1, 5, 9, 12].each do |level|
|
|
22
|
+
compressed = LZ4Native::LZ4HC.compress(@test_data, level)
|
|
23
|
+
assert compressed.is_a?(String)
|
|
24
|
+
assert compressed.bytesize > 0
|
|
25
|
+
|
|
26
|
+
decompressed = LZ4Native::LZ4.decompress_safe(compressed, @test_data.bytesize * 2)
|
|
27
|
+
assert_equal @test_data, decompressed
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_higher_compression_levels_produce_smaller_output
|
|
32
|
+
level_1 = LZ4Native::LZ4HC.compress(@large_data, 1)
|
|
33
|
+
level_12 = LZ4Native::LZ4HC.compress(@large_data, 12)
|
|
34
|
+
|
|
35
|
+
# Higher compression levels should generally produce smaller output
|
|
36
|
+
# Though this isn't guaranteed for all data, it's true for repetitive data
|
|
37
|
+
assert level_12.bytesize <= level_1.bytesize
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_hc_vs_default_compression_ratio
|
|
41
|
+
default_compressed = LZ4Native::LZ4.compress_default(@large_data)
|
|
42
|
+
hc_compressed = LZ4Native::LZ4HC.compress(@large_data, 9)
|
|
43
|
+
|
|
44
|
+
# HC should generally compress better than default
|
|
45
|
+
assert hc_compressed.bytesize <= default_compressed.bytesize
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_sizeof_state
|
|
49
|
+
size = LZ4Native::LZ4HC.sizeof_state
|
|
50
|
+
assert size.is_a?(Integer)
|
|
51
|
+
assert size > 0
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_high_level_compress_hc
|
|
55
|
+
compressed = LZ4Native.compress_hc(@test_data)
|
|
56
|
+
assert compressed.is_a?(String)
|
|
57
|
+
decompressed = LZ4Native.decompress(compressed, @test_data.bytesize * 2)
|
|
58
|
+
assert_equal @test_data, decompressed
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_high_level_compress_hc_with_level
|
|
62
|
+
compressed = LZ4Native.compress_hc(@test_data, 12)
|
|
63
|
+
assert compressed.is_a?(String)
|
|
64
|
+
decompressed = LZ4Native.decompress(compressed, @test_data.bytesize * 2)
|
|
65
|
+
assert_equal @test_data, decompressed
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_constants
|
|
69
|
+
assert LZ4Native::LZ4HC::CLEVEL_MIN.is_a?(Integer)
|
|
70
|
+
assert LZ4Native::LZ4HC::CLEVEL_DEFAULT.is_a?(Integer)
|
|
71
|
+
assert LZ4Native::LZ4HC::CLEVEL_MAX.is_a?(Integer)
|
|
72
|
+
assert LZ4Native::LZ4HC::CLEVEL_MIN < LZ4Native::LZ4HC::CLEVEL_DEFAULT
|
|
73
|
+
assert LZ4Native::LZ4HC::CLEVEL_DEFAULT < LZ4Native::LZ4HC::CLEVEL_MAX
|
|
74
|
+
end
|
|
75
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lz4-native-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Greninger
|
|
8
8
|
autorequire:
|
|
9
|
-
bindir:
|
|
9
|
+
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-11-
|
|
11
|
+
date: 2025-11-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -25,74 +25,81 @@ dependencies:
|
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '13.0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: minitest
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '5.0'
|
|
34
34
|
type: :development
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
40
|
+
version: '5.0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
42
|
+
name: rake-compiler
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '1.0'
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
55
|
-
description:
|
|
54
|
+
version: '1.0'
|
|
55
|
+
description: Complete Ruby bindings for LZ4 compression library, including lz4.h,
|
|
56
|
+
lz4hc.h, and lz4frame.h interfaces. Bundles the official LZ4 source code.
|
|
56
57
|
email:
|
|
57
|
-
-
|
|
58
|
+
- jgreninger@hotmail.com
|
|
58
59
|
executables: []
|
|
59
60
|
extensions:
|
|
60
|
-
- ext/
|
|
61
|
+
- ext/lz4_native/extconf.rb
|
|
61
62
|
extra_rdoc_files: []
|
|
62
63
|
files:
|
|
64
|
+
- CLAUDE.md
|
|
63
65
|
- LICENSE
|
|
64
66
|
- README.md
|
|
65
|
-
- ext/
|
|
66
|
-
- ext/
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
|
|
67
|
+
- ext/lz4_native/LICENSE
|
|
68
|
+
- ext/lz4_native/Makefile
|
|
69
|
+
- ext/lz4_native/README.md
|
|
70
|
+
- ext/lz4_native/dll/example/Makefile
|
|
71
|
+
- ext/lz4_native/dll/example/README.md
|
|
72
|
+
- ext/lz4_native/dll/example/fullbench-dll.sln
|
|
73
|
+
- ext/lz4_native/dll/example/fullbench-dll.vcxproj
|
|
74
|
+
- ext/lz4_native/extconf.rb
|
|
75
|
+
- ext/lz4_native/liblz4-dll.rc.in
|
|
76
|
+
- ext/lz4_native/liblz4.pc.in
|
|
77
|
+
- ext/lz4_native/lz4.c
|
|
78
|
+
- ext/lz4_native/lz4.h
|
|
79
|
+
- ext/lz4_native/lz4_native.c
|
|
80
|
+
- ext/lz4_native/lz4file.c
|
|
81
|
+
- ext/lz4_native/lz4file.h
|
|
82
|
+
- ext/lz4_native/lz4frame.c
|
|
83
|
+
- ext/lz4_native/lz4frame.h
|
|
84
|
+
- ext/lz4_native/lz4frame_static.h
|
|
85
|
+
- ext/lz4_native/lz4hc.c
|
|
86
|
+
- ext/lz4_native/lz4hc.h
|
|
87
|
+
- ext/lz4_native/xxhash.c
|
|
88
|
+
- ext/lz4_native/xxhash.h
|
|
89
|
+
- lib/lz4_native.rb
|
|
90
|
+
- lib/lz4_native/lz4_native.so
|
|
91
|
+
- lib/lz4_native/version.rb
|
|
92
|
+
- test/test_helper.rb
|
|
93
|
+
- test/test_lz4_basic.rb
|
|
94
|
+
- test/test_lz4frame.rb
|
|
95
|
+
- test/test_lz4hc.rb
|
|
96
|
+
homepage: https://github.com/jgreninger/lz4-native-ruby
|
|
91
97
|
licenses:
|
|
92
|
-
-
|
|
98
|
+
- BSD-2-Clause
|
|
93
99
|
metadata:
|
|
94
|
-
homepage_uri: https://github.com/
|
|
95
|
-
source_code_uri: https://github.com/
|
|
100
|
+
homepage_uri: https://github.com/jgreninger/lz4-native-ruby
|
|
101
|
+
source_code_uri: https://github.com/jgreninger/lz4-native-ruby
|
|
102
|
+
changelog_uri: https://github.com/jgreninger/lz4-native-ruby/blob/master/CHANGELOG.md
|
|
96
103
|
post_install_message:
|
|
97
104
|
rdoc_options: []
|
|
98
105
|
require_paths:
|
|
@@ -101,7 +108,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
101
108
|
requirements:
|
|
102
109
|
- - ">="
|
|
103
110
|
- !ruby/object:Gem::Version
|
|
104
|
-
version: 2.
|
|
111
|
+
version: 2.5.0
|
|
105
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
113
|
requirements:
|
|
107
114
|
- - ">="
|
|
@@ -111,5 +118,5 @@ requirements: []
|
|
|
111
118
|
rubygems_version: 3.4.20
|
|
112
119
|
signing_key:
|
|
113
120
|
specification_version: 4
|
|
114
|
-
summary:
|
|
121
|
+
summary: Native LZ4 compression bindings for Ruby
|
|
115
122
|
test_files: []
|
data/ext/lz4/extconf.rb
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
require "mkmf"
|
|
2
|
-
|
|
3
|
-
# Add LZ4 library source directory to include path
|
|
4
|
-
$INCFLAGS << " -I$(srcdir)/../../vendor/lz4/lib"
|
|
5
|
-
|
|
6
|
-
# Add LZ4 source directory to VPATH so make can find the source files
|
|
7
|
-
$VPATH << "$(srcdir)/../../vendor/lz4/lib"
|
|
8
|
-
|
|
9
|
-
# Add LZ4 source files to be compiled with just the base filenames
|
|
10
|
-
$srcs = ["lz4_ext.c", "lz4.c", "lz4hc.c"]
|
|
11
|
-
|
|
12
|
-
create_makefile("lz4/lz4_ext")
|
data/ext/lz4/lz4_ext.c
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
#include <ruby.h>
|
|
2
|
-
#include "lz4.h"
|
|
3
|
-
#include "lz4hc.h"
|
|
4
|
-
#include <stdio.h>
|
|
5
|
-
#include <stdlib.h>
|
|
6
|
-
#include <string.h>
|
|
7
|
-
|
|
8
|
-
static VALUE mLZ4;
|
|
9
|
-
|
|
10
|
-
#define CHUNK_SIZE (16 * 1024) // 16KB chunks
|
|
11
|
-
|
|
12
|
-
/*
|
|
13
|
-
* Compress a file using LZ4
|
|
14
|
-
*
|
|
15
|
-
* @param input_path [String] Path to input file
|
|
16
|
-
* @param output_path [String] Path to output file
|
|
17
|
-
* @param compression_level [Integer] Compression level (1-12)
|
|
18
|
-
* @return [Boolean] true on success
|
|
19
|
-
*/
|
|
20
|
-
static VALUE lz4_compress_file(VALUE self, VALUE input_path, VALUE output_path, VALUE compression_level) {
|
|
21
|
-
Check_Type(input_path, T_STRING);
|
|
22
|
-
Check_Type(output_path, T_STRING);
|
|
23
|
-
Check_Type(compression_level, T_FIXNUM);
|
|
24
|
-
|
|
25
|
-
const char* input_filename = StringValueCStr(input_path);
|
|
26
|
-
const char* output_filename = StringValueCStr(output_path);
|
|
27
|
-
int level = FIX2INT(compression_level);
|
|
28
|
-
|
|
29
|
-
// Validate compression level
|
|
30
|
-
if (level < 1 || level > LZ4HC_CLEVEL_MAX) {
|
|
31
|
-
rb_raise(rb_eArgError, "Compression level must be between 1 and %d", LZ4HC_CLEVEL_MAX);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
FILE* input_file = fopen(input_filename, "rb");
|
|
35
|
-
if (!input_file) {
|
|
36
|
-
rb_raise(rb_eIOError, "Cannot open input file: %s", input_filename);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
FILE* output_file = fopen(output_filename, "wb");
|
|
40
|
-
if (!output_file) {
|
|
41
|
-
fclose(input_file);
|
|
42
|
-
rb_raise(rb_eIOError, "Cannot open output file: %s", output_filename);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Get file size
|
|
46
|
-
fseek(input_file, 0, SEEK_END);
|
|
47
|
-
size_t input_size = ftell(input_file);
|
|
48
|
-
fseek(input_file, 0, SEEK_SET);
|
|
49
|
-
|
|
50
|
-
// Write uncompressed size as header (8 bytes for size_t)
|
|
51
|
-
fwrite(&input_size, sizeof(size_t), 1, output_file);
|
|
52
|
-
|
|
53
|
-
// Allocate buffers
|
|
54
|
-
char* input_buffer = (char*)malloc(CHUNK_SIZE);
|
|
55
|
-
int max_compressed_size = LZ4_compressBound(CHUNK_SIZE);
|
|
56
|
-
char* output_buffer = (char*)malloc(max_compressed_size);
|
|
57
|
-
|
|
58
|
-
if (!input_buffer || !output_buffer) {
|
|
59
|
-
free(input_buffer);
|
|
60
|
-
free(output_buffer);
|
|
61
|
-
fclose(input_file);
|
|
62
|
-
fclose(output_file);
|
|
63
|
-
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Compress file in chunks
|
|
67
|
-
size_t bytes_read;
|
|
68
|
-
while ((bytes_read = fread(input_buffer, 1, CHUNK_SIZE, input_file)) > 0) {
|
|
69
|
-
int compressed_size;
|
|
70
|
-
|
|
71
|
-
if (level >= LZ4HC_CLEVEL_MIN) {
|
|
72
|
-
compressed_size = LZ4_compress_HC(input_buffer, output_buffer, bytes_read, max_compressed_size, level);
|
|
73
|
-
} else {
|
|
74
|
-
compressed_size = LZ4_compress_default(input_buffer, output_buffer, bytes_read, max_compressed_size);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (compressed_size <= 0) {
|
|
78
|
-
free(input_buffer);
|
|
79
|
-
free(output_buffer);
|
|
80
|
-
fclose(input_file);
|
|
81
|
-
fclose(output_file);
|
|
82
|
-
rb_raise(rb_eRuntimeError, "Compression failed");
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Write chunk header: original size and compressed size
|
|
86
|
-
uint32_t chunk_original = (uint32_t)bytes_read;
|
|
87
|
-
uint32_t chunk_compressed = (uint32_t)compressed_size;
|
|
88
|
-
fwrite(&chunk_original, sizeof(uint32_t), 1, output_file);
|
|
89
|
-
fwrite(&chunk_compressed, sizeof(uint32_t), 1, output_file);
|
|
90
|
-
fwrite(output_buffer, 1, compressed_size, output_file);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
free(input_buffer);
|
|
94
|
-
free(output_buffer);
|
|
95
|
-
fclose(input_file);
|
|
96
|
-
fclose(output_file);
|
|
97
|
-
|
|
98
|
-
return Qtrue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/*
|
|
102
|
-
* Decompress a file using LZ4
|
|
103
|
-
*
|
|
104
|
-
* @param input_path [String] Path to compressed input file
|
|
105
|
-
* @param output_path [String] Path to output file
|
|
106
|
-
* @return [Boolean] true on success
|
|
107
|
-
*/
|
|
108
|
-
static VALUE lz4_decompress_file(VALUE self, VALUE input_path, VALUE output_path) {
|
|
109
|
-
Check_Type(input_path, T_STRING);
|
|
110
|
-
Check_Type(output_path, T_STRING);
|
|
111
|
-
|
|
112
|
-
const char* input_filename = StringValueCStr(input_path);
|
|
113
|
-
const char* output_filename = StringValueCStr(output_path);
|
|
114
|
-
|
|
115
|
-
FILE* input_file = fopen(input_filename, "rb");
|
|
116
|
-
if (!input_file) {
|
|
117
|
-
rb_raise(rb_eIOError, "Cannot open input file: %s", input_filename);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
FILE* output_file = fopen(output_filename, "wb");
|
|
121
|
-
if (!output_file) {
|
|
122
|
-
fclose(input_file);
|
|
123
|
-
rb_raise(rb_eIOError, "Cannot open output file: %s", output_filename);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Read uncompressed size header
|
|
127
|
-
size_t total_size;
|
|
128
|
-
if (fread(&total_size, sizeof(size_t), 1, input_file) != 1) {
|
|
129
|
-
fclose(input_file);
|
|
130
|
-
fclose(output_file);
|
|
131
|
-
rb_raise(rb_eRuntimeError, "Failed to read file header");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Allocate buffers
|
|
135
|
-
char* input_buffer = (char*)malloc(LZ4_compressBound(CHUNK_SIZE));
|
|
136
|
-
char* output_buffer = (char*)malloc(CHUNK_SIZE);
|
|
137
|
-
|
|
138
|
-
if (!input_buffer || !output_buffer) {
|
|
139
|
-
free(input_buffer);
|
|
140
|
-
free(output_buffer);
|
|
141
|
-
fclose(input_file);
|
|
142
|
-
fclose(output_file);
|
|
143
|
-
rb_raise(rb_eNoMemError, "Failed to allocate memory");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Decompress file in chunks
|
|
147
|
-
while (!feof(input_file)) {
|
|
148
|
-
uint32_t chunk_original, chunk_compressed;
|
|
149
|
-
|
|
150
|
-
// Read chunk header
|
|
151
|
-
if (fread(&chunk_original, sizeof(uint32_t), 1, input_file) != 1) {
|
|
152
|
-
break; // End of file
|
|
153
|
-
}
|
|
154
|
-
if (fread(&chunk_compressed, sizeof(uint32_t), 1, input_file) != 1) {
|
|
155
|
-
free(input_buffer);
|
|
156
|
-
free(output_buffer);
|
|
157
|
-
fclose(input_file);
|
|
158
|
-
fclose(output_file);
|
|
159
|
-
rb_raise(rb_eRuntimeError, "Failed to read chunk header");
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Read compressed data
|
|
163
|
-
if (fread(input_buffer, 1, chunk_compressed, input_file) != chunk_compressed) {
|
|
164
|
-
free(input_buffer);
|
|
165
|
-
free(output_buffer);
|
|
166
|
-
fclose(input_file);
|
|
167
|
-
fclose(output_file);
|
|
168
|
-
rb_raise(rb_eRuntimeError, "Failed to read compressed data");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Decompress
|
|
172
|
-
int decompressed_size = LZ4_decompress_safe(input_buffer, output_buffer, chunk_compressed, chunk_original);
|
|
173
|
-
|
|
174
|
-
if (decompressed_size < 0) {
|
|
175
|
-
free(input_buffer);
|
|
176
|
-
free(output_buffer);
|
|
177
|
-
fclose(input_file);
|
|
178
|
-
fclose(output_file);
|
|
179
|
-
rb_raise(rb_eRuntimeError, "Decompression failed");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (decompressed_size != chunk_original) {
|
|
183
|
-
free(input_buffer);
|
|
184
|
-
free(output_buffer);
|
|
185
|
-
fclose(input_file);
|
|
186
|
-
fclose(output_file);
|
|
187
|
-
rb_raise(rb_eRuntimeError, "Decompressed size mismatch");
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Write decompressed data
|
|
191
|
-
fwrite(output_buffer, 1, decompressed_size, output_file);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
free(input_buffer);
|
|
195
|
-
free(output_buffer);
|
|
196
|
-
fclose(input_file);
|
|
197
|
-
fclose(output_file);
|
|
198
|
-
|
|
199
|
-
return Qtrue;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/*
|
|
203
|
-
* Get LZ4 version string
|
|
204
|
-
*
|
|
205
|
-
* @return [String] LZ4 version
|
|
206
|
-
*/
|
|
207
|
-
static VALUE lz4_version(VALUE self) {
|
|
208
|
-
int version = LZ4_versionNumber();
|
|
209
|
-
int major = version / 10000;
|
|
210
|
-
int minor = (version % 10000) / 100;
|
|
211
|
-
int patch = version % 100;
|
|
212
|
-
|
|
213
|
-
char version_str[32];
|
|
214
|
-
snprintf(version_str, sizeof(version_str), "%d.%d.%d", major, minor, patch);
|
|
215
|
-
|
|
216
|
-
return rb_str_new_cstr(version_str);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
void Init_lz4_ext(void) {
|
|
220
|
-
mLZ4 = rb_define_module("LZ4");
|
|
221
|
-
|
|
222
|
-
rb_define_singleton_method(mLZ4, "compress_file", lz4_compress_file, 3);
|
|
223
|
-
rb_define_singleton_method(mLZ4, "decompress_file", lz4_decompress_file, 2);
|
|
224
|
-
rb_define_singleton_method(mLZ4, "version", lz4_version, 0);
|
|
225
|
-
|
|
226
|
-
// Define constants
|
|
227
|
-
rb_define_const(mLZ4, "MIN_COMPRESSION_LEVEL", INT2FIX(1));
|
|
228
|
-
rb_define_const(mLZ4, "MAX_COMPRESSION_LEVEL", INT2FIX(LZ4HC_CLEVEL_MAX));
|
|
229
|
-
rb_define_const(mLZ4, "DEFAULT_COMPRESSION_LEVEL", INT2FIX(9));
|
|
230
|
-
}
|
data/lib/lz4/lz4_ext.so
DELETED
|
Binary file
|
data/lib/lz4/version.rb
DELETED
data/lib/lz4.rb
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
require "lz4/lz4_ext"
|
|
2
|
-
require "lz4/version"
|
|
3
|
-
|
|
4
|
-
module LZ4
|
|
5
|
-
class Error < StandardError; end
|
|
6
|
-
class CompressionError < Error; end
|
|
7
|
-
class DecompressionError < Error; end
|
|
8
|
-
|
|
9
|
-
class << self
|
|
10
|
-
# Save references to the C extension methods
|
|
11
|
-
alias_method :_compress_file_ext, :compress_file
|
|
12
|
-
alias_method :_decompress_file_ext, :decompress_file
|
|
13
|
-
|
|
14
|
-
# Compress a file using LZ4
|
|
15
|
-
#
|
|
16
|
-
# @param input_path [String] Path to the input file
|
|
17
|
-
# @param output_path [String] Path to the output file
|
|
18
|
-
# @param compression_level [Integer] Compression level (1-12), default: 9
|
|
19
|
-
# @return [Boolean] true on success
|
|
20
|
-
# @raise [ArgumentError] if compression level is invalid
|
|
21
|
-
# @raise [IOError] if file operations fail
|
|
22
|
-
# @raise [CompressionError] if compression fails
|
|
23
|
-
def compress_file(input_path, output_path, compression_level: DEFAULT_COMPRESSION_LEVEL)
|
|
24
|
-
raise ArgumentError, "input_path must be a String" unless input_path.is_a?(String)
|
|
25
|
-
raise ArgumentError, "output_path must be a String" unless output_path.is_a?(String)
|
|
26
|
-
raise ArgumentError, "Input file does not exist: #{input_path}" unless File.exist?(input_path)
|
|
27
|
-
|
|
28
|
-
compression_level = compression_level.to_i
|
|
29
|
-
|
|
30
|
-
begin
|
|
31
|
-
_compress_file_ext(input_path, output_path, compression_level)
|
|
32
|
-
rescue ArgumentError
|
|
33
|
-
# Let ArgumentError pass through (e.g., invalid compression level)
|
|
34
|
-
raise
|
|
35
|
-
rescue => e
|
|
36
|
-
raise CompressionError, "Failed to compress file: #{e.message}"
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Decompress a file using LZ4
|
|
41
|
-
#
|
|
42
|
-
# @param input_path [String] Path to the compressed input file
|
|
43
|
-
# @param output_path [String] Path to the output file
|
|
44
|
-
# @return [Boolean] true on success
|
|
45
|
-
# @raise [ArgumentError] if arguments are invalid
|
|
46
|
-
# @raise [IOError] if file operations fail
|
|
47
|
-
# @raise [DecompressionError] if decompression fails
|
|
48
|
-
def decompress_file(input_path, output_path)
|
|
49
|
-
raise ArgumentError, "input_path must be a String" unless input_path.is_a?(String)
|
|
50
|
-
raise ArgumentError, "output_path must be a String" unless output_path.is_a?(String)
|
|
51
|
-
raise ArgumentError, "Input file does not exist: #{input_path}" unless File.exist?(input_path)
|
|
52
|
-
|
|
53
|
-
begin
|
|
54
|
-
_decompress_file_ext(input_path, output_path)
|
|
55
|
-
rescue => e
|
|
56
|
-
raise DecompressionError, "Failed to decompress file: #{e.message}"
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|