bzip2-ffi 1.0.0 → 1.1.0

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.
@@ -1,10 +1,13 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
1
4
  require 'ffi'
2
5
 
3
6
  module Bzip2
4
7
  module FFI
5
8
  # FFI bindings for the libbz2 low-level interface.
6
9
  #
7
- # See bzlib.h and http://bzip.org/docs.html.
10
+ # See bzlib.h and https://sourceware.org/bzip2/docs.html.
8
11
  #
9
12
  # @private
10
13
  module Libbz2 #:nodoc:
@@ -50,7 +53,7 @@ module Bzip2
50
53
 
51
54
  :bzalloc, :bzalloc,
52
55
  :bzfree, :bzfree,
53
- :opaque, :pointer
56
+ :opaque, :pointer
54
57
  end
55
58
 
56
59
  # int BZ2_bzCompressInt(bz_stream* strm, int blockSize100k, int verbosity, int workFactor);
@@ -61,7 +64,7 @@ module Bzip2
61
64
 
62
65
  # int BZ2_bzCompressEnd (bz_stream* strm);
63
66
  attach_function :BZ2_bzCompressEnd, [BzStream.by_ref], :int
64
-
67
+
65
68
  # int BZ2_bzDecompressInit (bz_stream *strm, int verbosity, int small);
66
69
  attach_function :BZ2_bzDecompressInit, [BzStream.by_ref, :int, :int], :int
67
70
 
@@ -71,5 +74,6 @@ module Bzip2
71
74
  # int BZ2_bzDecompressEnd (bz_stream *strm);
72
75
  attach_function :BZ2_bzDecompressEnd, [BzStream.by_ref], :int
73
76
  end
77
+ private_constant :Libbz2
74
78
  end
75
79
  end
@@ -1,10 +1,13 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
1
4
  require 'pathname'
2
5
  require 'stringio'
3
6
 
4
7
  module Bzip2
5
8
  module FFI
6
- # `Reader` reads and decompresses a bzip2 compressed stream or file. The
7
- # public instance methods of `Reader` are intended to be equivalent to those
9
+ # {Reader} reads and decompresses a bzip2 compressed stream or file. The
10
+ # public instance methods of {Reader} are intended to be equivalent to those
8
11
  # of a standard `IO` object.
9
12
  #
10
13
  # Data can be read as a stream using {open} and {#read}, for example:
@@ -15,7 +18,7 @@ module Bzip2
15
18
  # end
16
19
  # end
17
20
  #
18
- # Alternatively, without passing a block to `open`:
21
+ # Alternatively, without passing a block to {open}:
19
22
  #
20
23
  # reader = Bzip2::FFI::Reader.open(io_or_path)
21
24
  # begin
@@ -26,77 +29,88 @@ module Bzip2
26
29
  # reader.close
27
30
  # end
28
31
  #
29
- # An entire bzip2 structure can be read in a single step using {read}:
32
+ # All the available bzipped data can be read in a single step using {read}:
30
33
  #
31
34
  # uncompressed = Bzip2::FFI::Reader.read(io_or_path)
32
35
  #
33
- # The {open} and {read} methods accept either an `IO`-like object or a file
34
- # path. `IO`-like objects must have a `read` method. Paths can be given as
36
+ # The {open} and {read} methods accept either an IO-like object or a file
37
+ # path. IO-like objects must have a `#read` method. Paths can be given as
35
38
  # either a `String` or `Pathname`.
36
39
  #
37
40
  # No character conversion is performed on decompressed bytes. The {read} and
38
41
  # {#read} methods return instances of `String` that represent the raw
39
- # decompressed bytes, with `encoding` set to `Encoding::ASCII_8BIT` (also
42
+ # decompressed bytes, with `#encoding` set to `Encoding::ASCII_8BIT` (also
40
43
  # known as `Encoding::BINARY`).
41
44
  #
