ruby-xz 0.2.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,10 +1,10 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #--
3
- # (The MIT license)
4
- #
5
3
  # Basic liblzma-bindings for Ruby.
6
4
  #
7
- # Copyright © 2012, 2015 Marvin Gülker
5
+ # Copyright © 2011-2018 Marvin Gülker et al.
6
+ #
7
+ # See AUTHORS for the full list of contributors.
8
8
  #
9
9
  # Permission is hereby granted, free of charge, to any person obtaining a
10
10
  # copy of this software and associated documentation files (the ‘Software’),
@@ -35,457 +35,278 @@
35
35
  # the compressed data from; you can either pass this IO object directly
36
36
  # to the ::new method, effectively allowing you to pass any IO-like thing
37
37
  # you can imagine (just ensure it is readable), or you can pass a path
38
- # to a filename to ::open, in which case StreamReader takes care of both
39
- # opening and closing the file correctly. You can even take it one step
40
- # further and use the block form of ::new and ::open, which will automatically call
41
- # the #close method for you after the block finished. However, if you pass
42
- # an IO, remember you have to close:
43
- #
44
- # 1. The StreamReader instance.
45
- # 2. The IO object you passed to ::new.
46
- #
47
- # Do it <b>in exactly that order</b>, otherwise you may lose data.
48
- #
49
- # *WARNING*: The closing behaviour described above is subject to
50
- # change in the next major version. In the future, wrapped IO
51
- # objects are automatically closed always, regardless of whether you
52
- # passed a filename or an IO instance. This is to sync the API with
53
- # Ruby’s own Zlib::GzipReader. To prevent that, call #finish instead
54
- # of #close.
55
- #
56
- # See the +io-like+ gem’s documentation for the IO-reading methods
57
- # available for this class (although you’re probably familiar with
58
- # them through Ruby’s own IO class ;-)).
59
- #
60
- # ==Example
61
- # In this example, we’re going to use ruby-xz together with the
62
- # +archive-tar-minitar+ gem that allows to read tarballs. Used
63
- # together, the two libraries allow us to read XZ-compressed tarballs.
64
- #
65
- # require "xz"
66
- # require "archive/tar/minitar"
67
- #
68
- # XZ::StreamReader.open("foo.tar.xz") do |txz|
69
- # # This automatically closes txz
70
- # Archive::Tar::Minitar.unpack(txz, "foo")
71
- # end
38
+ # to a file to ::open, in which case StreamReader will open the path
39
+ # using Ruby's File class internally. If you use ::open's block form,
40
+ # the method will take care of properly closing both the liblzma
41
+ # stream and the File instance correctly.
72
42
  class XZ::StreamReader < XZ::Stream
73
43
 
74
- # The memory limit you set for this reader (in ::new).
44
+ # The memory limit configured for this lzma decoder.
75
45
  attr_reader :memory_limit
76
46
 
77
- # The flags you set for this reader (in ::new).
78
- attr_reader :flags
79
-
80
47
  # call-seq:
81
- # new(delegate, opts = {}) → reader
82
- # new(delegate, opts = {}){|reader| } → obj
48
+ # open(filename [, kw]) → stream_reader
49
+ # open(filename [, kw]){|sr| ...} → stream_reader
83
50
  #
84
- # Creates a new StreamReader instance. If you pass an IO,
85
- # remember you have to close *both* the resulting instance
86
- # (via the #close method) and the IO object you pass to flush
87
- # any internal buffers in order to be able to read all decompressed
88
- # data (beware Deprecations section below).
51
+ # Open the given file and wrap a new instance around it with ::new.
52
+ # If you use the block form, both the internally created File instance
53
+ # and the liblzma stream will be closed automatically for you.
89
54
  #
90
55
  # === Parameters
56
+ # [filename]
57
+ # Path to the file to open.
58
+ # [sr (block argument)]
59
+ # The created StreamReader instance.
91
60
  #
