ruby-xz 0.2.3 → 1.0.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,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