ruby-xz 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +75 -0
- data/lib/xz.rb +457 -0
- metadata +83 -0
data/README.rdoc
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
= ruby-xz
|
2
|
+
|
3
|
+
<b>ruby-xz</b> is a basic binding to the famous
|
4
|
+
{liblzma library}[http://tukaani.org/xz/], best known for the
|
5
|
+
extreme compression-ratio it's native +XZ+ format achieves. ruby-xz gives
|
6
|
+
you the possibility of creating and extracting XZ archives on any platform
|
7
|
+
where liblzma is installed. No compilation is needed, because ruby-xz is
|
8
|
+
written ontop of ffi[https://github.com/ffi/ffi].
|
9
|
+
|
10
|
+
== Installation
|
11
|
+
|
12
|
+
Install it the way you install all your gems.
|
13
|
+
|
14
|
+
# gem install ruby-xz
|
15
|
+
|
16
|
+
== Usage
|
17
|
+
|
18
|
+
The documentation of the XZ module is well and you should be able to find
|
19
|
+
everything you need to use ruby-xz. As said, it's not big, but powerful:
|
20
|
+
You can create and extract whole archive files, compress or decompress
|
21
|
+
streams of data or just plain strings.
|
22
|
+
|
23
|
+
=== First step
|
24
|
+
|
25
|
+
You have to require ruby-xz. Note the file you have to require is named
|
26
|
+
"xz.rb", so do
|
27
|
+
|
28
|
+
require "xz"
|
29
|
+
|
30
|
+
to get it.
|
31
|
+
|
32
|
+
=== Examples
|
33
|
+
|
34
|
+
#Compress a TAR archive
|
35
|
+
XZ.compress_file("myfile.tar", "myfile.tar.xz")
|
36
|
+
#Decompress it
|
37
|
+
XZ.decompress_file("myfile.tar.xz", "myfile.tar")
|
38
|
+
|
39
|
+
#Compress everything you get from a socket (note that there HAS to be a EOF
|
40
|
+
#sometime, otherwise this will run infinitely)
|
41
|
+
XZ.compress_stream(socket){|chunk| opened_file.write(chunk)}
|
42
|
+
|
43
|
+
#Compress a string
|
44
|
+
comp = XZ.compress("Mydata")
|
45
|
+
#Decompress it
|
46
|
+
data = XZ.decompress(comp)
|
47
|
+
|
48
|
+
Have a look at the XZ module's documentation for an in-depth description of
|
49
|
+
what is possible.
|
50
|
+
|
51
|
+
=== License
|
52
|
+
|
53
|
+
(The MIT License)
|
54
|
+
|
55
|
+
Basic liblzma-bindings for Ruby.
|
56
|
+
|
57
|
+
Copyright © 2011 Marvin Gülker
|
58
|
+
|
59
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
60
|
+
copy of this software and associated documentation files (the ‘Software’),
|
61
|
+
to deal in the Software without restriction, including without limitation
|
62
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
63
|
+
and/or sell copies of the Software, and to permit persons to whom the Software
|
64
|
+
is furnished to do so, subject to the following conditions:
|
65
|
+
|
66
|
+
The above copyright notice and this permission notice shall be included in all
|
67
|
+
copies or substantial portions of the Software.
|
68
|
+
|
69
|
+
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
70
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
71
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
72
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
73
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
74
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
75
|
+
THE SOFTWARE.
|
data/lib/xz.rb
ADDED
@@ -0,0 +1,457 @@
|
|
1
|
+
#Encoding: UTF-8
|
2
|
+
=begin (The MIT License)
|
3
|
+
|
4
|
+
Basic liblzma-bindings for Ruby.
|
5
|
+
|
6
|
+
Copyright © 2011 Marvin Gülker
|
7
|
+
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
9
|
+
copy of this software and associated documentation files (the ‘Software’),
|
10
|
+
to deal in the Software without restriction, including without limitation
|
11
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
12
|
+
and/or sell copies of the Software, and to permit persons to whom the Software
|
13
|
+
is furnished to do so, subject to the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
16
|
+
copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
24
|
+
THE SOFTWARE.
|
25
|
+
=end
|
26
|
+
|
27
|
+
require "ffi"
|
28
|
+
|
29
|
+
#The namespace and main module of this library. Each method of this module
|
30
|
+
#may raise exceptions of class XZ::LZMAError, which is not named in the
|
31
|
+
#methods' documentations anymore.
|
32
|
+
module XZ
|
33
|
+
|
34
|
+
#This module wraps functions and enums used by liblzma.
|
35
|
+
module LibLZMA
|
36
|
+
extend FFI::Library
|
37
|
+
|
38
|
+
#The maximum value of an uint64_t, as defined by liblzma.
|
39
|
+
#Should be the same as
|
40
|
+
# (2 ** 64) - 1
|
41
|
+
UINT64_MAX = 18446744073709551615
|
42
|
+
|
43
|
+
#Activates extreme compression. Same as xz's "-e" commandline switch.
|
44
|
+
LZMA_PRESET_EXTREME = 1 << 31
|
45
|
+
|
46
|
+
LZMA_TELL_NO_CHECK = 0x02
|
47
|
+
LZMA_TELL_UNSUPPORTED_CHECK = 0x02
|
48
|
+
LZMA_TELL_ANY_CHECK = 0x04
|
49
|
+
LZMA_CONCATENATED = 0x08
|
50
|
+
|
51
|
+
#Placeholder enum used by liblzma for later additions.
|
52
|
+
LZMA_RESERVED_ENUM = enum :lzma_reserved_enum, 0
|
53
|
+
|
54
|
+
#Actions that can be passed to the lzma_code() function.
|
55
|
+
LZMA_ACTION = enum :lzma_run, 0,
|
56
|
+
:lzma_sync_flush,
|
57
|
+
:lzma_full_flush,
|
58
|
+
:lzma_finish
|
59
|
+
|
60
|
+
#Integrity check algorithms supported by liblzma.
|
61
|
+
LZMA_CHECK = enum :lzma_check_none, 0,
|
62
|
+
:lzma_check_crc32, 1,
|
63
|
+
:lzma_check_crc64, 4,
|
64
|
+
:lzma_check_sha256, 10
|
65
|
+
|
66
|
+
#Possible return values of liblzma functions.
|
67
|
+
LZMA_RET = enum :lzma_ok, 0,
|
68
|
+
:lzma_stream_end,
|
69
|
+
:lzma_no_check,
|
70
|
+
:lzma_unsupported_check,
|
71
|
+
:lzma_get_check,
|
72
|
+
:lzma_mem_error,
|
73
|
+
:lzma_memlimit_error,
|
74
|
+
:lzma_format_error,
|
75
|
+
:lzma_options_error,
|
76
|
+
:lzma_data_error,
|
77
|
+
:lzma_buf_error,
|
78
|
+
:lzma_prog_error
|
79
|
+
|
80
|
+
ffi_lib "liblzma"
|
81
|
+
|
82
|
+
attach_function :lzma_easy_encoder, [:pointer, :uint32, :int], :int
|
83
|
+
attach_function :lzma_code, [:pointer, :int], :int
|
84
|
+
attach_function :lzma_stream_decoder, [:pointer, :uint64, :uint32], :int
|
85
|
+
attach_function :lzma_end, [:pointer], :void
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
#The class of the error that this library raises.
|
90
|
+
class LZMAError < StandardError
|
91
|
+
|
92
|
+
#Raises an appropriate exception if +val+ isn't a liblzma success code.
|
93
|
+
def self.raise_if_necessary(val)
|
94
|
+
case val
|
95
|
+
when :lzma_mem_error then raise(self, "Couldn't allocate memory!")
|
96
|
+
when :lzma_memlimit_error then raise(self, "Decoder ran out of (allowed) memory!")
|
97
|
+
when :lzma_format_error then raise(self, "Unrecognized file format!")
|
98
|
+
when :lzma_options_error then raise(self, "Invalid options passed!")
|
99
|
+
when :lzma_data_error then raise raise(self, "Archive is currupt.")
|
100
|
+
when :lzma_buf_error then raise(self, "Buffer unusable!")
|
101
|
+
when :lzma_prog_error then raise(self, "Program error--if you're sure your code is correct, you may have found a bug in liblzma.")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
#The main struct of the liblzma library.
|
108
|
+
class LZMAStream < FFI::Struct
|
109
|
+
layout :next_in, :pointer, #uint8
|
110
|
+
:avail_in, :size_t,
|
111
|
+
:total_in, :uint64,
|
112
|
+
:next_out, :pointer, #uint8
|
113
|
+
:avail_out, :size_t,
|
114
|
+
:total_out, :uint64,
|
115
|
+
:lzma_allocator, :pointer,
|
116
|
+
:lzma_internal, :pointer,
|
117
|
+
:reserved_ptr1, :pointer,
|
118
|
+
:reserved_ptr2, :pointer,
|
119
|
+
:reserved_ptr3, :pointer,
|
120
|
+
:reserved_ptr4, :pointer,
|
121
|
+
:reserved_int1, :uint64,
|
122
|
+
:reserved_int2, :uint64,
|
123
|
+
:reserved_int3, :size_t,
|
124
|
+
:reserved_int4, :size_t,
|
125
|
+
:reserved_enum1, :int,
|
126
|
+
:reserved_enum2, :int
|
127
|
+
|
128
|
+
#This method does basicly the same thing as the
|
129
|
+
#LZMA_STREAM_INIT macro of liblzma. Creates a new LZMAStream
|
130
|
+
#that has been initialized for usage. If any argument is passed,
|
131
|
+
#it is assumed to be a FFI::Pointer to a lzma_stream structure
|
132
|
+
#and that structure is wrapped.
|
133
|
+
def initialize(*args)
|
134
|
+
if args.empty? #Got a pointer, want to wrap it
|
135
|
+
super
|
136
|
+
else
|
137
|
+
s = super()
|
138
|
+
s[:next] = nil
|
139
|
+
s[:avail_in] = 0
|
140
|
+
s[:total_in] = 0
|
141
|
+
s[:next_out] = nil
|
142
|
+
s[:avail_out] = 0
|
143
|
+
s[:total_out] = 0
|
144
|
+
s[:lzma_allocator] = nil
|
145
|
+
s[:lzma_internal] = nil
|
146
|
+
s[:reserved_ptr1] = nil
|
147
|
+
s[:reserved_ptr2] = nil
|
148
|
+
s[:reserved_ptr3] = nil
|
149
|
+
s[:reserved_ptr4] = nil
|
150
|
+
s[:reserved_int1] = 0
|
151
|
+
s[:reserved_int2] = 0
|
152
|
+
s[:reserved_int3] = 0
|
153
|
+
s[:reserved_int4] = 0
|
154
|
+
s[:reserved_enum1] = LibLZMA::LZMA_RESERVED_ENUM[:lzma_reserved_enum]
|
155
|
+
s[:reserved_enum2] = LibLZMA::LZMA_RESERVED_ENUM[:lzma_reserved_enum]
|
156
|
+
s
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
#Number of bytes read in one chunk.
|
162
|
+
CHUNK_SIZE = 4096
|
163
|
+
#The version of this library.
|
164
|
+
VERSION = "0.0.1".freeze
|
165
|
+
|
166
|
+
class << self
|
167
|
+
|
168
|
+
#call-seq:
|
169
|
+
# decompress_stream(io [, memory_limit [, flags ] ] ) → a_string
|
170
|
+
# decompress_stream(io [, memory_limit [, flags ] ] ){|chunk| ... } → an_integer
|
171
|
+
# decode_stream(io [, memory_limit [, flags ] ] ) → a_string
|
172
|
+
# decode_stream(io [, memory_limit [, flags ] ] ){|chunk| ... } → an_integer
|
173
|
+
#
|
174
|
+
#Decompresses a stream containing XZ-compressed data.
|
175
|
+
#===Parameters
|
176
|
+
#[io] The IO to read from. It must be opened for reading.
|
177
|
+
#[memory_limit] (+UINT64_MAX+) If not XZ::LibLZMA::UINT64_MAX, makes liblzma
|
178
|
+
# use no more memory than +memory_limit+ bytes.
|
179
|
+
#[flags] (<tt>[:tell_unsupported_check]</tt>) Additional flags
|
180
|
+
# passed to liblzma (an array). Possible flags are:
|
181
|
+
# [:tell_no_check] Spit out a warning if the archive hasn't an
|
182
|
+
# itnegrity checksum.
|
183
|
+
# [:tell_unsupported_check] Spit out a warning if the archive
|
184
|
+
# has an unsupported checksum type.
|
185
|
+
# [:concatenated] Decompress concatenated archives.
|
186
|
+
#[chunk] (Block argument) One piece of decompressed data.
|
187
|
+
#===Return value
|
188
|
+
#If a block was given, returns the number of bytes written. Otherwise,
|
189
|
+
#returns the decompressed data as a BINARY-encoded string.
|
190
|
+
#===Example
|
191
|
+
# data = File.open("archive.xz", "rb"){|f| f.read}
|
192
|
+
# io = StringIO.new(data)
|
193
|
+
# XZ.decompress_stream(io) #=> "I AM THE DATA"
|
194
|
+
# io.rewind
|
195
|
+
# str = ""
|
196
|
+
# XZ.decompress_stream(io, XZ::LibLZMA::UINT64_MAX, [:tell_no_check]){|c| str << c} #=> 13
|
197
|
+
# str #=> "I AM THE DATA"
|
198
|
+
#===Remarks
|
199
|
+
#The block form is *much* better on memory usage, because it doesn't have
|
200
|
+
#to load everything into RAM at once. If you don't know how big your
|
201
|
+
#data gets or if you want to decompress much data, use the block form. Of
|
202
|
+
#course you shouldn't store the data your read in RAM then as in the
|
203
|
+
#example above.
|
204
|
+
def decompress_stream(io, memory_limit = LibLZMA::UINT64_MAX, flags = [:tell_unsupported_check], &block)
|
205
|
+
raise(ArgumentError, "Invalid memory limit set!") unless (0..LibLZMA::UINT64_MAX).include?(memory_limit)
|
206
|
+
flags.each do |flag|
|
207
|
+
raise(ArgumentError, "Unknown flag #{flag}!") unless [:tell_no_check, :tell_unsupported_check, :tell_any_check, :concatenated].include?(flag)
|
208
|
+
end
|
209
|
+
|
210
|
+
stream = LZMAStream.new
|
211
|
+
res = LibLZMA.lzma_stream_decoder(
|
212
|
+
stream.pointer,
|
213
|
+
memory_limit,
|
214
|
+
flags.inject(0){|val, flag| val | LibLZMA.const_get(:"LZMA_#{flag.upcase}")}
|
215
|
+
)
|
216
|
+
|
217
|
+
LZMAError.raise_if_necessary(res)
|
218
|
+
|
219
|
+
res = ""
|
220
|
+
if block_given?
|
221
|
+
res = lzma_code(io, stream, &block)
|
222
|
+
else
|
223
|
+
lzma_code(io, stream){|chunk| res << chunk}
|
224
|
+
end
|
225
|
+
|
226
|
+
LibLZMA.lzma_end(stream.pointer)
|
227
|
+
|
228
|
+
block_given? ? stream[:total_out] : res
|
229
|
+
end
|
230
|
+
alias decode_stream decompress_stream
|
231
|
+
|
232
|
+
#call-seq:
|
233
|
+
# compress_stream(io [, compression_level [, check [, extreme ] ] ] ) → a_string
|
234
|
+
# compress_stream(io [, compression_level [, check [, extreme ] ] ] ){|chunk| ... } → an_integer
|
235
|
+
# encode_stream(io [, compression_level [, check [, extreme ] ] ] ) → a_string
|
236
|
+
# encode_stream(io [, compression_level [, check [, extreme ] ] ] ){|chunk| ... } → an_integer
|
237
|
+
#
|
238
|
+
#Compresses a stream of data into XZ-compressed data.
|
239
|
+
#===Parameters
|
240
|
+
#[io] The IO to read the data from. Must be opened for
|
241
|
+
# reading.
|
242
|
+
#[compression_level] (6) Compression strength. Higher values indicate a
|
243
|
+
# smaller result, but longer compression time. Maximum
|
244
|
+
# is 9.
|
245
|
+
#[check] (:crc64) The checksum algorithm to use for verifying
|
246
|
+
# the data inside the archive. Possible values are:
|
247
|
+
# * :none
|
248
|
+
# * :crc32
|
249
|
+
# * :crc64
|
250
|
+
# * :sha256
|
251
|
+
#[extreme] (false) Tries to get the last bit out of the
|
252
|
+
# compression. This may succeed, but you can end
|
253
|
+
# up with *very* long computation times.
|
254
|
+
#[chunk] (Block argument) One piece of compressed data.
|
255
|
+
#===Return value
|
256
|
+
#If a block was given, returns the number of bytes written. Otherwise,
|
257
|
+
#returns the compressed data as a BINARY-encoded string.
|
258
|
+
#===Example
|
259
|
+
# data = File.read("file.txt")
|
260
|
+
# i = StringIO.new(data)
|
261
|
+
# XZ.compress_stream(i) #=> Some binary blob
|
262
|
+
# i.rewind
|
263
|
+
# str = ""
|
264
|
+
# XZ.compress_stream(i, 4, :sha256){|c| str << c} #=> 123
|
265
|
+
# str #=> Some binary blob
|
266
|
+
#===Remarks
|
267
|
+
#The block form is *much* better on memory usage, because it doesn't have
|
268
|
+
#to load everything into RAM at once. If you don't know how big your
|
269
|
+
#data gets or if you want to compress much data, use the block form. Of
|
270
|
+
#course you shouldn't store the data your read in RAM then as in the
|
271
|
+
#example above.
|
272
|
+
def compress_stream(io, compression_level = 6, check = :crc64, extreme = false, &block)
|
273
|
+
raise(ArgumentError, "Invalid compression level!") unless (0..9).include?(compression_level)
|
274
|
+
raise(ArgumentError, "Invalid checksum specified!") unless [:none, :crc32, :crc64, :sha256].include?(check)
|
275
|
+
|
276
|
+
stream = LZMAStream.new
|
277
|
+
res = LibLZMA.lzma_easy_encoder(
|
278
|
+
stream.pointer,
|
279
|
+
compression_level | (extreme ? LibLZMA::LZMA_PRESET_EXTREME : 0),
|
280
|
+
LibLZMA::LZMA_CHECK[:"lzma_check_#{check}"]
|
281
|
+
)
|
282
|
+
|
283
|
+
LZMAError.raise_if_necessary(res)
|
284
|
+
|
285
|
+
res = ""
|
286
|
+
if block_given?
|
287
|
+
res = lzma_code(io, stream, &block)
|
288
|
+
else
|
289
|
+
lzma_code(io, stream){|chunk| res << chunk}
|
290
|
+
end
|
291
|
+
|
292
|
+
LibLZMA.lzma_end(stream.pointer)
|
293
|
+
|
294
|
+
block_given? ? stream[:total_out] : res
|
295
|
+
end
|
296
|
+
alias encode_stream compress_stream
|
297
|
+
|
298
|
+
#Compresses +in_file+ and writes the result to +out_file+.
|
299
|
+
#===Parameters
|
300
|
+
#[in_file] The path to the file to read from.
|
301
|
+
#[out_file] The path of the file to write to. If it exists, it will be
|
302
|
+
# overwritten.
|
303
|
+
#For the other parameters, see the compress_stream method.
|
304
|
+
#===Return value
|
305
|
+
#The number of bytes written, i.e. the size of the archive.
|
306
|
+
#===Example
|
307
|
+
# XZ.compress("myfile.txt", "myfile.txt.xz")
|
308
|
+
# XZ.compress("myarchive.tar", "myarchive.tar.xz")
|
309
|
+
#===Remarks
|
310
|
+
#This method is safe to use with big files, because files are not loaded
|
311
|
+
#into memory completely at once.
|
312
|
+
def compress_file(in_file, out_file, compression_level = 6, check = :crc64, extreme = false)
|
313
|
+
File.open(in_file, "rb") do |i_file|
|
314
|
+
File.open(out_file, "wb") do |o_file|
|
315
|
+
compress_stream(i_file, compression_level, check, extreme) do |chunk|
|
316
|
+
o_file.write(chunk)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
#Compresses arbitrary data using the XZ algorithm.
|
323
|
+
#===Parameters
|
324
|
+
#[str] The data to compress.
|
325
|
+
#For the other parameters, see the compress_stream method.
|
326
|
+
#===Return value
|
327
|
+
#The compressed data as a BINARY-encoded string.
|
328
|
+
#===Example
|
329
|
+
# data = "I love Ruby"
|
330
|
+
# comp = XZ.compress(data) #=> binary blob
|
331
|
+
#===Remarks
|
332
|
+
#Don't use this method for big amounts of data--you may run out of
|
333
|
+
#memory. Use compress_file or compress_stream instead.
|
334
|
+
def compress(str, compression_level = 6, check = :crc64, extreme = false)
|
335
|
+
raise(NotImplementedError, "StringIO isn't available!") unless defined? StringIO
|
336
|
+
s = StringIO.new(str)
|
337
|
+
compress_stream(s, compression_level, check, extreme)
|
338
|
+
end
|
339
|
+
|
340
|
+
#Decompresses data in XZ format.
|
341
|
+
#===Parameters
|
342
|
+
#[str] The data to decompress.
|
343
|
+
#For the other parameters, see the decompress_stream method.
|
344
|
+
#===Return value
|
345
|
+
#The decompressed data as a BINARY-encoded string.
|
346
|
+
#===Example
|
347
|
+
# comp = File.open("data.xz", "rb"){|f| f.read}
|
348
|
+
# data = XZ.decompress(comp) #=> "I love Ruby"
|
349
|
+
#===Remarks
|
350
|
+
#Don't use this method for big amounts of data--you may run out of
|
351
|
+
#memory. Use decompress_file or decompress_stream instead.
|
352
|
+
def decompress(str, memory_limit = LibLZMA::UINT64_MAX, flags = [:tell_unsupported_check])
|
353
|
+
raise(NotImplementedError, "StringIO isn't available!") unless defined? StringIO
|
354
|
+
s = StringIO.new(str)
|
355
|
+
decompress_stream(s, memory_limit, flags)
|
356
|
+
end
|
357
|
+
|
358
|
+
#Decompresses +in_file+ and writes the result to +out_file+.
|
359
|
+
#===Parameters
|
360
|
+
#[in_file] The path to the file to read from.
|
361
|
+
#[out_file] The path of the file to write to. If it exists, it will
|
362
|
+
# be overwritten.
|
363
|
+
#For the other parameters, see the decompress_stream method.
|
364
|
+
#===Return value
|
365
|
+
#The number of bytes written, i.e. the size of the uncompressed data.
|
366
|
+
#===Example
|
367
|
+
# XZ.decompres("myfile.txt.xz", "myfile.txt")
|
368
|
+
# XZ.decompress("myarchive.tar.xz", "myarchive.tar")
|
369
|
+
#===Remarks
|
370
|
+
#This method is safe to use with big files, because files are not loaded
|
371
|
+
#into memory completely at once.
|
372
|
+
def decompress_file(in_file, out_file, memory_limit = LibLZMA::UINT64_MAX, flags = [:tell_unsupported_check])
|
373
|
+
File.open(in_file, "rb") do |i_file|
|
374
|
+
File.open(out_file, "wb") do |o_file|
|
375
|
+
decompress_stream(i_file, memory_limit, flags) do |chunk|
|
376
|
+
o_file.write(chunk)
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
private
|
383
|
+
|
384
|
+
#This method returns the size of +str+ in bytes.
|
385
|
+
def binary_size(str)
|
386
|
+
#Believe it or not, but this is faster than str.bytes.to_a.size.
|
387
|
+
#I benchmarked it, and it is as twice as fast.
|
388
|
+
str.dup.force_encoding("BINARY").size
|
389
|
+
end
|
390
|
+
|
391
|
+
#This method does the heavy work of (de-)compressing a stream. It takes
|
392
|
+
#an IO object to read data from (that means the IO must be opened
|
393
|
+
#for reading) and a XZ::LZMAStream object that is used to (de-)compress
|
394
|
+
#the data. Furthermore this method takes a block which gets passed
|
395
|
+
#the (de-)compressed data in chunks one at a time--this is needed to allow
|
396
|
+
#(de-)compressing of very large files that can't be loaded fully into
|
397
|
+
#memory.
|
398
|
+
def lzma_code(io, stream)
|
399
|
+
input_buffer_p = FFI::MemoryPointer.new(CHUNK_SIZE)
|
400
|
+
output_buffer_p = FFI::MemoryPointer.new(CHUNK_SIZE)
|
401
|
+
|
402
|
+
while str = io.read(CHUNK_SIZE)
|
403
|
+
input_buffer_p.write_string(str)
|
404
|
+
|
405
|
+
#Set the data for compressing
|
406
|
+
stream[:next_in] = input_buffer_p
|
407
|
+
stream[:avail_in] = binary_size(str)
|
408
|
+
|
409
|
+
#Now loop until we gathered all the data in stream[:next_out]. Depending on the
|
410
|
+
#amount of data, this may not fit into the buffer, meaning that we have to
|
411
|
+
#provide a pointer to a "new" buffer that liblzma can write into. Since
|
412
|
+
#liblzma already set stream[:avail_in] to 0 in the first iteration, the extra call to the
|
413
|
+
#lzma_code() function doesn't hurt (indeed the pipe_comp example from
|
414
|
+
#liblzma handles it this way too). Sometimes it happens that the compressed data
|
415
|
+
#is bigger than the original (notably when the amount of data to compress
|
416
|
+
#is small)
|
417
|
+
loop do
|
418
|
+
#Prepare for getting the compressed_data
|
419
|
+
stream[:next_out] = output_buffer_p
|
420
|
+
stream[:avail_out] = CHUNK_SIZE
|
421
|
+
|
422
|
+
#Compress the data
|
423
|
+
res = if io.eof?
|
424
|
+
LibLZMA.lzma_code(stream.pointer, LibLZMA::LZMA_ACTION[:lzma_finish])
|
425
|
+
else
|
426
|
+
LibLZMA.lzma_code(stream.pointer, LibLZMA::LZMA_ACTION[:lzma_run])
|
427
|
+
end
|
428
|
+
check_lzma_code_retval(res)
|
429
|
+
|
430
|
+
#Write the compressed data
|
431
|
+
data = output_buffer_p.read_string(CHUNK_SIZE - stream[:avail_out])
|
432
|
+
yield(data)
|
433
|
+
|
434
|
+
#If the buffer is completely filled, it's likely that there is
|
435
|
+
#more data liblzma wants to hand to us. Start a new iteration,
|
436
|
+
#but don't provide new input data.
|
437
|
+
break unless stream[:avail_out] == 0
|
438
|
+
end #loop
|
439
|
+
end #while
|
440
|
+
end #lzma_code
|
441
|
+
|
442
|
+
#Checks for errors and warnings that can be derived from the return
|
443
|
+
#value of the lzma_code() function and shows them if necessary.
|
444
|
+
def check_lzma_code_retval(code)
|
445
|
+
e = LibLZMA::LZMA_RET
|
446
|
+
case code
|
447
|
+
when e[:lzma_no_check] then warn("Couldn't verify archive integrity--archive has not integrity checksum.")
|
448
|
+
when e[:lzma_unsupported_check] then warn("Couldn't verify archive integrity--archive has an unsupported integrity checksum.")
|
449
|
+
when e[:lzma_get_check] then nil #This isn't useful for us. It indicates that the checksum type is now known.
|
450
|
+
else
|
451
|
+
LZMAError.raise_if_necessary(code)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
end #class << self
|
456
|
+
|
457
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-xz
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Marvin Gülker
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-03-16 00:00:00.000000000 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ffi
|
17
|
+
requirement: &15267800 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *15267800
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: hanna-nouveau
|
28
|
+
requirement: &15267300 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *15267300
|
37
|
+
description: ! 'This is a basic binding for liblzma that allows you to
|
38
|
+
|
39
|
+
create and extract XZ-compressed archives. It can cope with big
|
40
|
+
|
41
|
+
files as well as small ones, but doesn''t offer much
|
42
|
+
|
43
|
+
of the possibilities liblzma itself has.
|
44
|
+
|
45
|
+
'
|
46
|
+
email: sutniuq@gmx.net
|
47
|
+
executables: []
|
48
|
+
extensions: []
|
49
|
+
extra_rdoc_files:
|
50
|
+
- README.rdoc
|
51
|
+
files:
|
52
|
+
- lib/xz.rb
|
53
|
+
- README.rdoc
|
54
|
+
has_rdoc: true
|
55
|
+
homepage:
|
56
|
+
licenses: []
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options:
|
59
|
+
- -t
|
60
|
+
- ruby-xz RDocs
|
61
|
+
- -m
|
62
|
+
- README.rdoc
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '1.9'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.6.2
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: XZ compression via liblzma for Ruby.
|
83
|
+
test_files: []
|