92
- # [delegate]
93
- # An IO object to read the data from, If you’re in an urgent
94
- # need to pass a plain string, use StringIO from Ruby’s
95
- # standard library. If this is an IO, it must be
96
- # opened for reading.
97
- #
98
- # [opts]
99
- # Options hash accepting these parameters (defaults indicated
100
- # in parantheses):
101
- #
102
- # [:memory_limit (LibLZMA::UINT64_MAX)]
103
- # If not XZ::LibLZMA::UINT64_MAX, makes liblzma use
104
- # no more memory than this amount of bytes.
105
- #
106
- # [:flags ([:tell_unsupported_check])]
107
- # Additional flags passed to libzlma (an array). Possible
108
- # flags are:
109
- #
110
- # [:tell_no_check]
111
- # Spit out a warning if the archive hasn’t an integrity
112
- # checksum.
113
- # [:tell_unsupported_check]
114
- # Spit out a warning if the archive has an unsupported
115
- # checksum type.
116
- # [:concatenated]
117
- # Decompress concatenated archives.
118
- #
119
- # [reader]
120
- # Block argument. self of the new instance.
61
+ # See ::new for a description of the keyword parameters.
121
62
  #
122
63
  # === Return value
64
+ # The newly created instance.
123
65
  #
124
- # The block form returns the block’s last expression, the nonblock
125
- # form returns the newly created instance.
126
- #
127
- # === Deprecations
128
- #
129
- # The old API for this method as it was documented in version 0.2.1
130
- # still works, but is deprecated. Please change to the new API as
131
- # soon as possible.
132
- #
133
- # *WARNING*: The closing behaviour of the block form is subject to
134
- # upcoming change. In the next major release the wrapped IO *will*
135
- # be automatically closed, unless you call #finish.
66
+ # === Remarks
67
+ # Starting with version 1.0.0, the block form also returns the newly
68
+ # created instance rather than the block's return value. This is
69
+ # in line with Ruby's own GzipReader.open API.
136
70
  #
137
71
  # === Example
138
- #
139
- # # Wrap it around a file
140
- # f = File.open("foo.xz")
141
- # r = XZ::StreamReader.new(f)
142
- #
143
- # # Ignore any XZ checksums (may result in invalid
144
- # # data being read!)
145
- # File.open("foo.xz") do |f|
146
- # r = XZ::StreamReader.new(f, :flags => [:tell_no_check])
147
- # end
148
- def initialize(delegate, *args)
149
- if delegate.respond_to?(:to_io)
150
- # Correct use with IO
151
- super(delegate.to_io)
152
- @autoclose = false
153
- else
154
- # Deprecated use of filename
155
- XZ.deprecate "Calling XZ::StreamReader.new with a filename is deprecated, use XZ::StreamReader.open instead."
156
-
157
- @autoclose = true
158
- super(File.open(delegate, "rb"))
159
- end
160
-
161
- # Flag for calling #finish
162
- @finish = false
163
-
164
- opts = {}
165
- if args[0].kind_of?(Hash) # New API
166
- opts = args[0]
167
- opts[:memory_limit] ||= XZ::LibLZMA::UINT64_MAX
168
- opts[:flags] ||= [:tell_unsupported_check]
169
- else # Old API
170
- # no arguments may also happen in new API
171
- unless args.empty?
172
- XZ.deprecate "Calling XZ::StreamReader.new with explicit arguments is deprecated, use an options hash instead."
173
- end
174
-
175
- opts[:memory_limit] = args[0] || XZ::LibLZMA::UINT64_MAX
176
- opts[:flags] = args[1] || [:tell_unsupported_check]
177
- end
178
-
179
- raise(ArgumentError, "Invalid memory limit set!") unless (0..XZ::LibLZMA::UINT64_MAX).include?(opts[:memory_limit])
180
- opts[:flags].each do |flag|
181
- raise(ArgumentError, "Unknown flag #{flag}!") unless [:tell_no_check, :tell_unsupported_check, :tell_any_check, :concatenated].include?(flag)
182
- end
183
-
184
- @memory_limit = opts[:memory_limit]
185
- @flags = opts[:flags]
186
-
187
- res = XZ::LibLZMA.lzma_stream_decoder(@lzma_stream,
188
- @memory_limit,
189
- @flags.inject(0){|val, flag| val | XZ::LibLZMA.const_get(:"LZMA_#{flag.to_s.upcase}")})
190
- XZ::LZMAError.raise_if_necessary(res)
191
-
192
- @input_buffer_p = FFI::MemoryPointer.new(XZ::CHUNK_SIZE)
193
-
194
- # These two are only used in #unbuffered read.
195
- @__lzma_finished = false
196
- @__lzma_action = nil
72
+ # # Normal usage
73
+ # XZ::StreamReader.open("myfile.txt.xz") do |xz|
74
+ # puts xz.read #=> I love Ruby
75
+ # end
76
+ #
77
+ # # If you really need the File instance created internally:
78
+ # file = nil
79
+ # XZ::StreamReader.open("myfile.txt.xz") do |xz|
80
+ # puts xz.read #=> I love Ruby
81
+ # file = xz.finish # prevents closing
82
+ # end
83
+ # file.close # Now close it manually
84
+ #
85
+ # # Or just don't use the block form:
86
+ # xz = XZ::StreamReader.open("myfile.txt.xz")
87
+ # puts xz.read #=> I love Ruby
88
+ # file = xz.finish
89
+ # file.close # Don't forget to close it manually (or use xz.close instead of xz.finish above).
90
+ def self.open(filename, **args)
91
+ file = File.open(filename, "rb")
92
+ reader = new(file, **args)
197
93
 