42
- # `Reader` will read a single bzip2 compressed structure from the given
43
- # stream or file. If the stream or file contains data beyond the end of
44
- # the bzip2 structure, such data may be read during decompression. If such
45
- # an overread has occurred and the `IO`-like object being read from has a
46
- # `seek` method, `Reader` will use it to reposition the stream to the byte
47
- # immediately following the end of the bzip2 structure. If `seek` raises
48
- # an `IOError`, it will be caught and the stream position will be left
49
- # unchanged.
45
+ # {Reader} will normally read all consecutive bzip2 compressed structure
46
+ # from the given stream or file (unless the `:first_only` parameter is
47
+ # specified - see {open}). If the stream or file contains additional data
48
+ # beyond the end of the compressed bzip2 data, it may be read during
49
+ # decompression. If such an overread has occurred and the IO-like object
50
+ # being read from has a `#seek` method, {Reader} will use it to reposition
51
+ # the stream to the byte immediately following the end of the compressed
52
+ # bzip2 data. If `#seek` raises an `IOError`, it will be caught and the
53
+ # stream position will be left unchanged.
50
54
  class Reader < IO
51
55
  # The number of bytes read from the compressed data stream at a time.
52
56
  #
53
57
  # @private
54
58
  READ_BUFFER_SIZE = 4096 #:nodoc:
59
+ private_constant :READ_BUFFER_SIZE
55
60
 
56
61
  # The number of uncompressed bytes to read at a time when using {#read}
57
62
  # without a length.
58
63
  #
59
64
  # @private
60
65
  DEFAULT_DECOMPRESS_COUNT = 4096 #:nodoc:
66
+ private_constant :DEFAULT_DECOMPRESS_COUNT
61
67
 
62
68
  class << self
63
69
  # Use send to keep this hidden from YARD (visibility tag does not work).
64
70
  send(:public, :new)
65
71
 
66
- # Opens a {Reader} to read and decompress data from either an `IO`-like
67
- # object or a file. `IO`-like objects must have a `read` method. Files
72
+ # Opens a {Reader} to read and decompress data from either an IO-like
73
+ # object or a file. IO-like objects must have a `#read` method. Files
68
74
  # can be specified using either a `String` containing the file path or a
69
75
  # `Pathname`.
70
76
  #
71
- # If no block is given, the opened `Reader` instance is returned. After
77
+ # If no block is given, the opened {Reader} instance is returned. After
72
78
  # use, the instance should be closed using the {#close} method.
73
79
  #
74
- # If a block is given, it will be passed the opened `Reader` instance
75
- # as an argument. After the block terminates, the `Reader` instance will
76
- # automatically be closed. `open` will then return the result of the
80
+ # If a block is given, it will be passed the opened {Reader} instance
81
+ # as an argument. After the block terminates, the {Reader} instance will
82
+ # automatically be closed. {open} will then return the result of the
77
83
  # block.
78
84
  #
79
85
  # The following options can be specified using the `options` `Hash`:
80
86
  #
81
- # * `:autoclose` - When passing an `IO`-like object, set to `true` to
82
- # close the `IO` when the `Reader` instance is closed.
87
+ # * `:autoclose` - When passing an IO-like object, set to `true` to
88
+ # close it when the {Reader} instance is closed.
89
+ # * `:first_only` - Bzip2 files can contain multiple consecutive
90
+ # compressed strctures. Normally all the structures
91
+ # will be decompressed with the decompressed bytes
92
+ # concatenated. Set to `true` to only read the first
93
+ # structure.
83
94
  # * `:small` - Set to `true` to use an alternative decompression
84
95
  # algorithm that uses less memory, but at the cost of
85
96
  # decompressing more slowly (roughly 2,300 kB less memory
86
97
  # at about half the speed).
87
98
  #
88
- # If an `IO`-like object that has a `binmode` method is passed to
89
- # `open`, `binmode` will be called on `io_or_path` before yielding to
90
- # the block or returning.
99
+ # If an IO-like object that has a `#binmode` method is passed to {open},
100
+ # `#binmode` will be called on `io_or_path` before yielding to the block
101
+ # or returning.
91
102
  #
