archive_r_ruby 0.1.0 → 0.1.2
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/{LICENSE → LICENSE.txt} +28 -7
- data/ext/archive_r/archive_r_ext.cc +273 -77
- data/ext/archive_r/vendor/archive_r/LICENSE.txt +28 -7
- data/lib/archive_r.rb +1 -1
- metadata +8 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c84dab51e8d84438b6eca7d3d96baa9398ea8606307e423b2416b7f12f263ecf
|
|
4
|
+
data.tar.gz: 2b1df674056604436d193e8bfa871bfb823a41c2ba0edc6dd220d30c958f5ef0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15230cc63edf0704c6d7fd129b0fd5460eb42c474c34eeb5cd6ce834095819ea5202541fdada77a00f4aa63c0379fb152c8c10047b4386a02522858c6579ab71
|
|
7
|
+
data.tar.gz: 8ec342c7bb690af3daeeb4774db7e2ab838273bc36bbb964fbe3cb825cddb0963d1700948e03c5cb1dc7b4ece6f513d711a42e7e4cbee5e4cc061586657d9306
|
data/{LICENSE → LICENSE.txt}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
archive_r License
|
|
2
|
-
Version: 0.1.
|
|
2
|
+
Version: 0.1.2 (2025-12-02)
|
|
3
3
|
|
|
4
4
|
----------------------------------------
|
|
5
5
|
Primary License
|
|
@@ -44,13 +44,34 @@ License shown above.
|
|
|
44
44
|
- Purpose: header-only binding generator for the Python extension module.
|
|
45
45
|
- License: BSD-style License (https://github.com/pybind/pybind11)
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
- Purpose: build and release tasks for the Ruby gem.
|
|
49
|
-
- License: MIT License (https://github.com/ruby/rake)
|
|
47
|
+
The following components are redistributed only because libarchive (bundled with archive_r) depends on them at runtime:
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
- Purpose:
|
|
53
|
-
- License:
|
|
49
|
+
3. zlib
|
|
50
|
+
- Purpose: libarchive dependency providing DEFLATE compression; bundled inside archive_r binaries and wheels because libarchive requires it.
|
|
51
|
+
- License: zlib License (https://zlib.net/zlib_license.html)
|
|
54
52
|
|
|
53
|
+
4. bzip2
|
|
54
|
+
- Purpose: libarchive dependency providing bzip2 compression support; distributed with archive_r artifacts.
|
|
55
|
+
- License: BSD-style license (https://sourceware.org/bzip2/)
|
|
56
|
+
|
|
57
|
+
5. liblzma (XZ Utils)
|
|
58
|
+
- Purpose: libarchive dependency providing LZMA/XZ compression; included with archive_r packages.
|
|
59
|
+
- License: Public Domain + GNU LGPLv2.1+ (https://tukaani.org/xz/)
|
|
60
|
+
|
|
61
|
+
6. libxml2
|
|
62
|
+
- Purpose: libarchive dependency used for archive formats such as xar; distributed alongside archive_r.
|
|
63
|
+
- License: MIT-style License (http://xmlsoft.org/)
|
|
64
|
+
|
|
65
|
+
7. zstd
|
|
66
|
+
- Purpose: libarchive dependency providing Zstandard compression; shipped within archive_r binaries.
|
|
67
|
+
- License: BSD License (https://github.com/facebook/zstd)
|
|
68
|
+
|
|
69
|
+
8. OpenSSL 3
|
|
70
|
+
- Purpose: libarchive dependency providing cryptographic support for encrypted archives; included with archive_r packages.
|
|
71
|
+
- License: Apache License 2.0 with OpenSSL exception (https://www.openssl.org/source/license.html)
|
|
72
|
+
|
|
73
|
+
9. libiconv / libcharset
|
|
74
|
+
- Purpose: libxml2/libarchive dependency for character set conversion; redistributed with archive_r artifacts.
|
|
75
|
+
- License: GNU LGPLv2.1+ (https://www.gnu.org/software/libiconv/)
|
|
55
76
|
Users of archive_r should review the linked third-party licenses to ensure
|
|
56
77
|
compliance with their terms when redistributing this software.
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
#include "archive_r/data_stream.h"
|
|
5
5
|
#include "archive_r/entry.h"
|
|
6
6
|
#include "archive_r/entry_fault.h"
|
|
7
|
+
#include "archive_r/multi_volume_stream_base.h"
|
|
7
8
|
#include "archive_r/path_hierarchy_utils.h"
|
|
8
9
|
#include "archive_r/traverser.h"
|
|
9
10
|
#include <cctype>
|
|
11
|
+
#include <cstdio>
|
|
10
12
|
#include <cstring>
|
|
11
13
|
#include <memory>
|
|
12
14
|
#include <ruby.h>
|
|
@@ -16,6 +18,7 @@
|
|
|
16
18
|
#include <variant>
|
|
17
19
|
#include <vector>
|
|
18
20
|
#include <limits>
|
|
21
|
+
#include <optional>
|
|
19
22
|
|
|
20
23
|
using namespace archive_r;
|
|
21
24
|
|
|
@@ -24,15 +27,25 @@ static VALUE mArchive_r;
|
|
|
24
27
|
static VALUE cTraverser;
|
|
25
28
|
static VALUE cEntry;
|
|
26
29
|
static ID rb_id_read_method;
|
|
27
|
-
static ID rb_id_rewind_method;
|
|
28
30
|
static ID rb_id_seek_method;
|
|
29
31
|
static ID rb_id_tell_method;
|
|
30
32
|
static ID rb_id_eof_method;
|
|
33
|
+
static ID rb_id_close_method;
|
|
34
|
+
static ID rb_id_size_method;
|
|
31
35
|
static ID rb_id_call_method;
|
|
36
|
+
static ID rb_id_open_part_method;
|
|
37
|
+
static ID rb_id_close_part_method;
|
|
38
|
+
static ID rb_id_read_part_method;
|
|
39
|
+
static ID rb_id_seek_part_method;
|
|
40
|
+
static ID rb_id_part_size_method;
|
|
41
|
+
static ID rb_id_open_part_io_method;
|
|
42
|
+
static ID rb_id_close_part_io_method;
|
|
32
43
|
struct RubyCallbackHolder;
|
|
33
44
|
static std::shared_ptr<RubyCallbackHolder> g_stream_factory_callback;
|
|
34
45
|
|
|
35
46
|
// Helper: Convert Ruby string to C++ string
|
|
47
|
+
static VALUE cStream;
|
|
48
|
+
static PathHierarchy rb_value_to_path_hierarchy(VALUE value);
|
|
36
49
|
static std::string rb_string_to_cpp(VALUE rb_str) {
|
|
37
50
|
Check_Type(rb_str, T_STRING);
|
|
38
51
|
return std::string(RSTRING_PTR(rb_str), RSTRING_LEN(rb_str));
|
|
@@ -85,96 +98,257 @@ struct RubyCallbackHolder {
|
|
|
85
98
|
VALUE proc_value;
|
|
86
99
|
};
|
|
87
100
|
|
|
88
|
-
class
|
|
101
|
+
class RubyUserStream : public MultiVolumeStreamBase {
|
|
89
102
|
public:
|
|
90
|
-
|
|
91
|
-
:
|
|
92
|
-
,
|
|
93
|
-
,
|
|
94
|
-
,
|
|
95
|
-
,
|
|
96
|
-
,
|
|
97
|
-
,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
RubyUserStream(VALUE ruby_stream, PathHierarchy hierarchy, std::optional<bool> seekable_override)
|
|
104
|
+
: MultiVolumeStreamBase(std::move(hierarchy), determine_seek_support(ruby_stream, seekable_override))
|
|
105
|
+
, _ruby_stream(ruby_stream)
|
|
106
|
+
, _has_close(rb_respond_to(ruby_stream, rb_id_close_part_method))
|
|
107
|
+
, _has_seek(rb_respond_to(ruby_stream, rb_id_seek_part_method))
|
|
108
|
+
, _has_size(rb_respond_to(ruby_stream, rb_id_part_size_method))
|
|
109
|
+
, _has_open_part_io(rb_respond_to(ruby_stream, rb_id_open_part_io_method))
|
|
110
|
+
, _has_close_part_io(rb_respond_to(ruby_stream, rb_id_close_part_io_method))
|
|
111
|
+
, _active_io(Qnil)
|
|
112
|
+
, _active_io_seekable(false)
|
|
113
|
+
, _active_io_tellable(false)
|
|
114
|
+
, _active_io_has_close(false)
|
|
115
|
+
, _active_io_has_size(false) {
|
|
116
|
+
ensure_required_methods(ruby_stream);
|
|
117
|
+
rb_gc_register_address(&_ruby_stream);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
~RubyUserStream() override {
|
|
121
|
+
release_active_io();
|
|
122
|
+
rb_gc_unregister_address(&_ruby_stream);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
protected:
|
|
126
|
+
void open_single_part(const PathHierarchy &single_part) override {
|
|
127
|
+
if (_has_open_part_io) {
|
|
128
|
+
VALUE arg = path_hierarchy_to_rb(single_part);
|
|
129
|
+
VALUE io = rb_funcall(_ruby_stream, rb_id_open_part_io_method, 1, arg);
|
|
130
|
+
if (NIL_P(io)) {
|
|
131
|
+
rb_raise(rb_eRuntimeError, "open_part_io must return an IO-like object");
|
|
132
|
+
}
|
|
133
|
+
activate_io(io);
|
|
134
|
+
return;
|
|
101
135
|
}
|
|
102
|
-
|
|
136
|
+
|
|
137
|
+
VALUE arg = path_hierarchy_to_rb(single_part);
|
|
138
|
+
rb_funcall(_ruby_stream, rb_id_open_part_method, 1, arg);
|
|
103
139
|
}
|
|
104
140
|
|
|
105
|
-
|
|
141
|
+
void close_single_part() override {
|
|
142
|
+
if (_has_open_part_io) {
|
|
143
|
+
release_active_io();
|
|
144
|
+
if (_has_close_part_io) {
|
|
145
|
+
rb_funcall(_ruby_stream, rb_id_close_part_io_method, 0);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (_has_close) {
|
|
151
|
+
rb_funcall(_ruby_stream, rb_id_close_part_method, 0);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
106
154
|
|
|
107
|
-
ssize_t
|
|
155
|
+
ssize_t read_from_single_part(void *buffer, size_t size) override {
|
|
108
156
|
if (size == 0) {
|
|
109
157
|
return 0;
|
|
110
158
|
}
|
|
111
159
|
|
|
112
|
-
|
|
160
|
+
if (_has_open_part_io) {
|
|
161
|
+
if (_active_io == Qnil) {
|
|
162
|
+
rb_raise(rb_eRuntimeError, "open_part_io must return an IO before reading");
|
|
163
|
+
}
|
|
164
|
+
VALUE result = rb_funcall(_active_io, rb_id_read_method, 1, SIZET2NUM(size));
|
|
165
|
+
if (NIL_P(result)) {
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
Check_Type(result, T_STRING);
|
|
169
|
+
const ssize_t length = static_cast<ssize_t>(RSTRING_LEN(result));
|
|
170
|
+
if (length <= 0) {
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
std::memcpy(buffer, RSTRING_PTR(result), static_cast<size_t>(length));
|
|
174
|
+
return length;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
VALUE result = rb_funcall(_ruby_stream, rb_id_read_part_method, 1, SIZET2NUM(size));
|
|
113
178
|
if (NIL_P(result)) {
|
|
114
|
-
_at_end = true;
|
|
115
179
|
return 0;
|
|
116
180
|
}
|
|
117
181
|
Check_Type(result, T_STRING);
|
|
118
|
-
const ssize_t
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
return bytes_read;
|
|
182
|
+
const ssize_t length = static_cast<ssize_t>(RSTRING_LEN(result));
|
|
183
|
+
if (length <= 0) {
|
|
184
|
+
return 0;
|
|
122
185
|
}
|
|
186
|
+
std::memcpy(buffer, RSTRING_PTR(result), static_cast<size_t>(length));
|
|
187
|
+
return length;
|
|
188
|
+
}
|
|
123
189
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
190
|
+
int64_t seek_within_single_part(int64_t offset, int whence) override {
|
|
191
|
+
if (_has_open_part_io && _active_io != Qnil && _active_io_seekable) {
|
|
192
|
+
VALUE result = rb_funcall(_active_io, rb_id_seek_method, 2, LL2NUM(offset), INT2NUM(whence));
|
|
193
|
+
return NUM2LL(result);
|
|
127
194
|
}
|
|
128
|
-
return 0;
|
|
129
|
-
}
|
|
130
195
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
rb_raise(rb_eRuntimeError, "IO object does not respond to #rewind");
|
|
196
|
+
if (!_has_seek) {
|
|
197
|
+
return -1;
|
|
134
198
|
}
|
|
135
|
-
rb_funcall(
|
|
136
|
-
|
|
199
|
+
VALUE result = rb_funcall(_ruby_stream, rb_id_seek_part_method, 2, LL2NUM(offset), INT2NUM(whence));
|
|
200
|
+
return NUM2LL(result);
|
|
137
201
|
}
|
|
138
202
|
|
|
139
|
-
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
203
|
+
int64_t size_of_single_part(const PathHierarchy &single_part) override {
|
|
204
|
+
if (_has_open_part_io && _active_io != Qnil) {
|
|
205
|
+
if (_active_io_has_size) {
|
|
206
|
+
VALUE result = rb_funcall(_active_io, rb_id_size_method, 0);
|
|
207
|
+
return NUM2LL(result);
|
|
208
|
+
}
|
|
209
|
+
if (_active_io_seekable && _active_io_tellable) {
|
|
210
|
+
VALUE current = rb_funcall(_active_io, rb_id_tell_method, 0);
|
|
211
|
+
rb_funcall(_active_io, rb_id_seek_method, 2, LL2NUM(0), INT2NUM(SEEK_END));
|
|
212
|
+
VALUE end_pos = rb_funcall(_active_io, rb_id_tell_method, 0);
|
|
213
|
+
rb_funcall(_active_io, rb_id_seek_method, 2, current, INT2NUM(SEEK_SET));
|
|
214
|
+
return NUM2LL(end_pos);
|
|
215
|
+
}
|
|
143
216
|
}
|
|
144
|
-
return _at_end;
|
|
145
|
-
}
|
|
146
217
|
|
|
147
|
-
|
|
148
|
-
if (!_seekable) {
|
|
218
|
+
if (!_has_size) {
|
|
149
219
|
return -1;
|
|
150
220
|
}
|
|
151
|
-
VALUE
|
|
152
|
-
|
|
221
|
+
VALUE arg = path_hierarchy_to_rb(single_part);
|
|
222
|
+
VALUE result = rb_funcall(_ruby_stream, rb_id_part_size_method, 1, arg);
|
|
153
223
|
return NUM2LL(result);
|
|
154
224
|
}
|
|
155
225
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
226
|
+
private:
|
|
227
|
+
static bool determine_seek_support(VALUE ruby_stream, const std::optional<bool> &override_flag) {
|
|
228
|
+
if (override_flag.has_value()) {
|
|
229
|
+
return *override_flag;
|
|
230
|
+
}
|
|
231
|
+
if (rb_respond_to(ruby_stream, rb_id_seek_part_method)) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
if (rb_respond_to(ruby_stream, rb_id_open_part_io_method)) {
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
static void ensure_required_methods(VALUE ruby_stream) {
|
|
241
|
+
if (!rb_respond_to(ruby_stream, rb_id_open_part_method) && !rb_respond_to(ruby_stream, rb_id_open_part_io_method)) {
|
|
242
|
+
rb_raise(rb_eNotImpError, "Stream subclasses must implement #open_part_io or #open_part");
|
|
243
|
+
}
|
|
244
|
+
if (!rb_respond_to(ruby_stream, rb_id_read_part_method) && !rb_respond_to(ruby_stream, rb_id_open_part_io_method)) {
|
|
245
|
+
rb_raise(rb_eNotImpError, "Stream subclasses must implement #open_part_io or #read_part");
|
|
159
246
|
}
|
|
160
|
-
VALUE result = rb_funcall(_io, rb_id_tell_method, 0);
|
|
161
|
-
return NUM2LL(result);
|
|
162
247
|
}
|
|
163
248
|
|
|
164
|
-
|
|
249
|
+
void activate_io(VALUE io) {
|
|
250
|
+
if (!rb_respond_to(io, rb_id_read_method)) {
|
|
251
|
+
rb_raise(rb_eTypeError, "open_part_io must return an object responding to #read");
|
|
252
|
+
}
|
|
253
|
+
release_active_io();
|
|
254
|
+
_active_io = io;
|
|
255
|
+
rb_gc_register_address(&_active_io);
|
|
256
|
+
_active_io_seekable = rb_respond_to(io, rb_id_seek_method);
|
|
257
|
+
_active_io_tellable = rb_respond_to(io, rb_id_tell_method);
|
|
258
|
+
_active_io_has_close = rb_respond_to(io, rb_id_close_method);
|
|
259
|
+
_active_io_has_size = rb_respond_to(io, rb_id_size_method);
|
|
260
|
+
}
|
|
165
261
|
|
|
166
|
-
|
|
262
|
+
void release_active_io() {
|
|
263
|
+
if (_active_io == Qnil) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (_active_io_has_close) {
|
|
267
|
+
rb_funcall(_active_io, rb_id_close_method, 0);
|
|
268
|
+
}
|
|
269
|
+
rb_gc_unregister_address(&_active_io);
|
|
270
|
+
_active_io = Qnil;
|
|
271
|
+
_active_io_seekable = false;
|
|
272
|
+
_active_io_tellable = false;
|
|
273
|
+
_active_io_has_close = false;
|
|
274
|
+
_active_io_has_size = false;
|
|
275
|
+
}
|
|
167
276
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
bool
|
|
173
|
-
bool
|
|
174
|
-
|
|
175
|
-
bool
|
|
277
|
+
VALUE _ruby_stream;
|
|
278
|
+
bool _has_close;
|
|
279
|
+
bool _has_seek;
|
|
280
|
+
bool _has_size;
|
|
281
|
+
bool _has_open_part_io;
|
|
282
|
+
bool _has_close_part_io;
|
|
283
|
+
VALUE _active_io;
|
|
284
|
+
bool _active_io_seekable;
|
|
285
|
+
bool _active_io_tellable;
|
|
286
|
+
bool _active_io_has_close;
|
|
287
|
+
bool _active_io_has_size;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
struct RubyStreamWrapper {
|
|
291
|
+
std::shared_ptr<RubyUserStream> stream;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
static void stream_wrapper_free(void *ptr) {
|
|
295
|
+
auto *wrapper = static_cast<RubyStreamWrapper *>(ptr);
|
|
296
|
+
delete wrapper;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
static size_t stream_wrapper_memsize(const void *ptr) {
|
|
300
|
+
return sizeof(RubyStreamWrapper);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
static const rb_data_type_t stream_wrapper_type = {
|
|
304
|
+
"ArchiveR::Stream",
|
|
305
|
+
{
|
|
306
|
+
nullptr,
|
|
307
|
+
stream_wrapper_free,
|
|
308
|
+
stream_wrapper_memsize,
|
|
309
|
+
},
|
|
310
|
+
nullptr,
|
|
311
|
+
nullptr,
|
|
312
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
|
176
313
|
};
|
|
177
314
|
|
|
315
|
+
static RubyStreamWrapper *get_stream_wrapper(VALUE self) {
|
|
316
|
+
RubyStreamWrapper *wrapper = nullptr;
|
|
317
|
+
TypedData_Get_Struct(self, RubyStreamWrapper, &stream_wrapper_type, wrapper);
|
|
318
|
+
if (!wrapper) {
|
|
319
|
+
rb_raise(rb_eRuntimeError, "invalid Stream wrapper");
|
|
320
|
+
}
|
|
321
|
+
return wrapper;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
static VALUE stream_allocate(VALUE klass) {
|
|
325
|
+
auto *wrapper = new RubyStreamWrapper();
|
|
326
|
+
wrapper->stream.reset();
|
|
327
|
+
return TypedData_Wrap_Struct(klass, &stream_wrapper_type, wrapper);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
static VALUE stream_initialize(int argc, VALUE *argv, VALUE self) {
|
|
331
|
+
RubyStreamWrapper *wrapper = get_stream_wrapper(self);
|
|
332
|
+
if (wrapper->stream) {
|
|
333
|
+
rb_raise(rb_eRuntimeError, "Stream already initialized");
|
|
334
|
+
}
|
|
335
|
+
VALUE hierarchy_value = Qnil;
|
|
336
|
+
VALUE opts = Qnil;
|
|
337
|
+
rb_scan_args(argc, argv, "11", &hierarchy_value, &opts);
|
|
338
|
+
PathHierarchy hierarchy = rb_value_to_path_hierarchy(hierarchy_value);
|
|
339
|
+
std::optional<bool> seekable_override;
|
|
340
|
+
if (!NIL_P(opts)) {
|
|
341
|
+
Check_Type(opts, T_HASH);
|
|
342
|
+
static ID id_seekable = rb_intern("seekable");
|
|
343
|
+
VALUE seekable_value = rb_hash_aref(opts, ID2SYM(id_seekable));
|
|
344
|
+
if (!NIL_P(seekable_value)) {
|
|
345
|
+
seekable_override = RTEST(seekable_value);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
wrapper->stream = std::make_shared<RubyUserStream>(self, std::move(hierarchy), seekable_override);
|
|
349
|
+
return self;
|
|
350
|
+
}
|
|
351
|
+
|
|
178
352
|
static VALUE entry_fault_to_rb(const EntryFault &fault) {
|
|
179
353
|
VALUE hash = rb_hash_new();
|
|
180
354
|
static ID id_message = rb_intern("message");
|
|
@@ -371,16 +545,42 @@ static std::vector<PathHierarchy> rb_paths_to_hierarchies(VALUE paths) {
|
|
|
371
545
|
return result;
|
|
372
546
|
}
|
|
373
547
|
|
|
548
|
+
static VALUE invoke_ruby_stream_factory(const std::shared_ptr<RubyCallbackHolder> &holder, const PathHierarchy &hierarchy) {
|
|
549
|
+
struct FactoryPayload {
|
|
550
|
+
std::shared_ptr<RubyCallbackHolder> holder;
|
|
551
|
+
VALUE arg;
|
|
552
|
+
} payload{ holder, path_hierarchy_to_rb(hierarchy) };
|
|
553
|
+
|
|
554
|
+
auto invoke = [](VALUE data) -> VALUE {
|
|
555
|
+
auto *info = reinterpret_cast<FactoryPayload *>(data);
|
|
556
|
+
return rb_funcall(info->holder->proc_value, rb_id_call_method, 1, info->arg);
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
int state = 0;
|
|
560
|
+
VALUE result = rb_protect(invoke, reinterpret_cast<VALUE>(&payload), &state);
|
|
561
|
+
if (state != 0) {
|
|
562
|
+
rb_jump_tag(state);
|
|
563
|
+
}
|
|
564
|
+
return result;
|
|
565
|
+
}
|
|
566
|
+
|
|
374
567
|
static std::shared_ptr<IDataStream> stream_from_ruby_value(VALUE value, const PathHierarchy &requested) {
|
|
375
568
|
if (NIL_P(value)) {
|
|
376
569
|
return nullptr;
|
|
377
570
|
}
|
|
378
571
|
|
|
379
|
-
if (
|
|
380
|
-
|
|
572
|
+
if (rb_obj_is_kind_of(value, cStream)) {
|
|
573
|
+
RubyStreamWrapper *wrapper = get_stream_wrapper(value);
|
|
574
|
+
if (!wrapper->stream) {
|
|
575
|
+
rb_raise(rb_eRuntimeError, "Stream instance is not initialized");
|
|
576
|
+
}
|
|
577
|
+
if (!hierarchies_equal(wrapper->stream->source_hierarchy(), requested)) {
|
|
578
|
+
rb_raise(rb_eArgError, "Stream instance must be created with the same hierarchy passed to the factory");
|
|
579
|
+
}
|
|
580
|
+
return wrapper->stream;
|
|
381
581
|
}
|
|
382
582
|
|
|
383
|
-
return
|
|
583
|
+
rb_raise(rb_eTypeError, "stream factory must return nil or an Archive_r::Stream instance");
|
|
384
584
|
}
|
|
385
585
|
|
|
386
586
|
// Helper: Convert EntryMetadataValue to Ruby object
|
|
@@ -495,22 +695,7 @@ static VALUE archive_r_register_stream_factory(int argc, VALUE *argv, VALUE self
|
|
|
495
695
|
auto holder = std::make_shared<RubyCallbackHolder>(callable);
|
|
496
696
|
|
|
497
697
|
RootStreamFactory factory = [holder](const PathHierarchy &hierarchy) -> std::shared_ptr<IDataStream> {
|
|
498
|
-
|
|
499
|
-
std::shared_ptr<RubyCallbackHolder> holder;
|
|
500
|
-
VALUE arg;
|
|
501
|
-
} payload{ holder, path_hierarchy_to_rb(hierarchy) };
|
|
502
|
-
|
|
503
|
-
auto invoke = [](VALUE data) -> VALUE {
|
|
504
|
-
auto *info = reinterpret_cast<FactoryPayload *>(data);
|
|
505
|
-
return rb_funcall(info->holder->proc_value, rb_id_call_method, 1, info->arg);
|
|
506
|
-
};
|
|
507
|
-
|
|
508
|
-
int state = 0;
|
|
509
|
-
VALUE result = rb_protect(invoke, reinterpret_cast<VALUE>(&payload), &state);
|
|
510
|
-
if (state != 0) {
|
|
511
|
-
rb_jump_tag(state);
|
|
512
|
-
}
|
|
513
|
-
|
|
698
|
+
VALUE result = invoke_ruby_stream_factory(holder, hierarchy);
|
|
514
699
|
return stream_from_ruby_value(result, hierarchy);
|
|
515
700
|
};
|
|
516
701
|
|
|
@@ -867,13 +1052,24 @@ static VALUE traverser_s_open(int argc, VALUE *argv, VALUE klass) {
|
|
|
867
1052
|
extern "C" void Init_archive_r() {
|
|
868
1053
|
// Define module Archive_r
|
|
869
1054
|
mArchive_r = rb_define_module("Archive_r");
|
|
1055
|
+
cStream = rb_define_class_under(mArchive_r, "Stream", rb_cObject);
|
|
1056
|
+
rb_define_alloc_func(cStream, stream_allocate);
|
|
1057
|
+
rb_define_method(cStream, "initialize", RUBY_METHOD_FUNC(stream_initialize), -1);
|
|
870
1058
|
|
|
871
1059
|
rb_id_read_method = rb_intern("read");
|
|
872
|
-
rb_id_rewind_method = rb_intern("rewind");
|
|
873
1060
|
rb_id_seek_method = rb_intern("seek");
|
|
874
1061
|
rb_id_tell_method = rb_intern("tell");
|
|
875
1062
|
rb_id_eof_method = rb_intern("eof?");
|
|
1063
|
+
rb_id_close_method = rb_intern("close");
|
|
1064
|
+
rb_id_size_method = rb_intern("size");
|
|
876
1065
|
rb_id_call_method = rb_intern("call");
|
|
1066
|
+
rb_id_open_part_method = rb_intern("open_part");
|
|
1067
|
+
rb_id_close_part_method = rb_intern("close_part");
|
|
1068
|
+
rb_id_read_part_method = rb_intern("read_part");
|
|
1069
|
+
rb_id_seek_part_method = rb_intern("seek_part");
|
|
1070
|
+
rb_id_part_size_method = rb_intern("part_size");
|
|
1071
|
+
rb_id_open_part_io_method = rb_intern("open_part_io");
|
|
1072
|
+
rb_id_close_part_io_method = rb_intern("close_part_io");
|
|
877
1073
|
|
|
878
1074
|
// Define Entry class
|
|
879
1075
|
cEntry = rb_define_class_under(mArchive_r, "Entry", rb_cObject);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
archive_r License
|
|
2
|
-
Version: 0.1.
|
|
2
|
+
Version: 0.1.2 (2025-12-02)
|
|
3
3
|
|
|
4
4
|
----------------------------------------
|
|
5
5
|
Primary License
|
|
@@ -44,13 +44,34 @@ License shown above.
|
|
|
44
44
|
- Purpose: header-only binding generator for the Python extension module.
|
|
45
45
|
- License: BSD-style License (https://github.com/pybind/pybind11)
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
- Purpose: build and release tasks for the Ruby gem.
|
|
49
|
-
- License: MIT License (https://github.com/ruby/rake)
|
|
47
|
+
The following components are redistributed only because libarchive (bundled with archive_r) depends on them at runtime:
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
- Purpose:
|
|
53
|
-
- License:
|
|
49
|
+
3. zlib
|
|
50
|
+
- Purpose: libarchive dependency providing DEFLATE compression; bundled inside archive_r binaries and wheels because libarchive requires it.
|
|
51
|
+
- License: zlib License (https://zlib.net/zlib_license.html)
|
|
54
52
|
|
|
53
|
+
4. bzip2
|
|
54
|
+
- Purpose: libarchive dependency providing bzip2 compression support; distributed with archive_r artifacts.
|
|
55
|
+
- License: BSD-style license (https://sourceware.org/bzip2/)
|
|
56
|
+
|
|
57
|
+
5. liblzma (XZ Utils)
|
|
58
|
+
- Purpose: libarchive dependency providing LZMA/XZ compression; included with archive_r packages.
|
|
59
|
+
- License: Public Domain + GNU LGPLv2.1+ (https://tukaani.org/xz/)
|
|
60
|
+
|
|
61
|
+
6. libxml2
|
|
62
|
+
- Purpose: libarchive dependency used for archive formats such as xar; distributed alongside archive_r.
|
|
63
|
+
- License: MIT-style License (http://xmlsoft.org/)
|
|
64
|
+
|
|
65
|
+
7. zstd
|
|
66
|
+
- Purpose: libarchive dependency providing Zstandard compression; shipped within archive_r binaries.
|
|
67
|
+
- License: BSD License (https://github.com/facebook/zstd)
|
|
68
|
+
|
|
69
|
+
8. OpenSSL 3
|
|
70
|
+
- Purpose: libarchive dependency providing cryptographic support for encrypted archives; included with archive_r packages.
|
|
71
|
+
- License: Apache License 2.0 with OpenSSL exception (https://www.openssl.org/source/license.html)
|
|
72
|
+
|
|
73
|
+
9. libiconv / libcharset
|
|
74
|
+
- Purpose: libxml2/libarchive dependency for character set conversion; redistributed with archive_r artifacts.
|
|
75
|
+
- License: GNU LGPLv2.1+ (https://www.gnu.org/software/libiconv/)
|
|
55
76
|
Users of archive_r should review the linked third-party licenses to ensure
|
|
56
77
|
compliance with their terms when redistributing this software.
|
data/lib/archive_r.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: archive_r_ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- raizo.tcs
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-12-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -38,16 +38,16 @@ dependencies:
|
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '5.0'
|
|
41
|
-
description:
|
|
42
|
-
files
|
|
41
|
+
description: Ruby bindings for archive_r that recursively walk nested and multipart
|
|
42
|
+
archives directly from the source stream without creating temporary files
|
|
43
43
|
email:
|
|
44
|
-
-
|
|
44
|
+
- raizo.tcs@users.noreply.github.com
|
|
45
45
|
executables: []
|
|
46
46
|
extensions:
|
|
47
47
|
- ext/archive_r/extconf.rb
|
|
48
48
|
extra_rdoc_files: []
|
|
49
49
|
files:
|
|
50
|
-
- LICENSE
|
|
50
|
+
- LICENSE.txt
|
|
51
51
|
- README.md
|
|
52
52
|
- ext/archive_r/archive_r_ext.cc
|
|
53
53
|
- ext/archive_r/extconf.rb
|
|
@@ -108,5 +108,5 @@ requirements: []
|
|
|
108
108
|
rubygems_version: 3.4.20
|
|
109
109
|
signing_key:
|
|
110
110
|
specification_version: 4
|
|
111
|
-
summary: Ruby bindings for archive_r
|
|
111
|
+
summary: Ruby bindings for archive_r that traverse nested archives without temp extraction
|
|
112
112
|
test_files: []
|