198
94
  if block_given?
199
95
  begin
200
- yield(self)
96
+ yield(reader)
201
97
  ensure
202
- close unless closed?
98
+ # Close both delegate IO and reader.
99
+ reader.close unless reader.finished?
203
100
  end
204
101
  end
102
+
103
+ reader
205
104
  end
206
105
 
207
- # call-seq:
208
- # open(filename, opts = {}) → reader
209
- # open(filename, opts = {}){|reader| …} → obj
210
- #
211
- # Opens a file from disk and wraps an XZ::StreamReader instance
212
- # around the resulting File IO object. This is a convenience
213
- # method that is equivalent to calling
214
- #
215
- # file = File.open(filename, "rb")
216
- # reader = XZ::StreamReader.new(file, opts)
217
- #
218
- # , except that you don’t have to explicitely close the File
219
- # instance, this is done automatically when you call #close.
220
- # Beware the Deprecations section in this regard.
106
+ # Creates a new instance that is wrapped around the given IO object.
221
107
  #
222
108
  # === Parameters
223
- #
224
- # [filename]
225
- # Path to a file on the disk to open. This file should
226
- # exist and be readable, otherwise you may get Errno
227
- # exceptions.
228
- #
229
- # [opts]
230
- # Options hash. See ::new for a description of the possible
231
- # options.
232
- #
233
- # [reader]
234
- # Block argument. self of the new instance.
109
+ # ==== Positional parameters
110
+ # [delegate_io]
111
+ # The underlying IO object to read the compressed data from.
112
+ # This IO object has to have been opened in binary mode,
113
+ # otherwise you are likely to receive exceptions indicating
114
+ # that the compressed data is corrupt.
115
+ #
116
+ # ==== Keyword arguments
117
+ # [memory_limit (+UINT64_MAX+)]
118
+ # If not XZ::LibLZMA::UINT64_MAX, makes liblzma
119
+ # use no more memory than +memory_limit+ bytes.
120
+ # [flags (<tt>[:tell_unsupported_check]</tt>)]
121
+ # Additional flags passed to liblzma (an array).
122
+ # Possible flags are:
123
+ #
124
+ # [:tell_no_check]
125
+ # Spit out a warning if the archive hasn't an
126
+ # integrity checksum.
127
+ # [:tell_unsupported_check]
128
+ # Spit out a warning if the archive
129
+ # has an unsupported checksum type.
130
+ # [:concatenated]
131
+ # Decompress concatenated archives.
132
+ # [external_encoding (Encoding.default_external)]
133
+ # Assume the decompressed data inside the XZ is encoded in
134
+ # this encoding. Defaults to Encoding.default_external,
135
+ # which in turn defaults to the environment.
136
+ # [internal_encoding (Encoding.default_internal)]
137
+ # Request that the data found in the XZ file (which is assumed
138
+ # to be in the encoding specified by +external_encoding+) to
139
+ # be transcoded into this encoding. Defaults to Encoding.default_internal,
140
+ # which defaults to nil, which means to not transcode anything.
235
141
  #
236
142
  # === Return value
143
+ # The newly created instance.
237
144
  #