92
- # @param io_or_path [Object] Either an `IO`-like object with a `read`
103
+ # @param io_or_path [Object] Either an IO-like object with a `#read`
93
104
  # method or a file path as a `String` or
94
105
  # `Pathname`.
95
106
  # @param options [Hash] Optional parameters (`:autoclose` and `:small`).
96
- # @return [Object] The opened `Reader` instance if no block is given, or
107
+ # @yield [reader] If a block is given, it is yielded to.
108
+ # @yieldparam reader [Reader] The new {Reader} instance.
109
+ # @yieldresult [Object] A result to be returned as the result of {open}.
110
+ # @return [Object] The opened {Reader} instance if no block is given, or
97
111
  # the result of the block if a block is given.
98
112
  # @raise [ArgumentError] If `io_or_path` is _not_ a `String`, `Pathname`
99
- # or an `IO`-like object with a `read` method.
113
+ # or an IO-like object with a `#read` method.
100
114
  # @raise [Errno::ENOENT] If the specified file does not exist.
101
115
  # @raise [Error::Bzip2Error] If an error occurs when initializing
102
116
  # libbz2.
@@ -113,37 +127,42 @@ module Bzip2
113
127
  end
114
128
 
115
129
  # Reads and decompresses and entire bzip2 compressed structure from
116
- # either an `IO`-like object or a file and returns the decompressed
117
- # bytes as a `String`. `IO`-like objects must have a `read` method.
118
- # Files can be specified using either a `String` containing the file
119
- # path or a `Pathname`.
130
+ # either an IO-like object or a file and returns the decompressed bytes
131
+ # as a `String`. IO-like objects must have a `#read` method. Files can
132
+ # be specified using either a `String` containing the file path or a
133
+ # `Pathname`.
120
134
  #
121
135
  # The following options can be specified using the `options` `Hash`:
122
136
  #
123
- # * `:autoclose` - When passing an `IO`-like object, set to `true` to
124
- # close the `IO` when the compressed data has been
125
- # read.
137
+ # * `:autoclose` - When passing an IO-like object, set to `true` to
138
+ # close it when the compressed data has been read.
139
+ # * `:first_only` - Bzip2 files can contain multiple consecutive
140
+ # compressed strctures. Normally all the structures
141
+ # will be decompressed with the decompressed bytes
142
+ # concatenated. Set to `true` to only read the first
143
+ # structure.
126
144
  # * `:small` - Set to `true` to use an alternative decompression
127
145
  # algorithm that uses less memory, but at the cost of
128
146
  # decompressing more slowly (roughly 2,300 kB less memory
129
147
  # at about half the speed).
130
148
  #
131
- # No character conversion is performed on decompressed bytes. `read`
149
+ # No character conversion is performed on decompressed bytes. {read}
132
150
  # returns a `String` that represents the raw decompressed bytes, with
133
151
  # `encoding` set to `Encoding::ASCII_8BIT` (also known as
134
152
  # `Encoding::BINARY`).
135
153
  #
136
- # If an `IO`-like object that has a `binmode` method is passed to
137
- # `read`, `binmode` will be called on `io_or_path` before any compressed
138
- # data is read.
154
+ # If an IO-like object that has a `#inmode` method is passed to {read},
155
+ # `#binmode` will be called on `io_or_path` before any compressed data
156
+ # is read.
139
157
  #
140
- # @param io_or_path [Object] Either an `IO`-like object with a `read`
158
+ # @param io_or_path [Object] Either an IO-like object with a `#read`
141
159
  # method or a file path as a `String` or
142
160
  # `Pathname`.
143
- # @param options [Hash] Optional parameters (`:autoclose` and `:small`).
161
+ # @param options [Hash] Optional parameters (`:autoclose`, `:first_only`
162
+ # and `:small`).
144
163
  # @return [String] The decompressed data.
145
164
  # @raise [ArgumentError] If `io_or_path` is _not_ a `String`, `Pathname`
146
- # or an `IO`-like object with a `read` method.
165
+ # or an IO-like object with a `#read` method.
147
166
  # @raise [Errno::ENOENT] If the specified file does not exist.
