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.
- checksums.yaml +4 -4
- data/AUTHORS +1 -3
- data/HISTORY.rdoc +45 -0
- data/LICENSE +21 -0
- data/README.md +44 -43
- data/lib/xz.rb +163 -87
- data/lib/xz/fiddle_helper.rb +91 -0
- data/lib/xz/lib_lzma.rb +117 -103
- data/lib/xz/stream.rb +429 -32
- data/lib/xz/stream_reader.rb +221 -400
- data/lib/xz/stream_writer.rb +173 -314
- data/lib/xz/version.rb +4 -4
- metadata +17 -43
- data/COPYING +0 -26
data/lib/xz/stream_reader.rb
CHANGED
@@ -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 ©
|
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
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
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
|
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
|
-
#
|
82
|
-
#
|
48
|
+
# open(filename [, kw]) → stream_reader
|
49
|
+
# open(filename [, kw]){|sr| ...} → stream_reader
|
83
50
|
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
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
|
-
#
|
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
|
-
#
|
125
|
-
# form returns the newly
|
126
|
-
#
|
127
|
-
#
|
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
|
-
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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(
|
96
|
+
yield(reader)
|
201
97
|
ensure
|
202
|
-
|
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
|
-
#
|
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
|
-
# [
|
225
|
-
#
|
226
|
-
#
|
227
|
-
# exceptions
|
228
|
-
#
|
229
|
-
#
|
230
|
-
#
|
231
|
-
#
|
232
|
-
#
|
233
|
-
#
|
234
|
-
#
|
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
|
-
#
|
239
|
-
#
|
240
|
-
#
|
241
|
-
#
|
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
|
-
#
|
249
|
-
#
|
250
|
-
#
|
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
|
-
#
|
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
|
-
#
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
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
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
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
|
-
|
302
|
-
|
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
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
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
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
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
|
-
|
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
|
-
|
324
|
-
@lzma_stream[:total_out]
|
325
|
-
end
|
244
|
+
@pos += @readbuf.bytesize
|
326
245
|
|
327
|
-
|
328
|
-
|
329
|
-
|
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
|
-
|
360
|
-
|
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
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
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
|
-
#
|
373
|
-
#
|
374
|
-
#
|
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
|
-
#
|
386
|
-
#
|
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
|
-
|
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
|
-
|
405
|
-
res = XZ::LibLZMA.
|
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
|
-
#
|
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
|
-
#
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
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
|
-
|
294
|
+
# Like IO#ungetc.
|
295
|
+
def ungetc(str)
|
296
|
+
@readbuf.prepend(str)
|
297
|
+
end
|
469
298
|
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
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
|
-
|
488
|
-
|
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
|