archive_r_ruby 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 073f63cf2d5a0b176c74824c73c10e2aa95e3038a01212812c8d10655c71727c
4
- data.tar.gz: 84d8e7c7c4f2b83696024bec5df22d5e6943f42732f17ff7ad1be6fb20831bd1
3
+ metadata.gz: c5c817b1729dcccb9268c75be4c2ce7fe8ca6a41e38421190d0028faa6223eaa
4
+ data.tar.gz: f6257d3a247d2abd5f88d149e09cd0d16a78623403b430c78f1e613db99f9b4d
5
5
  SHA512:
6
- metadata.gz: acd57fa4d9d0009832296a1a48b53c2b04959a639f291ffe564f62f23d40feddc66ec42289f788668e89d11728f09d4ebb3d7f0abc185c20f62e05622140c0a0
7
- data.tar.gz: 802effdd52cbc83796e27d81a87ec75745d4993c3f2bde1619a3489ab18730e4f6ed2b39f70f8987b5bbb7765f013a18ad360c54e52c59defe53ae0ccdf88cc5
6
+ metadata.gz: b9076f2e8c632e23374174921c5862814348f8d64ca20405e3851101f798c15eaf56afef46339227b50b9a5da70ea40833ace51d3894435dc488a20cbe86cdb4
7
+ data.tar.gz: 9236f13270d0a46db84e592b0603817fc1b255c50d3ec80e7659d6c3eba02d7f6bcc457c303800a3c03d374e6209f4cf33a6c3e4857d0257989d9768e8bbf9b5
data/LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  archive_r License
2
- Version: 0.1.3 (2025-12-02)
2
+ Version: 0.1.6 (2025-12-10)
3
3
 
4
4
  ----------------------------------------
5
5
  Primary License