148
167
  # @raise [Error::Bzip2Error] If an error occurs when initializing
149
168
  # libbz2 or decompressing data.
@@ -155,11 +174,11 @@ module Bzip2
155
174
 
156
175
  private
157
176
 
158
- # Returns a Proc that can be used as a finalizer to call
159
- # `BZ2_bzDecompressEnd` with the given `stream`.
177
+ # Returns a `Proc` that can be used as a finalizer to call
178
+ # {Libbz2::BZ2_bzDecompressEnd} with the given `stream`.
160
179
  #
161
180
  # @param stream [Libbz2::BzStream] The stream that should be passed to
162
- # `BZ2_bzDecompressEnd`.
181
+ # {Libbz2::BZ2_bzDecompressEnd}.
163
182
  def finalize(stream)
164
183
  ->(id) do
165
184
  Libbz2::BZ2_bzDecompressEnd(stream)
@@ -167,65 +186,74 @@ module Bzip2
167
186
  end
168
187
  end
169
188
 
170
- # Initializes a {Reader} to read compressed data from an `IO`-like object
171
- # (`io`). `io` must have a `read` method.
189
+ # Initializes a {Reader} to read compressed data from an IO-like object
190
+ # (`io`). `io` must have a `#read` method.
172
191
  #
173
192
  # The following options can be specified using the `options` `Hash`:
174
193
  #
175
- # * `:autoclose` - Set to `true` to close `io` when the `Reader` instance
194
+ # * `:autoclose` - Set to `true` to close `io` when the {Reader} instance
176
195
  # is closed.
196
+ # * `:first_only` - Bzip2 files can contain multiple consecutive
197
+ # compressed strctures. Normally all the structures will
198
+ # be decompressed with the decompressed bytes
199
+ # concatenated. Set to `true` to only read the first
200
+ # structure.
177
201
  # * `:small` - Set to `true` to use an alternative decompression
178
202
  # algorithm that uses less memory, but at the cost of
179
203
  # decompressing more slowly (roughly 2,300 kB less memory
180
204
  # at about half the speed).
181
205
  #
182
- # `binmode` is called on `io` if `io` responds to `binmode`.
206
+ # `#binmode` is called on `io` if `io` responds to `#binmode`.
183
207
  #
184
- # After use, the `Reader` instance should be closed using the {#close}
208
+ # After use, the {Reader} instance should be closed using the {#close}
185
209
  # method.
186
210
  #
187
- # @param io [Object] An `IO`-like object with a `read` method.
188
- # @param options [Hash] Optional parameters (`:autoclose` and `:small`).
189
- # @raise [ArgumentError] If `io` is `nil` or does not respond to `read`.
211
+ # @param io [Object] An IO-like object with a `#read` method.
212
+ # @param options [Hash] Optional parameters (`:autoclose`, `:first_only`
213
+ # and `:small`).
214
+ # @raise [ArgumentError] If `io` is `nil` or does not respond to `#read`.
190
215
  # @raise [Error::Bzip2Error] If an error occurs when initializing libbz2.
191
216
  def initialize(io, options = {})
192
217
  super
193
218
  raise ArgumentError, 'io must respond to read' unless io.respond_to?(:read)
194
219
 
195
- small = options[:small]
220
+ @first_only = options[:first_only]
221
+ @small = options[:small] ? 1 : 0
196
222
 
197
223
  @in_eof = false
198
224
  @out_eof = false
199
225
  @in_buffer = nil
226
+ @structure_number = 1
227
+ @structure_start_pos = 0
228
+ @in_pos = 0
229
+ @out_pos = 0
200
230
 
201
- check_error(Libbz2::BZ2_bzDecompressInit(stream, 0, small ? 1 : 0))
202
-
203
- ObjectSpace.define_finalizer(self, self.class.send(:finalize, stream))
231
+ decompress_init(stream)
204
232
  end
205
233
 
206
234
  # Ends decompression and closes the {Reader}.
207
235
  #