238
- # The block form returns the block’s last expression, the nonblock
239
- # form returns the newly created XZ::StreamReader instance.
240
- #
241
- # === Deprecations
242
- #
243
- # In the API up to and including version 0.2.1 this method was an
244
- # alias for ::new. This continues to work for now, but using it
245
- # as an alias for ::new is deprecated. The next major version will
246
- # only accept a string as a parameter for this method.
145
+ # === Remarks
146
+ # The strings returned from the reader will be in the encoding specified
147
+ # by the +internal_encoding+ parameter. If that parameter is nil (default),
148
+ # then they will be in the encoding specified by +external_encoding+.
247
149
  #
248
- # *WARNING*: Future versions of ruby-xz will always close the
249
- # wrapped IO, regardless of whether you pass in your own IO or use
250
- # this convenience method! To prevent that, call the #finish method.
150
+ # This method used to accept a block in earlier versions. Since version 1.0.0,
151
+ # this behaviour has been removed to synchronise the API with Ruby's own
152
+ # GzipReader.open.
251
153
  #
252
- # === Examples
154
+ # This method doesn't close the underlying IO or the liblzma stream.
155
+ # You need to call #finish or #close manually; see ::open for a method
156
+ # that takes a block to automate this.
253
157
  #
254
- # XZ::StreamReader.new("myfile.xz"){|r| r.read}
255
- def self.open(filename, *args, &block)
256
- if filename.respond_to?(:to_io)
257
- # Deprecated use of IO
258
- XZ.deprecate "Calling XZ::StreamReader.open with an IO is deprecated, use XZ::StreamReader.new instead"
259
- new(filename.to_io, *args, &block)
260
- else
261
- # Correct use with filename
262
- file = File.open(filename, "rb")
263
-
264
- obj = new(file, *args)
265
- obj.instance_variable_set(:@autoclose, true) # Only needed during deprecation phase (see #close)
266
-
267
- if block_given?
268
- begin
269
- block.call(obj)
270
- ensure
271
- obj.close unless obj.closed?
272
- end
273
- else
274
- obj
275
- end
158
+ # === Example
159
+ # file = File.open("compressed.txt.xz", "rb") # Note binary mode
160
+ # xz = XZ::StreamReader.open(file)
161
+ # puts xz.read #=> I love Ruby
162
+ # xz.close # closes both `xz' and `file'
163
+ #
164
+ # file = File.open("compressed.txt.xz", "rb") # Note binary mode
165
+ # xz = XZ::StreamReader.open(file)
166
+ # puts xz.read #=> I love Ruby
167
+ # xz.finish # closes only `xz'
168
+ # file.close # Now close `file' manually
169
+ def initialize(delegate_io, memory_limit: XZ::LibLZMA::UINT64_MAX, flags: [:tell_unsupported_check], external_encoding: nil, internal_encoding: nil)
170
+ super(delegate_io)
171
+ raise(ArgumentError, "When specifying the internal encoding, the external encoding must also be specified") if internal_encoding && !external_encoding
172
+ raise(ArgumentError, "Memory limit out of range") unless memory_limit > 0 && memory_limit <= XZ::LibLZMA::UINT64_MAX
173
+
174
+ @memory_limit = memory_limit
175
+ @readbuf = String.new
176
+ @readbuf.force_encoding(Encoding::BINARY)
177
+
178
+ if external_encoding
179
+ encargs = []
180
+ encargs << external_encoding
181
+ encargs << internal_encoding if internal_encoding
182
+ set_encoding(*encargs)
276
183
  end
277
- end
278
184
 
279
- # Closes this StreamReader instance. Don’t use it afterwards
280
- # anymore.
281
- #
282
- # === Return value
283
- #
284
- # The total number of bytes decompressed.
285
- #
286
- # === Example
287
- #
288
- # r.close #=> 6468
289
- #
290
- # === Remarks
291
- #
292
- # If you passed an IO to ::new, this method doesn’t close it, so
293
- # you have to close it yourself.
294
- #
295
- # *WARNING*: The next major release will change this behaviour.
296
- # In the future, the wrapped IO object will always be closed.
297
- # Use the #finish method for keeping it open.
298
- def close
299
- super
185
+ @allflags = flags.reduce(0) do |val, flag|
186
+ flag = XZ::LibLZMA::LZMA_DECODE_FLAGS[flag] || raise(ArgumentError, "Unknown flag #{flag}")
187
+ val | flag
188
+ end
300
189
 