@@ -66,12 +66,32 @@ The following components are redistributed only because libarchive (bundled with
66
66
  - Purpose: libarchive dependency providing Zstandard compression; shipped within archive_r binaries.
67
67
  - License: BSD License (https://github.com/facebook/zstd)
68
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)
69
+ 8. Nettle
70
+ - Purpose: libarchive dependency providing cryptographic support (macOS/Linux); bundled with archive_r binaries.
71
+ - License: GNU LGPLv3+ or GNU GPLv2+ (https://www.lysator.liu.se/~nisse/nettle/)
72
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/)
73
+ 9. mini-gmp
74
+ - Purpose: Nettle dependency for arithmetic operations (macOS/Linux); bundled with archive_r binaries.
75
+ - License: GNU LGPLv3+ or GNU GPLv2+ (https://gmplib.org/)
76
+
77
+ 10. OpenSSL 3
78
+ - Purpose: libarchive dependency providing cryptographic support (Windows); bundled with archive_r binaries.
79
+ - License: Apache License 2.0 with OpenSSL exception (https://www.openssl.org/source/license.html)
80
+
81
+ 11. lz4
82
+ - Purpose: libarchive dependency providing LZ4 compression; shipped with archive_r artifacts when required.
83
+ - License: BSD 2-Clause (https://github.com/lz4/lz4)
84
+
85
+ 12. libb2 (BLAKE2)
86
+ - Purpose: libarchive dependency providing BLAKE2 hashing; bundled when archive formats require it.
87
+ - License: CC0 1.0 Universal (https://github.com/BLAKE2/libb2)
88
+
89
+ 13. libattr
90
+ - Purpose: libarchive dependency providing extended attribute support on POSIX platforms; included in POSIX builds only.
91
+ - License: LGPL-2.1-or-later for the library (https://savannah.nongnu.org/projects/attr)
92
+
93
+ 14. libacl
94
+ - Purpose: libarchive dependency providing POSIX ACL support; included in POSIX builds only.
95
+ - License: LGPL-2.1-or-later for the library (https://savannah.nongnu.org/projects/acl)
76
96
  Users of archive_r should review the linked third-party licenses to ensure
77
97
  compliance with their terms when redistributing this software.
data/README.md CHANGED
@@ -37,9 +37,9 @@ bundle exec rake build # creates archive_r-<version>.gem locally
37
37
 
38
38
  The `rake test` task compiles the extension, installs it into `lib/`, and executes the Minitest suite.
39
39
 
40
- ## Running the full repository test suite
40
+ ## Running the repository test suite
41
41
 
42
- `./run_tests.sh` prepares a clean GEM_HOME (`build/ruby_gem_home`), installs the gem produced in `build/bindings/ruby`, and runs `bindings/ruby/test/test_traverser.rb`. The script now streams the `gem install` log to the console and preserves it in `build/logs/ruby_gem_install.log` for later inspection.
42
+ From the repository root run `./bindings/ruby/run_binding_tests.sh`. The script prepares a clean GEM_HOME (`build/ruby_gem_home`), installs the gem produced in `build/bindings/ruby`, runs `bindings/ruby/test/test_traverser.rb`, and saves the install log to `build/logs/ruby_gem_install.log`. CI invokes this script after the core tests.
43
43
 
44
44
  ## Usage Example
45
45
 
@@ -118,7 +118,7 @@ public:
118
118
  }
119
119
 
120
120
  ~RubyUserStream() override {
121
- release_active_io();
121
+ release_active_io_resources(false);
122
122
  rb_gc_unregister_address(&_ruby_stream);
123
123
  }
124
124
 
@@ -140,7 +140,7 @@ protected:
140
140
 
141
141
  void close_single_part() override {
142
142
  if (_has_open_part_io) {
143
- release_active_io();
143
+ release_active_io_resources(true);
144
144
  if (_has_close_part_io) {
145
145
  rb_funcall(_ruby_stream, rb_id_close_part_io_method, 0);
146
146
  }
@@ -250,7 +250,7 @@ private:
250
250
  if (!rb_respond_to(io, rb_id_read_method)) {
251
251
  rb_raise(rb_eTypeError, "open_part_io must return an object responding to #read");
252
252
  }
253
- release_active_io();
253
+ release_active_io_resources(true);
254
254
  _active_io = io;
255
255
  rb_gc_register_address(&_active_io);
256
256
  _active_io_seekable = rb_respond_to(io, rb_id_seek_method);
@@ -259,11 +259,11 @@ private:
259
259
  _active_io_has_size = rb_respond_to(io, rb_id_size_method);
260
260
  }
261
261
 
262
- void release_active_io() {
262
+ void release_active_io_resources(bool close_io) {
263
263
  if (_active_io == Qnil) {
264
264
  return;
265
265
  }
266
- if (_active_io_has_close) {
266
+ if (close_io && _active_io_has_close) {
267
267
  rb_funcall(_active_io, rb_id_close_method, 0);
268
268
  }
269
269
  rb_gc_unregister_address(&_active_io);
@@ -46,6 +46,7 @@ end
46
46
  archive_r_include = File.join(archive_r_root, 'include')
47
47
  archive_r_src = File.join(archive_r_root, 'src')
48
48
  archive_r_lib_dir = File.join(archive_r_root, 'build')
49
+ archive_r_local_libs = File.expand_path('.libs', __dir__)
49
50
  glue_source = File.join(__dir__, 'archive_r_ext.cc')
50
51
 
51
52
  # Ensure make can locate vendored sources via VPATH
@@ -58,6 +59,11 @@ $VPATH << archive_r_src
58
59
  # Add include paths
59
60
  $INCFLAGS << " -I#{archive_r_include}"
60
61
  $INCFLAGS << " -I#{archive_r_src}"
62
+ $LIBPATH.unshift(archive_r_local_libs)
63
+
64
+ unless Gem.win_platform?
65
+ $LDFLAGS << ' -Wl,-rpath,$ORIGIN/.libs'
66
+ end
61
67
 
62
68
  # C++17 standard
63
69
  $CXXFLAGS << " -std=c++17"
@@ -82,31 +88,30 @@ if ENV['LIBARCHIVE_LIBRARY_DIRS']
82
88
  end
83
89
 
84
90
  # Check for libarchive
85
- unless have_library('archive')
86
- # Try alternative names for Windows/Static builds
87
- unless have_library('archive_static') || have_library('libarchive')
88
- abort "libarchive is required but not found"
89
- end
91
+ unless have_library('archive') || have_library('libarchive')
92
+ abort "libarchive is required but not found"
90
93
  end
91
94
 
92
- # Try to link with pre-built static library first
93
- prebuilt_lib = File.join(archive_r_lib_dir, 'libarchive_r_core.a')
94
- prebuilt_lib_win = File.join(archive_r_lib_dir, 'archive_r_core.lib')
95
- prebuilt_lib_win_release = File.join(archive_r_lib_dir, 'Release', 'archive_r_core.lib')
96
-
97
- if File.exist?(prebuilt_lib)
98
- $LOCAL_LIBS << " #{prebuilt_lib}"
99
- puts "Using pre-built archive_r core library (Unix style)"
100
- elsif File.exist?(prebuilt_lib_win)
101
- $LOCAL_LIBS << " \"#{prebuilt_lib_win}\""
102
- puts "Using pre-built archive_r core library (Windows style)"
103
- elsif File.exist?(prebuilt_lib_win_release)
104
- $LOCAL_LIBS << " \"#{prebuilt_lib_win_release}\""
105
- puts "Using pre-built archive_r core library (Windows Release style)"
95
+ shared_candidates = [
96
+ File.join(archive_r_lib_dir, 'libarchive_r_core.so'),
97
+ File.join(archive_r_lib_dir, 'libarchive_r_core.dylib'),
98
+ File.join(archive_r_lib_dir, 'archive_r_core.dll'),
99
+ File.join(archive_r_lib_dir, 'archive_r_core.lib'),
100
+ File.join(archive_r_lib_dir, 'Release', 'archive_r_core.dll'),
101
+ File.join(archive_r_lib_dir, 'Release', 'archive_r_core.lib'),
102
+ File.join(archive_r_local_libs, 'libarchive_r_core.so'),
103
+ File.join(archive_r_local_libs, 'libarchive_r_core.dylib'),
104
+ File.join(archive_r_local_libs, 'archive_r_core.dll'),
105
+ ]
106
+
107
+ found_shared = shared_candidates.find { |path| File.exist?(path) }
108
+
109
+ if found_shared
110
+ $LIBPATH.unshift(File.dirname(found_shared))
111
+ $libs = "-larchive_r_core #{$libs}"
112
+ puts "Using pre-built shared archive_r core: #{found_shared}"
106
113
  else
107
- # Build from source as fallback (ensure the Ruby glue source is compiled too)
108
- puts "Pre-built library not found, will build from source"
109
-
114
+ puts "Pre-built shared library not found, will build from source"
110
115
  srcs = [glue_source] + Dir.glob(File.join(archive_r_src, '*.cc'))
111
116
  $srcs = srcs
112
117
  end
@@ -1,5 +1,5 @@
1
1
  archive_r License
2
- Version: 0.1.3 (2025-12-02)
2
+ Version: 0.1.6 (2025-12-10)
3
3
 
4
4
  ----------------------------------------
5
5
  Primary License
@@ -66,12 +66,32 @@ The following components are redistributed only because libarchive (bundled with
66
66
  - Purpose: libarchive dependency providing Zstandard compression; shipped within archive_r binaries.
67
67
  - License: BSD License (https://github.com/facebook/zstd)
68
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)
69
+ 8. Nettle
70
+ - Purpose: libarchive dependency providing cryptographic support (macOS/Linux); bundled with archive_r binaries.
71
+ - License: GNU LGPLv3+ or GNU GPLv2+ (https://www.lysator.liu.se/~nisse/nettle/)
72
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/)
73
+ 9. mini-gmp
74
+ - Purpose: Nettle dependency for arithmetic operations (macOS/Linux); bundled with archive_r binaries.
75
+ - License: GNU LGPLv3+ or GNU GPLv2+ (https://gmplib.org/)
76
+
77
+ 10. OpenSSL 3
78
+ - Purpose: libarchive dependency providing cryptographic support (Windows); bundled with archive_r binaries.
79
+ - License: Apache License 2.0 with OpenSSL exception (https://www.openssl.org/source/license.html)
80
+
81
+ 11. lz4
82
+ - Purpose: libarchive dependency providing LZ4 compression; shipped with archive_r artifacts when required.
83
+ - License: BSD 2-Clause (https://github.com/lz4/lz4)
84
+
85
+ 12. libb2 (BLAKE2)
86
+ - Purpose: libarchive dependency providing BLAKE2 hashing; bundled when archive formats require it.
87
+ - License: CC0 1.0 Universal (https://github.com/BLAKE2/libb2)
88
+
89
+ 13. libattr
90
+ - Purpose: libarchive dependency providing extended attribute support on POSIX platforms; included in POSIX builds only.
91
+ - License: LGPL-2.1-or-later for the library (https://savannah.nongnu.org/projects/attr)
92
+
93
+ 14. libacl
94
+ - Purpose: libarchive dependency providing POSIX ACL support; included in POSIX builds only.
95
+ - License: LGPL-2.1-or-later for the library (https://savannah.nongnu.org/projects/acl)
76
96
  Users of archive_r should review the linked third-party licenses to ensure
77
97
  compliance with their terms when redistributing this software.
@@ -60,6 +60,11 @@ void StreamArchive::rewind() {
60
60
 
61
61
  PathHierarchy StreamArchive::source_hierarchy() const { return _stream->source_hierarchy(); }
62
62
 
63
+ std::shared_ptr<StreamArchive> StreamArchive::parent_archive() const {
64
+ auto entry_stream = std::dynamic_pointer_cast<EntryPayloadStream>(_stream);
65
+ return entry_stream ? entry_stream->parent_archive() : nullptr;
66
+ }
67
+
63
68
  la_ssize_t StreamArchive::read_callback_bridge(struct archive *a, void *client_data, const void **buff) {
64
69
  auto *archive = static_cast<StreamArchive *>(client_data);
65
70
 
@@ -120,7 +125,9 @@ EntryPayloadStream::EntryPayloadStream(std::shared_ptr<StreamArchive> parent_arc
120
125
  }
121
126
  }
122
127
 
123
- EntryPayloadStream::~EntryPayloadStream() = default;
128
+ EntryPayloadStream::~EntryPayloadStream() {
129
+ deactivate_active_part();
130
+ }
124
131
 
125
132
  std::shared_ptr<StreamArchive> EntryPayloadStream::parent_archive() const { return _parent_archive; }
126
133
 
@@ -143,11 +150,8 @@ void EntryPayloadStream::open_single_part(const PathHierarchy &single_part) {
143
150
  }
144
151
 
145
152
  void EntryPayloadStream::close_single_part() {
146
- if (_parent_archive->current_entryname.empty()) {
147
- return;
148
- }
149
-
150
- _parent_archive->skip_data();
153
+ // libarchive automatically skips unread data when reading the next header,
154
+ // so explicit skipping here is unnecessary and avoids potential exceptions in destructor.
151
155
  }
152
156
 
153
157
  ssize_t EntryPayloadStream::read_from_single_part(void *buffer, size_t size) {
@@ -170,7 +174,8 @@ int64_t EntryPayloadStream::size_of_single_part(const PathHierarchy &single_part
170
174
 
171
175
  ArchiveStackCursor::ArchiveStackCursor()
172
176
  : options_snapshot()
173
- , stream_stack() {}
177
+ , _current_stream(nullptr)
178
+ , _current_archive(nullptr) {}
174
179
 
175
180
  void ArchiveStackCursor::configure(const ArchiveOption &options) {
176
181
  options_snapshot = options;
@@ -178,16 +183,16 @@ void ArchiveStackCursor::configure(const ArchiveOption &options) {
178
183
 
179
184
  void ArchiveStackCursor::reset() {
180
185
  options_snapshot = ArchiveOption{};
181
- stream_stack.clear();
186
+ _current_stream = nullptr;
187
+ _current_archive = nullptr;
182
188
  }
183
189
 
184
190
  bool ArchiveStackCursor::descend() {
185
- if (stream_stack.empty()) {
186
- throw std::logic_error("stream stack is empty");
191
+ if (!_current_stream) {
192
+ throw std::logic_error("current stream is empty");
187
193
  }
188
194
 
189
- auto stream = stream_stack.back();
190
-
195
+ auto stream = _current_stream;
191
196
  if (auto *archive = current_archive()) {
192
197
  if (stream && !archive->current_entry_content_ready()) {
193
198
  stream->rewind();
@@ -196,17 +201,22 @@ bool ArchiveStackCursor::descend() {
196
201
 
197
202
  PathHierarchy dummy_hierarchy = stream->source_hierarchy();
198
203
  auto archive_ptr = std::make_shared<StreamArchive>(std::move(stream), options_snapshot);
199
- append_single(dummy_hierarchy, std::string{});
200
- stream_stack.emplace_back(std::make_shared<EntryPayloadStream>(archive_ptr, std::move(dummy_hierarchy)));
204
+ _current_archive = archive_ptr;
205
+ _current_stream = nullptr;
201
206
  return true;
202
207
  }
203
208
 
204
209
  bool ArchiveStackCursor::ascend() {
205
- if (stream_stack.size() <= 0) {
210
+ if (depth() <= 0) {
206
211
  return false;
207
212
  }
208
213
 
209
- stream_stack.pop_back();
214
+ if (_current_archive) {
215
+ _current_stream = _current_archive->get_stream();
216
+ _current_archive = _current_archive->parent_archive();
217
+ } else {
218
+ _current_stream = nullptr;
219
+ }
210
220
 
211
221
  return true;
212
222
  }
@@ -217,6 +227,8 @@ bool ArchiveStackCursor::next() {
217
227
  return false;
218
228
  }
219
229
 
230
+ _current_stream = nullptr;
231
+
220
232
  while (true) {
221
233
  if (!archive->skip_to_next_header()) {
222
234
  return false;
@@ -225,7 +237,8 @@ bool ArchiveStackCursor::next() {
225
237
  break;
226
238
  }
227
239
  }
228
- stream_stack.back() = create_stream(current_entry_hierarchy());
240
+
241
+ _current_stream = create_stream(current_entry_hierarchy());
229
242
  return true;
230
243
  }
231
244
 
@@ -233,33 +246,32 @@ bool ArchiveStackCursor::synchronize_to_hierarchy(const PathHierarchy &target_hi
233
246
  if (target_hierarchy.empty()) {
234
247
  throw_entry_fault("target hierarchy cannot be empty", {});
235
248
  }
236
-
237
- const size_t last_depth = target_hierarchy.size() - 1;
238
- if (stream_stack.size() < target_hierarchy.size()) {
239
- stream_stack.resize(target_hierarchy.size());
249
+
250
+ // 1. Ascend until we find a common ancestor
251
+ while (depth() > 0) {
252
+ auto current_h = _current_archive->source_hierarchy();
253
+ if (current_h.size() <= target_hierarchy.size() &&
254
+ hierarchies_equal(current_h, pathhierarchy_prefix_until(target_hierarchy, current_h.size() - 1))) {
255
+ break;
256
+ }
257
+ ascend();
240
258
  }
241
- for (size_t depth = 0; depth < target_hierarchy.size(); ++depth) {
242
- auto prefix = pathhierarchy_prefix_until(target_hierarchy, depth);
243
- auto stream = stream_stack[depth];
244
259
 
245
- // Reuse the existing stream when it already matches this prefix.
246
- if (stream && hierarchies_equal(stream->source_hierarchy(), prefix)) {
247
- continue;
260
+ // 2. Descend to target
261
+ for (size_t d = depth(); d < target_hierarchy.size(); ++d) {
262
+ auto prefix = pathhierarchy_prefix_until(target_hierarchy, d);
263
+
264
+ if (!_current_stream || !hierarchies_equal(_current_stream->source_hierarchy(), prefix)) {
265
+ _current_stream = create_stream(prefix);
266
+ _current_stream->rewind();
248
267
  }
249
- // Shrink the stack to the current depth before creating a fresh stream.
250
- stream_stack.resize(depth+1);
251
- stream = create_stream(prefix);
252
- stream_stack.back() = stream;
253
- stream->rewind();
254
-
255
- if (depth == last_depth) {
256
- return true;
268
+
269
+ if (d < target_hierarchy.size() - 1) {
270
+ descend();
257
271
  }
258
- // Descend into the archive for the next level of the hierarchy.
259
- descend();
260
272
  }
261
-
262
- return true;
273
+
274
+ return true;
263
275
  }
264
276
 
265
277
  ssize_t ArchiveStackCursor::read(void *buff, size_t len) {
@@ -267,38 +279,22 @@ ssize_t ArchiveStackCursor::read(void *buff, size_t len) {
267
279
  return 0;
268
280
  }
269
281
 
270
- if (stream_stack.empty()) {
271
- throw_entry_fault("Stream stack is empty", {});
282
+ if (StreamArchive *archive = current_archive()) {
283
+ return archive->read_current(buff, len);
272
284
  }
273
285
 
274
- auto stream = stream_stack.back();
275
- ssize_t bytes = 0;
276
- bytes = stream->read(buff, len);
277
-
278
- if (bytes < 0) {
279
- const std::string message = "Failed to read from active stream";
280
- throw_entry_fault(message, current_entry_hierarchy());
286
+ if (_current_stream) {
287
+ return _current_stream->read(buff, len);
281
288
  }
282
-
283
- return bytes;
289
+ return 0;
284
290
  }
285
291
 
286
292
  StreamArchive *ArchiveStackCursor::current_archive() {
287
- if (stream_stack.size() <= 0) {
288
- return nullptr;
289
- }
290
-
291
- const auto stream = std::dynamic_pointer_cast<EntryPayloadStream>(stream_stack.back());
292
- if (!stream) {
293
- return nullptr;
294
- }
295
-
296
- auto parent_archive = stream->parent_archive();
297
- return parent_archive ? parent_archive.get() : nullptr;
293
+ return _current_archive.get();
298
294
  }
299
295
 
300
296
  PathHierarchy ArchiveStackCursor::current_entry_hierarchy() {
301
- if (stream_stack.empty() || !stream_stack.front()) {
297
+ if (depth() == 0 || (!_current_stream && !_current_archive)) {
302
298
  return {};
303
299
  }
304
300
 
@@ -310,7 +306,7 @@ PathHierarchy ArchiveStackCursor::current_entry_hierarchy() {
310
306
  return path;
311
307
  }
312
308
 
313
- return stream_stack.front()->source_hierarchy();
309
+ return _current_stream->source_hierarchy();
314
310
  }
315
311
 
316
312
  std::shared_ptr<IDataStream> ArchiveStackCursor::create_stream(const PathHierarchy &hierarchy) {
@@ -322,9 +318,7 @@ std::shared_ptr<IDataStream> ArchiveStackCursor::create_stream(const PathHierarc
322
318
  }
323
319
  return std::make_shared<SystemFileStream>(hierarchy);
324
320
  }
325
- auto stream = std::dynamic_pointer_cast<EntryPayloadStream>(stream_stack.back());
326
-
327
- return std::make_shared<EntryPayloadStream>(stream->parent_archive(), hierarchy);
321
+ return std::make_shared<EntryPayloadStream>(_current_archive, hierarchy);
328
322
  }
329
323
 
330
324
  } // namespace archive_r
@@ -31,6 +31,9 @@ public:
31
31
  void rewind() override;
32
32
 
33
33
  PathHierarchy source_hierarchy() const;
34
+ std::shared_ptr<StreamArchive> parent_archive() const;
35
+
36
+ std::shared_ptr<IDataStream> get_stream() const { return _stream; }
34
37
 
35
38
  private:
36
39
  static la_ssize_t read_callback_bridge(struct archive *a, void *client_data, const void **buff);
@@ -75,7 +78,7 @@ struct ArchiveStackCursor {
75
78
 
76
79
  void configure(const ArchiveOption &options);
77
80
  void reset();
78
- bool has_stream() const { return !stream_stack.empty(); }
81
+ bool has_stream() const { return _current_stream != nullptr; }
79
82
 
80
83
  bool descend();
81
84
  bool ascend();
@@ -83,7 +86,16 @@ struct ArchiveStackCursor {
83
86
  bool synchronize_to_hierarchy(const PathHierarchy &hierarchy);
84
87
  ssize_t read(void *buffer, size_t len);
85
88
 
86
- size_t depth() const { return stream_stack.size(); }
89
+ size_t depth() const {
90
+ size_t d = 0;
91
+ auto a = _current_archive;
92
+ while (a) {
93
+ d++;
94
+ a = a->parent_archive();
95
+ }
96
+ return d;
97
+ }
98
+
87
99
  StreamArchive *current_archive();
88
100
 
89
101
  PathHierarchy current_entry_hierarchy();
@@ -91,7 +103,10 @@ struct ArchiveStackCursor {
91
103
  std::shared_ptr<IDataStream> create_stream(const PathHierarchy &hierarchy);
92
104
 
93
105
  ArchiveOption options_snapshot;
94
- std::vector<std::shared_ptr<IDataStream>> stream_stack;
106
+
107
+ private:
108
+ std::shared_ptr<IDataStream> _current_stream;
109
+ std::shared_ptr<StreamArchive> _current_archive;
95
110
  };
96
111
 
97
112
  } // namespace archive_r
@@ -20,6 +20,7 @@ struct MultiVolumeStreamBase::Impl {
20
20
  std::vector<int64_t> part_offsets;
21
21
  std::size_t total_parts = 0;
22
22
  std::size_t active_part_index = 0;
23
+ std::size_t open_part_index = 0;
23
24
  bool part_open = false;
24
25
  int64_t logical_offset = 0;
25
26
  int64_t total_size = -1;
@@ -84,7 +85,7 @@ void MultiVolumeStreamBase::rewind() {
84
85
  }
85
86
 
86
87
  bool MultiVolumeStreamBase::at_end() const {
87
- return (_impl->active_part_index >= _impl->total_parts) && !_impl->part_open;
88
+ return _impl->active_part_index >= _impl->total_parts;
88
89
  }
89
90
 
90
91
  int64_t MultiVolumeStreamBase::seek(int64_t offset, int whence) {
@@ -122,14 +123,14 @@ int64_t MultiVolumeStreamBase::seek(int64_t offset, int whence) {
122
123
  int64_t MultiVolumeStreamBase::tell() const { return _impl->logical_offset; }
123
124
 
124
125
  void MultiVolumeStreamBase::Impl::ensure_part_active(std::size_t part_index) {
125
- if (part_open && active_part_index == part_index) {
126
+ if (part_open && open_part_index == part_index) {
126
127
  return;
127
128
  }
128
129
 
129
130
  self.deactivate_active_part();
130
131
  PathHierarchy single_part = pathhierarchy_select_single_part(self._logical_path, part_index);
131
132
  self.open_single_part(single_part);
132
- active_part_index = part_index;
133
+ open_part_index = part_index;
133
134
  part_open = true;
134
135
  }
135
136
 
@@ -144,7 +145,6 @@ bool MultiVolumeStreamBase::Impl::advance_to_next_part() {
144
145
  if (active_part_index >= total_parts) {
145
146
  return false;
146
147
  }
147
- self.deactivate_active_part();
148
148
  ++active_part_index;
149
149
  return active_part_index < total_parts;
150
150
  }
@@ -78,7 +78,9 @@ SystemFileStream::SystemFileStream(PathHierarchy logical_path)
78
78
  }
79
79
  }
80
80
 
81
- SystemFileStream::~SystemFileStream() = default;
81
+ SystemFileStream::~SystemFileStream() {
82
+ deactivate_active_part();
83
+ }
82
84
 
83
85
  void SystemFileStream::open_single_part(const PathHierarchy &single_part) {
84
86
  const PathEntry &entry = single_part.back();
@@ -104,8 +106,10 @@ void SystemFileStream::open_single_part(const PathHierarchy &single_part) {
104
106
  }
105
107
 
106
108
  void SystemFileStream::close_single_part() {
107
- std::fclose(_handle);
108
- _handle = nullptr;
109
+ if (_handle) {
110
+ std::fclose(_handle);
111
+ _handle = nullptr;
112
+ }
109
113
  _active_path.clear();
110
114
  }
111
115
 
@@ -10,7 +10,6 @@
10
10
  #include "archive_type.h"
11
11
  #include "entry_fault_error.h"
12
12
  #include "system_file_stream.h"
13
- #include <iostream>
14
13
  #include <filesystem>
15
14
  #include <memory>
16
15
  #include <stdexcept>
data/lib/archive_r.rb CHANGED
@@ -35,7 +35,7 @@ rescue LoadError
35
35
  end
36
36
 
37
37
  module Archive_r
38
- VERSION = "0.1.3"
38
+ VERSION = "0.1.6"
39
39
  # Common archive formats excluding libarchive's mtree/raw pseudo formats
40
40
  STANDARD_FORMATS = %w[
41
41
  7zip ar cab cpio empty iso9660 lha rar tar warc xar zip
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: archive_r_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - raizo.tcs
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-12-04 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rake
@@ -91,7 +90,6 @@ metadata:
91
90
  source_code_uri: https://github.com/raizo-tcs/archive_r
92
91
  bug_tracker_uri: https://github.com/raizo-tcs/archive_r/issues
93
92
  changelog_uri: https://github.com/raizo-tcs/archive_r/releases
94
- post_install_message:
95
93
  rdoc_options: []
96
94
  require_paths:
97
95
  - lib
@@ -106,8 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
104
  - !ruby/object:Gem::Version
107
105
  version: '0'
108
106
  requirements: []
109
- rubygems_version: 3.4.20
110
- signing_key:
107
+ rubygems_version: 4.0.0
111
108
  specification_version: 4
112
109
  summary: Ruby bindings for archive_r that traverse nested archives without temp extraction
113
110
  test_files: []