208
236
  # If the {open} method is used with a block, it is not necessary to call
209
- # `close`. Otherwise, `close` should be called once the `Reader` is no
237
+ # {#close}. Otherwise, {#close} should be called once the {Reader} is no
210
238
  # longer needed.
211
239
  #
212
240
  # @return [NilType] `nil`.
213
- # @raise [IOError] If the `Reader` has already been closed.
241
+ # @raise [IOError] If the {Reader} has already been closed.
214
242
  def close
215
243
  s = stream
216
244
 
217
245
  unless @out_eof
218
246
  decompress_end(s)
219
247
  end
220
-
248
+
221
249
  s[:next_in] = nil
222
250
  s[:next_out] = nil
223
-
251
+
224
252
  if @in_buffer
225
253
  @in_buffer.free
226
254
  @in_buffer = nil
227
255
  end
228
-
256
+
229
257
  super
230
258
  end
231
259
 
@@ -240,17 +268,17 @@ module Bzip2
240
268
  # A result of `nil` or a `String` with a length less than `length` bytes
241
269
  # indicates that the end of the decompressed data has been reached.
242
270
  #
243
- # If `length` is `nil`, `read` reads until the end of the decompressed
271
+ # If `length` is `nil`, {#read} reads until the end of the decompressed
244
272
  # data, returning the uncompressed bytes as a `String`.
245
273
  #
246
- # If `length` is 0, `read` returns an empty `String`.
274
+ # If `length` is 0, {#read} returns an empty `String`.
247
275
  #
248
276
  # If the optional `buffer` argument is present, it must reference a
249
277
  # `String` that will receive the decompressed data. `buffer` will
250
- # contain only the decompressed data after the call to `read`, even if it
278
+ # contain only the decompressed data after the call to {#read}, even if it
251
279
  # is not empty beforehand.
252
280
  #
253
- # No character conversion is performed on decompressed bytes. `read`
281
+ # No character conversion is performed on decompressed bytes. {#read}
254
282
  # returns a `String` that represents the raw decompressed bytes, with
255
283
  # `encoding` set to `Encoding::ASCII_8BIT` (also known as
256
284
  # `Encoding::BINARY`).
@@ -267,7 +295,7 @@ module Bzip2
267
295
  # the end of the decompressed data has been reached.
268
296
  # @raise [ArgumentError] If `length` is negative.
269
297
  # @raise [Error::Bzip2Error] If an error occurs during decompression.
270
- # @raise [IOError] If the `Reader` has been closed.
298
+ # @raise [IOError] If the {Reader} has been closed.
271
299
  def read(length = nil, buffer = nil)
272
300
  if buffer
273
301
  buffer.clear
@@ -279,11 +307,11 @@ module Bzip2
279
307
 
280
308
  if length == 0
281
309
  check_closed
282
- return buffer || ''
310
+ return buffer || String.new
283
311
  end
284
312
 
285
313
  decompressed = decompress(length)
286
-
314
+
287
315
  return nil unless decompressed
288
316
  buffer ? buffer << decompressed : decompressed
289
317
  else
@@ -292,11 +320,11 @@ module Bzip2
292
320
  # StringIO#binmode is a no-op, but call in case it is implemented in
293
321
  # future versions.
294
322
  result.binmode
295
-
323
+
296
324
  result.set_encoding(Encoding::ASCII_8BIT)
297
325
 
298
326
  loop do
299
- decompressed = decompress(DEFAULT_DECOMPRESS_COUNT)
327
+ decompressed = decompress(DEFAULT_DECOMPRESS_COUNT)
300
328
  break unless decompressed
301
329
  result.write(decompressed)
302
330
  break if decompressed.bytesize < DEFAULT_DECOMPRESS_COUNT
@@ -306,6 +334,29 @@ module Bzip2
306
334
  end
307
335
  end
308
336
 
