Skip to content

Commit 204634e

Browse files
authored
Merge pull request #109 from SpringMT/v2
V2
2 parents 921ea17 + 3109328 commit 204634e

File tree

11 files changed

+139
-180
lines changed

11 files changed

+139
-180
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ Fork from https://github.com/jarredholman/ruby-zstd.
1212
## Zstd version
1313
[v1.5.7](https://github.com/facebook/zstd/tree/v1.5.7)
1414

15+
## Versioning Policy
16+
17+
Starting from v2.0.0, this gem follows Semantic Versioning.
18+
19+
- **Major version** (X.0.0): Breaking changes to the API
20+
- **Minor version** (X.Y.0): New features, including Zstd library version updates
21+
- **Patch version** (X.Y.Z): Bug fixes and other backward-compatible changes
22+
23+
### Zstd Library Updates
24+
25+
Updates to the underlying Zstd library version will be released as **minor version** updates, as they may introduce new features or performance improvements while maintaining backward compatibility.
26+
27+
**Note**: Versions prior to v2.0.0 followed the Zstd library versioning scheme with an additional patch number (e.g., 1.5.6.2). This approach has been replaced with semantic versioning to provide clearer expectations for API stability.
28+
1529
## Installation
1630

1731
Add this line to your application's Gemfile:
@@ -155,6 +169,20 @@ result << stream.decompress(cstr[10..-1])
155169

156170
DDict can also be specified to `dict:`.
157171

172+
#### Streaming Decompression with Position Tracking
173+
174+
If you need to know how much of the input data was consumed during decompression, you can use the `decompress_with_pos` method:
175+
176+
```ruby
177+
cstr = "" # Compressed data
178+
stream = Zstd::StreamingDecompress.new
179+
result, consumed_bytes = stream.decompress_with_pos(cstr[0, 10])
180+
# result contains the decompressed data
181+
# consumed_bytes contains the number of bytes from input that were processed
182+
```
183+
184+
This is particularly useful when processing streaming data where you need to track the exact position in the input stream.
185+
158186
### Skippable frame
159187

160188
```ruby

benchmarks/Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ DEPENDENCIES
1616
snappy
1717

1818
BUNDLED WITH
19-
2.5.7
19+
2.5.9

examples/sinatra/Gemfile.lock

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
11
PATH
22
remote: ../..
33
specs:
4-
zstd-ruby (1.5.6.2)
4+
zstd-ruby (2.0.0.pre.preview1)
55

66
GEM
77
remote: https://rubygems.org/
88
specs:
9-
base64 (0.2.0)
10-
mustermann (3.0.0)
9+
base64 (0.3.0)
10+
logger (1.7.0)
11+
mustermann (3.0.3)
1112
ruby2_keywords (~> 0.0.1)
12-
rack (3.0.10)
13-
rack-protection (4.0.0)
13+
rack (3.2.0)
14+
rack-protection (4.1.1)
1415
base64 (>= 0.1.0)
16+
logger (>= 1.6.0)
1517
rack (>= 3.0.0, < 4)
16-
rack-session (2.0.0)
18+
rack-session (2.1.1)
19+
base64 (>= 0.1.0)
1720
rack (>= 3.0.0)
18-
rackup (2.1.0)
21+
rackup (2.2.1)
1922
rack (>= 3)
20-
webrick (~> 1.8)
2123
ruby2_keywords (0.0.5)
22-
sinatra (4.0.0)
24+
sinatra (4.1.1)
25+
logger (>= 1.6.0)
2326
mustermann (~> 3.0)
2427
rack (>= 3.0.0, < 4)
25-
rack-protection (= 4.0.0)
28+
rack-protection (= 4.1.1)
2629
rack-session (>= 2.0.0, < 3)
2730
tilt (~> 2.0)
28-
tilt (2.3.0)
29-
webrick (1.8.1)
31+
tilt (2.6.1)
3032

3133
PLATFORMS
3234
arm64-darwin-21
@@ -38,4 +40,4 @@ DEPENDENCIES
3840
zstd-ruby!
3941

4042
BUNDLED WITH
41-
2.5.7
43+
2.5.9

ext/zstdruby/common.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ static int convert_compression_level(VALUE compression_level_value)
1818
return NUM2INT(compression_level_value);
1919
}
2020

21-
static void set_compress_params(ZSTD_CCtx* const ctx, VALUE level_from_args, VALUE kwargs)
21+
static void set_compress_params(ZSTD_CCtx* const ctx, VALUE kwargs)
2222
{
2323
ID kwargs_keys[2];
2424
kwargs_keys[0] = rb_intern("level");
@@ -29,9 +29,6 @@ static void set_compress_params(ZSTD_CCtx* const ctx, VALUE level_from_args, VAL
2929
int compression_level = ZSTD_CLEVEL_DEFAULT;
3030
if (kwargs_values[0] != Qundef && kwargs_values[0] != Qnil) {
3131
compression_level = convert_compression_level(kwargs_values[0]);
32-
} else if (!NIL_P(level_from_args)) {
33-
rb_warn("`level` in args is deprecated; use keyword args `level:` instead.");
34-
compression_level = convert_compression_level(level_from_args);
3532
}
3633
ZSTD_CCtx_setParameter(ctx, ZSTD_c_compressionLevel, compression_level);
3734

ext/zstdruby/streaming_compress.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@ static VALUE
7171
rb_streaming_compress_initialize(int argc, VALUE *argv, VALUE obj)
7272
{
7373
VALUE kwargs;
74-
VALUE compression_level_value;
75-
rb_scan_args(argc, argv, "01:", &compression_level_value, &kwargs);
74+
rb_scan_args(argc, argv, "00:", &kwargs);
7675

7776
struct streaming_compress_t* sc;
7877
TypedData_Get_Struct(obj, struct streaming_compress_t, &streaming_compress_type, sc);
@@ -82,7 +81,7 @@ rb_streaming_compress_initialize(int argc, VALUE *argv, VALUE obj)
8281
if (ctx == NULL) {
8382
rb_raise(rb_eRuntimeError, "%s", "ZSTD_createCCtx error");
8483
}
85-
set_compress_params(ctx, compression_level_value, kwargs);
84+
set_compress_params(ctx, kwargs);
8685

8786
sc->ctx = ctx;
8887
sc->buf = rb_str_new(NULL, buffOutSize);

ext/zstdruby/streaming_decompress.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,27 @@ rb_streaming_decompress_decompress(VALUE obj, VALUE src)
113113
return result;
114114
}
115115

116+
static VALUE
117+
rb_streaming_decompress_decompress_with_pos(VALUE obj, VALUE src)
118+
{
119+
StringValue(src);
120+
const char* input_data = RSTRING_PTR(src);
121+
size_t input_size = RSTRING_LEN(src);
122+
ZSTD_inBuffer input = { input_data, input_size, 0 };
123+
124+
struct streaming_decompress_t* sd;
125+
TypedData_Get_Struct(obj, struct streaming_decompress_t, &streaming_decompress_type, sd);
126+
const char* output_data = RSTRING_PTR(sd->buf);
127+
VALUE result = rb_str_new(0, 0);
128+
ZSTD_outBuffer output = { (void*)output_data, sd->buf_size, 0 };
129+
size_t const ret = zstd_stream_decompress(sd->dctx, &output, &input, false);
130+
if (ZSTD_isError(ret)) {
131+
rb_raise(rb_eRuntimeError, "decompress error error code: %s", ZSTD_getErrorName(ret));
132+
}
133+
rb_str_cat(result, output.dst, output.pos);
134+
return rb_ary_new_from_args(2, result, ULONG2NUM(input.pos));
135+
}
136+
116137
extern VALUE rb_mZstd, cStreamingDecompress;
117138
void
118139
zstd_ruby_streaming_decompress_init(void)
@@ -121,4 +142,5 @@ zstd_ruby_streaming_decompress_init(void)
121142
rb_define_alloc_func(cStreamingDecompress, rb_streaming_decompress_allocate);
122143
rb_define_method(cStreamingDecompress, "initialize", rb_streaming_decompress_initialize, -1);
123144
rb_define_method(cStreamingDecompress, "decompress", rb_streaming_decompress_decompress, 1);
145+
rb_define_method(cStreamingDecompress, "decompress_with_pos", rb_streaming_decompress_decompress_with_pos, 1);
124146
}

ext/zstdruby/zstdruby.c

Lines changed: 2 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,15 @@ static VALUE zstdVersion(VALUE self)
1111
static VALUE rb_compress(int argc, VALUE *argv, VALUE self)
1212
{
1313
VALUE input_value;
14-
VALUE compression_level_value;
1514
VALUE kwargs;
16-
rb_scan_args(argc, argv, "11:", &input_value, &compression_level_value, &kwargs);
15+
rb_scan_args(argc, argv, "10:", &input_value, &kwargs);
1716

1817
ZSTD_CCtx* const ctx = ZSTD_createCCtx();
1918
if (ctx == NULL) {
2019
rb_raise(rb_eRuntimeError, "%s", "ZSTD_createCCtx error");
2120
}
2221

23-
set_compress_params(ctx, compression_level_value, kwargs);
22+
set_compress_params(ctx, kwargs);
2423

2524
StringValue(input_value);
2625
char* input_data = RSTRING_PTR(input_value);
@@ -40,51 +39,6 @@ static VALUE rb_compress(int argc, VALUE *argv, VALUE self)
4039
return output;
4140
}
4241

43-
static VALUE rb_compress_using_dict(int argc, VALUE *argv, VALUE self)
44-
{
45-
rb_warn("Zstd.compress_using_dict is deprecated; use Zstd.compress with `dict:` instead.");
46-
VALUE input_value;
47-
VALUE dict;
48-
VALUE compression_level_value;
49-
rb_scan_args(argc, argv, "21", &input_value, &dict, &compression_level_value);
50-
int compression_level = convert_compression_level(compression_level_value);
51-
52-
StringValue(input_value);
53-
char* input_data = RSTRING_PTR(input_value);
54-
size_t input_size = RSTRING_LEN(input_value);
55-
size_t max_compressed_size = ZSTD_compressBound(input_size);
56-
57-
char* dict_buffer = RSTRING_PTR(dict);
58-
size_t dict_size = RSTRING_LEN(dict);
59-
60-
ZSTD_CDict* const cdict = ZSTD_createCDict(dict_buffer, dict_size, compression_level);
61-
if (cdict == NULL) {
62-
rb_raise(rb_eRuntimeError, "%s", "ZSTD_createCDict failed");
63-
}
64-
ZSTD_CCtx* const ctx = ZSTD_createCCtx();
65-
if (ctx == NULL) {
66-
ZSTD_freeCDict(cdict);
67-
rb_raise(rb_eRuntimeError, "%s", "ZSTD_createCCtx failed");
68-
}
69-
70-
VALUE output = rb_str_new(NULL, max_compressed_size);
71-
char* output_data = RSTRING_PTR(output);
72-
size_t const compressed_size = ZSTD_compress_usingCDict(ctx, (void*)output_data, max_compressed_size,
73-
(void*)input_data, input_size, cdict);
74-
75-
if (ZSTD_isError(compressed_size)) {
76-
ZSTD_freeCDict(cdict);
77-
ZSTD_freeCCtx(ctx);
78-
rb_raise(rb_eRuntimeError, "%s: %s", "compress failed", ZSTD_getErrorName(compressed_size));
79-
}
80-
81-
rb_str_resize(output, compressed_size);
82-
ZSTD_freeCDict(cdict);
83-
ZSTD_freeCCtx(ctx);
84-
return output;
85-
}
86-
87-
8842
static VALUE decompress_buffered(ZSTD_DCtx* dctx, const char* input_data, size_t input_size)
8943
{
9044
ZSTD_inBuffer input = { input_data, input_size, 0 };
@@ -142,59 +96,6 @@ static VALUE rb_decompress(int argc, VALUE *argv, VALUE self)
14296
return output;
14397
}
14498

145-
static VALUE rb_decompress_using_dict(int argc, VALUE *argv, VALUE self)
146-
{
147-
rb_warn("Zstd.decompress_using_dict is deprecated; use Zstd.decompress with `dict:` instead.");
148-
VALUE input_value;
149-
VALUE dict;
150-
rb_scan_args(argc, argv, "20", &input_value, &dict);
151-
152-
StringValue(input_value);
153-
char* input_data = RSTRING_PTR(input_value);
154-
size_t input_size = RSTRING_LEN(input_value);
155-
156-
char* dict_buffer = RSTRING_PTR(dict);
157-
size_t dict_size = RSTRING_LEN(dict);
158-
ZSTD_DDict* const ddict = ZSTD_createDDict(dict_buffer, dict_size);
159-
if (ddict == NULL) {
160-
rb_raise(rb_eRuntimeError, "%s", "ZSTD_createDDict failed");
161-
}
162-
unsigned const expected_dict_id = ZSTD_getDictID_fromDDict(ddict);
163-
unsigned const actual_dict_id = ZSTD_getDictID_fromFrame(input_data, input_size);
164-
if (expected_dict_id != actual_dict_id) {
165-
ZSTD_freeDDict(ddict);
166-
rb_raise(rb_eRuntimeError, "DictID mismatch");
167-
}
168-
169-
ZSTD_DCtx* const ctx = ZSTD_createDCtx();
170-
if (ctx == NULL) {
171-
ZSTD_freeDDict(ddict);
172-
rb_raise(rb_eRuntimeError, "%s", "ZSTD_createDCtx failed");
173-
}
174-
175-
unsigned long long const uncompressed_size = ZSTD_getFrameContentSize(input_data, input_size);
176-
if (uncompressed_size == ZSTD_CONTENTSIZE_ERROR) {
177-
ZSTD_freeDDict(ddict);
178-
ZSTD_freeDCtx(ctx);
179-
rb_raise(rb_eRuntimeError, "%s: %s", "not compressed by zstd", ZSTD_getErrorName(uncompressed_size));
180-
}
181-
if (uncompressed_size == ZSTD_CONTENTSIZE_UNKNOWN) {
182-
return decompress_buffered(ctx, input_data, input_size);
183-
}
184-
185-
VALUE output = rb_str_new(NULL, uncompressed_size);
186-
char* output_data = RSTRING_PTR(output);
187-
size_t const decompress_size = ZSTD_decompress_usingDDict(ctx, output_data, uncompressed_size, input_data, input_size, ddict);
188-
if (ZSTD_isError(decompress_size)) {
189-
ZSTD_freeDDict(ddict);
190-
ZSTD_freeDCtx(ctx);
191-
rb_raise(rb_eRuntimeError, "%s: %s", "decompress error", ZSTD_getErrorName(decompress_size));
192-
}
193-
ZSTD_freeDDict(ddict);
194-
ZSTD_freeDCtx(ctx);
195-
return output;
196-
}
197-
19899
static void free_cdict(void *dict)
199100
{
200101
ZSTD_freeCDict(dict);
@@ -284,9 +185,7 @@ zstd_ruby_init(void)
284185
{
285186
rb_define_module_function(rb_mZstd, "zstd_version", zstdVersion, 0);
286187
rb_define_module_function(rb_mZstd, "compress", rb_compress, -1);
287-
rb_define_module_function(rb_mZstd, "compress_using_dict", rb_compress_using_dict, -1);
288188
rb_define_module_function(rb_mZstd, "decompress", rb_decompress, -1);
289-
rb_define_module_function(rb_mZstd, "decompress_using_dict", rb_decompress_using_dict, -1);
290189

291190
rb_define_alloc_func(rb_cCDict, rb_cdict_alloc);
292191
rb_define_private_method(rb_cCDict, "initialize", rb_cdict_initialize, -1);

lib/zstd-ruby/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Zstd
2-
VERSION = "1.5.7.0"
2+
VERSION = "2.0.0-preview1"
33
end

spec/zstd-ruby-streaming-decompress_spec.rb

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,71 @@
1717
end
1818
end
1919

20+
describe 'decompress_with_pos' do
21+
it 'should return decompressed data and consumed input position' do
22+
str = "hello world test data"
23+
cstr = Zstd.compress(str)
24+
stream = Zstd::StreamingDecompress.new
25+
26+
# Test with partial input
27+
result_array = stream.decompress_with_pos(cstr[0, 10])
28+
expect(result_array).to be_an(Array)
29+
expect(result_array.length).to eq(2)
30+
31+
decompressed_data = result_array[0]
32+
consumed_bytes = result_array[1]
33+
34+
expect(decompressed_data).to be_a(String)
35+
expect(consumed_bytes).to be_a(Integer)
36+
expect(consumed_bytes).to be > 0
37+
expect(consumed_bytes).to be <= 10
38+
end
39+
40+
it 'should work with complete compressed data' do
41+
str = "foo bar buzz"
42+
cstr = Zstd.compress(str)
43+
stream = Zstd::StreamingDecompress.new
44+
45+
result_array = stream.decompress_with_pos(cstr)
46+
decompressed_data = result_array[0]
47+
consumed_bytes = result_array[1]
48+
49+
expect(decompressed_data).to eq(str)
50+
expect(consumed_bytes).to eq(cstr.length)
51+
end
52+
53+
it 'should work with multiple calls' do
54+
str = "test data for multiple calls"
55+
cstr = Zstd.compress(str)
56+
stream = Zstd::StreamingDecompress.new
57+
58+
result = ''
59+
total_consumed = 0
60+
chunk_size = 5
61+
62+
while total_consumed < cstr.length
63+
remaining_data = cstr[total_consumed..-1]
64+
chunk = remaining_data[0, chunk_size]
65+
66+
result_array = stream.decompress_with_pos(chunk)
67+
decompressed_chunk = result_array[0]
68+
consumed_bytes = result_array[1]
69+
70+
result << decompressed_chunk
71+
total_consumed += consumed_bytes
72+
73+
expect(consumed_bytes).to be > 0
74+
expect(consumed_bytes).to be <= chunk.length
75+
76+
# If we consumed less than the chunk size, we might be done or need more data
77+
break if consumed_bytes < chunk.length && total_consumed == cstr.length
78+
end
79+
80+
expect(result).to eq(str)
81+
expect(total_consumed).to eq(cstr.length)
82+
end
83+
end
84+
2085
describe 'streaming decompress + GC.compact' do
2186
it 'shoud work' do
2287
# str = SecureRandom.hex(150)
@@ -109,4 +174,3 @@
109174
end
110175
end
111176
end
112-

0 commit comments

Comments
 (0)