301
- # Close the XZ stream
302
- res = XZ::LibLZMA.lzma_end(@lzma_stream.pointer)
190
+ res = XZ::LibLZMA.lzma_stream_decoder(@lzma_stream.to_ptr,
191
+ @memory_limit,
192
+ @allflags)
303
193
  XZ::LZMAError.raise_if_necessary(res)
194
+ end
304
195
 
305
- unless @finish
306
- # New API: Close the wrapped IO
307
- #@delegate_io.close
308
- # uncomment on API break and remove OLD API below. Note that with
309
- # the new API that always closes the underlying IO, it is not necessary
310
- # to distinguish a self-opened IO from a wrapped preexisting IO.
311
- # The variable @autoclose can thus be removed on API break.
196
+ # Mostly like IO#read. The +length+ parameter refers to the amount
197
+ # of decompressed bytes to read, not the amount of bytes to read
198
+ # from the compressed data. That is, if you request a read of 50
199
+ # bytes, you will receive a string with a maximum length of 50
200
+ # bytes, regardless of how many bytes this was in compressed form.
201
+ #
202
+ # Return values are as per IO#read.
203
+ def read(length = nil, outbuf = String.new)
204
+ return "".force_encoding(Encoding::BINARY) if length == 0 # Shortcut; retval as per IO#read.
205
+
206
+ # Note: Querying the underlying IO as early as possible allows to
207
+ # have Ruby's own IO exceptions to bubble up.
208
+ if length
209
+ return nil if eof? # In line with IO#read
210
+ outbuf.force_encoding(Encoding::BINARY) # As per IO#read docs
211
+
212
+ # The user's request is in decompressed bytes, so it doesn't matter
213
+ # how much is actually read from the compressed file.
214
+ if @delegate_io.eof?
215
+ data = ""
216
+ action = XZ::LibLZMA::LZMA_FINISH
217
+ else
218
+ data = @delegate_io.read(XZ::CHUNK_SIZE)
219
+ action = @delegate_io.eof? ? XZ::LibLZMA::LZMA_FINISH : XZ::LibLZMA::LZMA_RUN
220
+ end
312
221
 
313
- # Old API:
314
- #If we created a File object, close this as well.
315
- if @autoclose
316
- # This does not change in the new API, so no deprecation warning.
317
- @delegate_io.close
222
+ lzma_code(data, action) { |decompressed| @readbuf << decompressed }
223
+
224
+ # If the requested amount has been read, return it.
225
+ # Also return if EOF has been reached. Note that
226
+ # String#slice! will clear the string to an empty one
227
+ # if `length' is greater than the string length.
228
+ # If EOF is not yet reached, try reading and decompresing
229
+ # more data.
230
+ if @readbuf.bytesize >= length || @delegate_io.eof?
231
+ result = @readbuf.slice!(0, length)
232
+ @pos += result.bytesize
233
+ return outbuf.replace(result)
318
234
  else
319
- XZ.deprecate "XZ::StreamReader#close will automatically close the wrapped IO in the future. Use #finish to prevent that."
235
+ return read(length, outbuf)
236
+ end
237
+ else
238
+ # Read the entire file and decompress it into memory, returning it.
239
+ while chunk = @delegate_io.read(XZ::CHUNK_SIZE)
240
+ action = @delegate_io.eof? ? XZ::LibLZMA::LZMA_FINISH : XZ::LibLZMA::LZMA_RUN
241
+ lzma_code(chunk, action) { |decompressed| @readbuf << decompressed }
320
242
  end
321
- end
322
243
 
323
- # Return the number of bytes written in total.
324
- @lzma_stream[:total_out]
325
- end
244
+ @pos += @readbuf.bytesize
326
245
 
327
- # If called in the block form of ::new or ::open, prevents the
328
- # wrapped IO from being closed, only the LZMA stream is closed
329
- # then. If called outside the block form of ::new and open, behaves
330
- # like #close, but only closes the underlying LZMA stream. The
331
- # wrapped IO object is kept open.
332
- #
333
- # === Return value
334
- #
335
- # Returns the wrapped IO object. This allows you to wire the File
336
- # instance out of a StreamReader instance that was created with
337
- # ::open.
338
- #
339
- # === Example
340
- #
341
- # # Nonblock form
342
- # f = File.open("foo.xz", "rb")
343
- # r = XZ::StreamReader.new(f)
344
- # r.finish
345
- # # f is still open here!
346
- #
347
- # # Block form
348
- # str = nil
349
- # f = XZ::StreamReader.open("foo.xz") do |r|
350
- # str = r.read
351
- # r.finish
352
- # end
353
- # # f now is an *open* File instance of mode "rb".
354
- def finish
355
- # Do not close wrapped IO object in #close
356
- @finish = true
357
- close
246
+ # Apply encoding conversion.
247
+ # First, tag the read data with the external encoding.
248
+ @readbuf.force_encoding(@external_encoding)
358
249
 