337
+ # Returns `true` if decompression has completed, otherwise `false`.
338
+ #
339
+ # Note that it is possible for `false` to be returned after all the
340
+ # decompressed data has been read. In such cases, the next call to {#read}
341
+ # will detect the end of the bzip2 structure and set {#eof?} to `true`.
342
+ #
343
+ # @return [Boolean] If decompression has completed, otherwise `false`.
344
+ # @raise [IOError] If the {Reader} has been closed.
345
+ def eof?
346
+ check_closed
347
+ @out_eof
348
+ end
349
+ alias eof eof?
350
+
351
+ # Returns the number of decompressed bytes that have been read.
352
+ #
353
+ # @return [Integer] The number of decompressed bytes that have been read.
354
+ # @raise [IOError] If the {Reader} has been closed.
355
+ def pos
356
+ check_closed
357
+ @out_pos
358
+ end
359
+
309
360
  private
310
361
 
311
362
  # Attempts to decompress and return `count` bytes.
@@ -315,9 +366,9 @@ module Bzip2
315
366
  # @return [String] The decompressed data as a `String` with ASCII-8BIT
316
367
  # encoding, or `nil` if length was a positive integer and
317
368
  # the end of the decompressed data has been reached.
318
- # @raise [ArgumentError] if `count` is not greater than or equal to 1.
369
+ # @raise [ArgumentError] If `count` is not greater than or equal to 1.
319
370
  # @raise [Error::Bzip2Error] If an error occurs during decompression.
320
- # @raise [IOError] If the `Reader` has been closed.
371
+ # @raise [IOError] If the {Reader} has been closed.
321
372
  def decompress(count)
322
373
  raise ArgumentError, "count must be a positive integer" unless count >= 1
323
374
  s = stream
@@ -335,6 +386,7 @@ module Bzip2
335
386
  bytes = io.read(READ_BUFFER_SIZE)
336
387
 
337
388
  if bytes && bytes.bytesize > 0
389
+ @in_pos += bytes.bytesize
338
390
  @in_eof = bytes.bytesize < READ_BUFFER_SIZE
339
391
  @in_buffer = ::FFI::MemoryPointer.new(1, bytes.bytesize)
340
392
  @in_buffer.write_bytes(bytes)
@@ -345,8 +397,15 @@ module Bzip2
345
397
  end
346
398
  end
347
399
 
400
+ # Reached the end of input without reading anything in the current
401
+ # bzip2 structure. No more data to process.
402
+ if @in_pos == @structure_start_pos
403
+ @out_eof = true
404
+ break
405
+ end
406
+
348
407
  prev_avail_out = s[:avail_out]
349
-
408
+
350
409
  res = Libbz2::BZ2_bzDecompress(s)
351
410
 
352
411
  if s[:avail_in] == 0 && @in_buffer
@@ -355,43 +414,51 @@ module Bzip2
355
414
  @in_buffer = nil
356
415
  end
357
416
 
358
- check_error(res)
359
-
360
- if res == Libbz2::BZ_STREAM_END
361
- # The input could contain data after the end of the bzip2 stream.
362
- #
363
- # s[:avail_in] will contain the number of bytes that have been
364
- # read from io, but not been consumed by BZ2_bzDecompress.
417
+ if @structure_number > 1 && res == Libbz2::BZ_DATA_ERROR_MAGIC
418
+ # Found something other than the bzip2 magic bytes after the end
419
+ # of a bzip2 structure.
365
420
  #
366
421
  # Attempt to move the input stream back by the amount that has
367
422
  # been over-read.
368
- if s[:avail_in] > 0 && io.respond_to?(:seek)
369
- io.seek(-s[:avail_in], ::IO::SEEK_CUR) rescue IOError
370
- end
371
-
372
- if @in_buffer
373
- s[:next_in] = nil
374
- @in_buffer.free
375
- @in_buffer = nil
376
- end
377
-
423
+ attempt_seek_to_structure_start
378
424
  decompress_end(s)
379
-
380
425
  @out_eof = true
381
426
  break
382
427
  end
383
428
 
384
- break if s[:avail_out] == 0
429
+ check_error(res)
430
+
431
+ if res == Libbz2::BZ_STREAM_END
432
+ decompress_end(s)
385
433
 