359
- @delegate_io
360
- end
250
+ # Now, transcode it to the internal encoding if that was requested.
251
+ # Otherwise return it with the external encoding as-is.
252
+ if @internal_encoding
253
+ @readbuf.encode!(@internal_encoding, @transcode_options)
254
+ outbuf.force_encoding(@internal_encoding)
255
+ else
256
+ outbuf.force_encoding(@external_encoding)
257
+ end
361
258
 
362
- # call-seq:
363
- # pos() → an_integer
364
- # tell() an_integer
365
- #
366
- # Total number of output bytes provided to you yet.
367
- def pos
368
- @lzma_stream[:total_out]
259
+ outbuf.replace(@readbuf)
260
+ @readbuf.clear
261
+ @readbuf.force_encoding(Encoding::BINARY) # Back to binary mode for further reading
262
+
263
+ return outbuf
264
+ end
369
265
  end
370
- alias tell pos
371
266
 
372
- # Instrcuts liblzma to immediately stop decompression,
373
- # rewinds the wrapped IO object and reinitalizes the
374
- # StreamReader instance with the same values passed
375
- # originally to the ::new method. The wrapped IO object
376
- # must support the +rewind+ method for this method to
377
- # work; if it doesn’t, this method throws an IOError.
378
- # After the exception was thrown, the StreamReader instance
379
- # is in an unusable state. You cannot continue using it
380
- # (don’t call #close on it either); close the wrapped IO
381
- # stream and create another instance of this class.
382
- #
383
- # === Raises
267
+ # Abort the current decompression process and reset everything
268
+ # to the start so that reading from this reader will start over
269
+ # from the beginning of the compressed data.
384
270
  #
385
- # [IOError]
386
- # The wrapped IO doesn’t support rewinding.
387
- # Do not use the StreamReader instance anymore
388
- # after receiving this exception.
389
- #
390
- # ==Remarks
391
- #
392
- # I don’t really like this method, it uses several dirty
393
- # tricks to circumvent both io-like’s and liblzma’s control
394
- # mechanisms. I only implemented this because the
395
- # <tt>archive-tar-minitar</tt> gem calls this method when
396
- # unpacking a TAR archive from a stream.
271
+ # The delegate IO has to support the #rewind method. Otherwise
272
+ # like IO#rewind.
397
273
  def rewind
398
- # HACK: Wipe all data from io-like’s internal read buffer.
399
- # This heavily relies on io-like’s internal structure.
400
- # Be always sure to test this when a new version of
401
- # io-like is released!
402
- __io_like__internal_read_buffer.clear
274
+ super
403
275
 
404
- # Forcibly close the XZ stream (internally frees it!)
405
- res = XZ::LibLZMA.lzma_end(@lzma_stream.pointer)
276
+ @readbuf.clear
277
+ res = XZ::LibLZMA.lzma_stream_decoder(@lzma_stream.to_ptr,
278
+ @memory_limit,
279
+ @allflags)
406
280
  XZ::LZMAError.raise_if_necessary(res)
407
281
 
408
- # Rewind the wrapped IO
409
- begin
410
- @delegate_io.rewind
411
- rescue => e
412
- raise(IOError, "Delegate IO failed to rewind! Original message: #{e.message}")
413
- end
414
-
415
- # Reinitialize everything. Note this doesn’t affect @autofile as it
416
- # is already set and stays so (we don’t pass a filename here,
417
- # but rather an IO)
418
- initialize(@delegate_io, :memory_limit => @memory_limit, :flags => @flags)
282
+ 0 # Mimic IO#rewind's return value
419
283
  end
420
284
 
421
- # NO, you CANNOT seek in this object!!
422
- # io-like’s default behaviour is to raise Errno::ESPIPE
423
- # when calling a non-defined seek, which is not what some
424
- # libraries such as RubyGem’s TarReader expect (they expect
425
- # a NoMethodError/NameError instead).
426
- undef seek
427
-
428
- private
429
-
430
- # Called by io-like’s read methods such as #read. Does the heavy work
431
- # of feeding liblzma the compressed data and reading the returned
432
- # uncompressed data.
433
- def unbuffered_read(length)
434
- raise(EOFError, "Input data completely processed!") if @__lzma_finished
435
-
436
- output_buffer_p = FFI::MemoryPointer.new(length) # User guarantees that this fits into RAM
437
-
438
- @lzma_stream[:next_out] = output_buffer_p
439
- @lzma_stream[:avail_out] = output_buffer_p.size
440
-
441
- loop do
442
- # DON’T overwrite any not yet consumed input from any previous
443
- # run! Instead, wait until the last input data is entirely
444
- # consumed, then provide new data.
445
- # TODO: Theoretically, one could move the remaining data to the
446
- # beginning of the pointer and fill the rest with new data,
447
- # being a tiny bit more performant.
448
- if @lzma_stream[:avail_in].zero?
449
- compressed_data = @delegate_io.read(@input_buffer_p.size) || "" # nil at EOS → ""
450
- @input_buffer_p.write_string(compressed_data)
451
- @lzma_stream[:next_in] = @input_buffer_p
452
- @lzma_stream[:avail_in] = binary_size(compressed_data)
453
-
454
- # Now check if we’re at the last bytes of data and set accordingly the
455
- # LZMA-action to carry out (for any subsequent runs until
456
- # all input data has been consumed and the above condition
457
- # is triggered again).
458
- #
459
- # The @__lzma_action variable is only used in this method
460
- # and is _not_ supposed to be accessed from any other method.
461
- if compressed_data.empty?
462
- @__lzma_action = XZ::LibLZMA::LZMA_ACTION[:lzma_finish]
463
- else
464
- @__lzma_action = XZ::LibLZMA::LZMA_ACTION[:lzma_run]
465
- end
466
- end
285
+ # Like IO#ungetbyte.
286
+ def ungetbyte(obj)
287
+ if obj.respond_to? :chr
288
+ @readbuf.prepend(obj.chr)
289
+ else
290
+ @readbuf.prepend(obj.to_s)
291
+ end
292
+ end
467
293
 
468
- res = XZ::LibLZMA.lzma_code(@lzma_stream.pointer, @__lzma_action)
294
+ # Like IO#ungetc.
295
+ def ungetc(str)
296
+ @readbuf.prepend(str)
297
+ end
469
298
 
470
- # liblzma signals LZMA_BUF_ERROR when the output buffer is
471
- # completely filled, which means we can return now.
472
- # When it signals LZMA_STREAM_END, the buffer won’t be filled
473
- # completely anymore as the whole input data has been consumed.
474
- if res == XZ::LibLZMA::LZMA_RET[:lzma_buf_error]
475
- # @lzma_stream[:avail_out] holds the number of free bytes _behind_
476
- # the produced output!
477
- return output_buffer_p.read_string(output_buffer_p.size - @lzma_stream[:avail_out])
478
- elsif res == XZ::LibLZMA::LZMA_RET[:lzma_stream_end]
479
- # @__lzma_finished is not supposed to be used outside this method!
480
- @__lzma_finished = true
481
- return output_buffer_p.read_string(output_buffer_p.size - @lzma_stream[:avail_out])
482
- else
483
- XZ::LZMAError.raise_if_necessary(res)
484
- end
485
- end #loop
299
+ # Returns true if:
300
+ #
301
+ # 1. The underlying IO has reached EOF, and
302
+ # 2. liblzma has returned everything it could make out of that.
303
+ def eof?
304
+ @delegate_io.eof? && @readbuf.empty?
305
+ end
486
306
 
487
- rescue XZ::LZMAError => e
488
- raise(SystemCallError, e.message)
307
+ # Human-readable description
308
+ def inspect
309
+ "<#{self.class} pos=#{@pos} bufsize=#{@readbuf.bytesize} finished=#{@finished} closed=#{closed?} io=#{@delegate_io.inspect}>"
489
310
  end
490
311
 
491
312
  end