386
- # No more input available and calling BZ2_bzDecompress didn't
387
- # advance the output. Raise an error.
388
- if @in_eof && prev_avail_out == s[:avail_out]
389
- raise Error::UnexpectedEofError.new
434
+ if (s[:avail_in] > 0 || !@in_eof) && !@first_only
435
+ # Re-initialize to read a second bzip2 structure if there is
436
+ # still input available and not restricting to the first stream.
437
+ @structure_number += 1
438
+ @structure_start_pos = @in_pos - s[:avail_in]
439
+ decompress_init(s)
440
+ else
441
+ # May have already read data after the end of the first bzip2
442
+ # structure.
443
+ attempt_seek_to_structure_start if @first_only
444
+ @out_eof = true
445
+ break
446
+ end
447
+ else
448
+ # No more input available and calling BZ2_bzDecompress didn't
449
+ # advance the output. Raise an error.
450
+ if @in_eof && s[:avail_in] == 0 && prev_avail_out == s[:avail_out]
451
+ decompress_end(s)
452
+ @out_eof = true
453
+ raise Error::UnexpectedEofError.new
454
+ end
390
455
  end
456
+
457
+ break if s[:avail_out] == 0
391
458
  end
392
459
 
393
460
  result = out_buffer.read_bytes(out_buffer.size - s[:avail_out])
394
- ensure
461
+ ensure
395
462
  out_buffer.free
396
463
  s[:next_out] = nil
397
464
  s[:avail_out] = 0
@@ -400,18 +467,51 @@ module Bzip2
400
467
  if @out_eof && result.bytesize == 0
401
468
  nil
402
469
  else
470
+ @out_pos += result.bytesize
403
471
  result
404
- end
472
+ end
473
+ end
474
+
475
+ # Attempts to reposition the compressed stream to the start of the current
476
+ # structure. Used when {Libbz2::BZ2_bzDecompress} has read beyond the end
477
+ # of a bzip2 structure.
478
+ def attempt_seek_to_structure_start
479
+ if io.respond_to?(:seek)
480
+ diff = @structure_start_pos - @in_pos
481
+ if diff < 0
482
+ begin
483
+ io.seek(diff, ::IO::SEEK_CUR)
484
+ @in_pos += diff
485
+ rescue IOError
486
+ end
487
+ end
488
+ end
489
+ end
490
+
491
+ # Calls {Libbz2::BZ2_bzDecompressInit} to initialize the decompression
492
+ # stream `s`.
493
+ #
494
+ # Defines a finalizer to ensure that the memory associated with the stream
495
+ # is deallocated.
496
+ #
497
+ # @param s [Libbz2::BzStream] The stream to initialize decompression for.
498
+ # @raise [Error::Bzip2Error] If {Libbz2::BZ2_bzDecompressInit} reports an
499
+ # error.
500
+ def decompress_init(s)
501
+ check_error(Libbz2::BZ2_bzDecompressInit(s, 0, @small))
502
+
503
+ ObjectSpace.define_finalizer(self, self.class.send(:finalize, s))
405
504
  end
406
505
 
407
- # Calls BZ2_bzDecompressEnd to release memeory associated with the
408
- # decompression stream `s`.
506
+ # Calls {Libbz2::BZ2_bzDecompressEnd} to release memory associated with
507
+ # the decompression stream `s`.
409
508
  #
410
509
  # Notifies `ObjectSpace` that it is no longer necessary to finalize the
411
- # `Reader` instance.
510
+ # {Reader} instance.
412
511
  #
413
512
  # @param s [Libbz2::BzStream] The stream to end decompression for.
414
- # @raise [Error::Bzip2Error] If `BZ2_bzDecompressEnd` reports an error.
513
+ # @raise [Error::Bzip2Error] If {Libbz2::BZ2_bzDecompressEnd} reports an
514
+ # error.
415
515
  def decompress_end(s)
416
516
  res = Libbz2::BZ2_bzDecompressEnd(s)
417
517
  ObjectSpace.undefine_finalizer(self)