ruby-xz 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.rdoc +6 -0
- data/README.rdoc +48 -2
- data/VERSION +1 -0
- data/lib/xz.rb +119 -164
- data/lib/xz/lib_lzma.rb +155 -0
- data/lib/xz/stream.rb +67 -0
- data/lib/xz/stream_reader.rb +285 -0
- data/lib/xz/stream_writer.rb +215 -0
- metadata +14 -10
data/HISTORY.rdoc
ADDED
data/README.rdoc
CHANGED
@@ -7,12 +7,24 @@ you the possibility of creating and extracting XZ archives on any platform
|
|
7
7
|
where liblzma is installed. No compilation is needed, because ruby-xz is
|
8
8
|
written ontop of ffi[https://github.com/ffi/ffi].
|
9
9
|
|
10
|
+
ruby-xz supports both "intuitive" (de)compression by providing methods to
|
11
|
+
directly operate on strings and files, but also allows you to operate
|
12
|
+
directly on IO streams (see the various methods of the XZ module). On top
|
13
|
+
of that, ruby-xz offers an advanced interface that allows you to treat
|
14
|
+
XZ-compressed data as IO streams, both for reading and for writing. See the
|
15
|
+
XZ::StreamReader and XZ::StreamWriter classes for more information on this.
|
16
|
+
|
10
17
|
== Installation
|
11
18
|
|
12
19
|
Install it the way you install all your gems.
|
13
20
|
|
14
21
|
# gem install ruby-xz
|
15
22
|
|
23
|
+
Although it is designed for Ruby 1.9 (which I highly recommend),
|
24
|
+
ruby-xz should also work with Ruby 1.8.7. However, the Ruby 1.8
|
25
|
+
compatibility may be skipped anytime (causing at least a minor version
|
26
|
+
bump), so you shouldn’t rely on it.
|
27
|
+
|
16
28
|
== Usage
|
17
29
|
|
18
30
|
The documentation of the XZ module is well and you should be able to find
|
@@ -54,7 +66,9 @@ what is possible.
|
|
54
66
|
|
55
67
|
Basic liblzma-bindings for Ruby.
|
56
68
|
|
57
|
-
Copyright © 2011 Marvin Gülker
|
69
|
+
Copyright © 2011,2012 Marvin Gülker
|
70
|
+
|
71
|
+
Copyright © 2011 Christoph Plank
|
58
72
|
|
59
73
|
Permission is hereby granted, free of charge, to any person obtaining a
|
60
74
|
copy of this software and associated documentation files (the ‘Software’),
|
@@ -72,4 +86,36 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
72
86
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
73
87
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
74
88
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
75
|
-
THE SOFTWARE.
|
89
|
+
THE SOFTWARE.
|
90
|
+
|
91
|
+
==== require_relative
|
92
|
+
|
93
|
+
This library includes the sourcecode of Steve Klabnik’s
|
94
|
+
{require_relative gem}[http://steveklabnik.github.com/require_relative/]
|
95
|
+
in order to make it 1.8-compatible. It’s licensed under the BSD license:
|
96
|
+
|
97
|
+
Copyright (c) 2011, Steve Klabnik
|
98
|
+
|
99
|
+
All rights reserved.
|
100
|
+
|
101
|
+
Redistribution and use in source and binary forms, with or without
|
102
|
+
modification, are permitted provided that the following conditions are
|
103
|
+
met:
|
104
|
+
|
105
|
+
* Redistributions of source code must retain the above copyright
|
106
|
+
notice, this list of conditions and the following disclaimer.
|
107
|
+
* Redistributions in binary form must reproduce the above copyright
|
108
|
+
notice, this list of conditions and the following disclaimer in the
|
109
|
+
documentation and/or other materials provided with the distribution.
|
110
|
+
|
111
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
112
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
113
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
114
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
115
|
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
116
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
117
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
118
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
119
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
120
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
121
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/xz.rb
CHANGED
@@ -1,167 +1,113 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# (The MIT License)
|
3
|
+
#
|
4
|
+
# Basic liblzma-bindings for Ruby.
|
5
|
+
#
|
6
|
+
# Copyright © 2011,2012 Marvin Gülker
|
7
|
+
# Copyright © 2011 Christoph Plank
|
8
|
+
#
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
10
|
+
# copy of this software and associated documentation files (the ‘Software’),
|
11
|
+
# to deal in the Software without restriction, including without limitation
|
12
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
13
|
+
# and/or sell copies of the Software, and to permit persons to whom the Software
|
14
|
+
# is furnished to do so, subject to the following conditions:
|
15
|
+
#
|
16
|
+
# The above copyright notice and this permission notice shall be included in all
|
17
|
+
# copies or substantial portions of the Software.
|
18
|
+
#
|
19
|
+
# THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
20
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
21
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
22
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
23
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
24
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
25
|
+
# THE SOFTWARE.
|
3
26
|
|
4
|
-
|
27
|
+
if RUBY_VERSION < "1.9"
|
28
|
+
require "rubygems"
|
29
|
+
|
30
|
+
# The following is the complete sourcode of the require_relative gem
|
31
|
+
# by Steve Klabnik, licensed under the BSD license:
|
32
|
+
#
|
33
|
+
# Copyright (c) 2011, Steve Klabnik
|
34
|
+
# All rights reserved.
|
35
|
+
#
|
36
|
+
# Redistribution and use in source and binary forms, with or without
|
37
|
+
# modification, are permitted provided that the following conditions are
|
38
|
+
# met:
|
39
|
+
#
|
40
|
+
# * Redistributions of source code must retain the above copyright
|
41
|
+
# notice, this list of conditions and the following disclaimer.
|
42
|
+
# * Redistributions in binary form must reproduce the above copyright
|
43
|
+
# notice, this list of conditions and the following disclaimer in the
|
44
|
+
# documentation and/or other materials provided with the distribution.
|
45
|
+
#
|
46
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
47
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
48
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
49
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
50
|
+
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
51
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
52
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
53
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
54
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
55
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
56
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
57
|
+
# require\_relative has no effect on Ruby 1.9 (or other versions that provide Kernel#require_relative
|
58
|
+
# out of the box)
|
59
|
+
unless Object.new.respond_to?(:require_relative, true)
|
60
|
+
# Yep, you're looking at it! This gem is pretty small, and for good reason.
|
61
|
+
# There's not much to do! We use split to find the filename that we're
|
62
|
+
# looking to require, raise a LoadError if it's called in a context (like eval)
|
63
|
+
# that it shouldn't be, and then require it via regular old require.
|
64
|
+
#
|
65
|
+
# Now, in 1.9, "." is totally removed from the $LOAD_PATH. We don't do that
|
66
|
+
# here, because that would break a lot of other code! You're still vulnerable
|
67
|
+
# to the security hole that caused this change to happen in the first place.
|
68
|
+
# You will be able to use this gem to transition the code you write over to
|
69
|
+
# the 1.9 syntax, though.
|
70
|
+
def require_relative(relative_feature) # :nodoc:
|
5
71
|
|
6
|
-
|
72
|
+
file = caller.first.split(/:\d/,2).first
|
7
73
|
|
8
|
-
|
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:
|
74
|
+
raise LoadError, "require_relative is called in #{$1}" if /\A\((.*)\)/ =~ file
|
14
75
|
|
15
|
-
|
16
|
-
|
76
|
+
require File.expand_path(relative_feature, File.dirname(file))
|
77
|
+
end
|
78
|
+
end
|
17
79
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
80
|
+
unless String.instance_methods.include?(:clear)
|
81
|
+
class String # :nodoc:
|
82
|
+
def clear
|
83
|
+
replace("")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
26
88
|
|
89
|
+
require "pathname"
|
27
90
|
require "ffi"
|
91
|
+
require 'stringio'
|
92
|
+
require "io/like"
|
28
93
|
|
29
94
|
#The namespace and main module of this library. Each method of this module
|
30
95
|
#may raise exceptions of class XZ::LZMAError, which is not named in the
|
31
96
|
#methods' documentations anymore.
|
97
|
+
#
|
98
|
+
#All strings you receive from any method defined in this module
|
99
|
+
#and the classes defined in it are encoded in BINARY, so you may
|
100
|
+
#have to call #force_encoding on them to tag them with the correct
|
101
|
+
#encoding (assuming you _know_ what their correct encoding should be).
|
102
|
+
#ruby-xz can’t handle this as compiled strings don’t come with encoding
|
103
|
+
#information.
|
32
104
|
module XZ
|
33
105
|
|
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
106
|
|
161
107
|
#Number of bytes read in one chunk.
|
162
108
|
CHUNK_SIZE = 4096
|
163
109
|
#The version of this library.
|
164
|
-
VERSION =
|
110
|
+
VERSION = Pathname.new(__FILE__).dirname.expand_path.parent.join("VERSION").read.chomp.freeze
|
165
111
|
|
166
112
|
class << self
|
167
113
|
|
@@ -179,7 +125,7 @@ module XZ
|
|
179
125
|
#[flags] (<tt>[:tell_unsupported_check]</tt>) Additional flags
|
180
126
|
# passed to liblzma (an array). Possible flags are:
|
181
127
|
# [:tell_no_check] Spit out a warning if the archive hasn't an
|
182
|
-
#
|
128
|
+
# integrity checksum.
|
183
129
|
# [:tell_unsupported_check] Spit out a warning if the archive
|
184
130
|
# has an unsupported checksum type.
|
185
131
|
# [:concatenated] Decompress concatenated archives.
|
@@ -199,7 +145,7 @@ module XZ
|
|
199
145
|
#The block form is *much* better on memory usage, because it doesn't have
|
200
146
|
#to load everything into RAM at once. If you don't know how big your
|
201
147
|
#data gets or if you want to decompress much data, use the block form. Of
|
202
|
-
#course you shouldn't store the data
|
148
|
+
#course you shouldn't store the data you read in RAM then as in the
|
203
149
|
#example above.
|
204
150
|
def decompress_stream(io, memory_limit = LibLZMA::UINT64_MAX, flags = [:tell_unsupported_check], &block)
|
205
151
|
raise(ArgumentError, "Invalid memory limit set!") unless (0..LibLZMA::UINT64_MAX).include?(memory_limit)
|
@@ -211,12 +157,13 @@ module XZ
|
|
211
157
|
res = LibLZMA.lzma_stream_decoder(
|
212
158
|
stream.pointer,
|
213
159
|
memory_limit,
|
214
|
-
flags.inject(0){|val, flag| val | LibLZMA.const_get(:"LZMA_#{flag.upcase}")}
|
160
|
+
flags.inject(0){|val, flag| val | LibLZMA.const_get(:"LZMA_#{flag.to_s.upcase}")}
|
215
161
|
)
|
216
162
|
|
217
163
|
LZMAError.raise_if_necessary(res)
|
218
164
|
|
219
165
|
res = ""
|
166
|
+
res.encode!("BINARY") if RUBY_VERSION >= "1.9"
|
220
167
|
if block_given?
|
221
168
|
res = lzma_code(io, stream, &block)
|
222
169
|
else
|
@@ -274,15 +221,14 @@ module XZ
|
|
274
221
|
raise(ArgumentError, "Invalid checksum specified!") unless [:none, :crc32, :crc64, :sha256].include?(check)
|
275
222
|
|
276
223
|
stream = LZMAStream.new
|
277
|
-
res = LibLZMA.lzma_easy_encoder(
|
278
|
-
|
279
|
-
|
280
|
-
LibLZMA::LZMA_CHECK[:"lzma_check_#{check}"]
|
281
|
-
)
|
224
|
+
res = LibLZMA.lzma_easy_encoder(stream.pointer,
|
225
|
+
compression_level | (extreme ? LibLZMA::LZMA_PRESET_EXTREME : 0),
|
226
|
+
LibLZMA::LZMA_CHECK[:"lzma_check_#{check}"])
|
282
227
|
|
283
228
|
LZMAError.raise_if_necessary(res)
|
284
229
|
|
285
230
|
res = ""
|
231
|
+
res.encode!("BINARY") if RUBY_VERSION >= "1.9"
|
286
232
|
if block_given?
|
287
233
|
res = lzma_code(io, stream, &block)
|
288
234
|
else
|
@@ -300,7 +246,7 @@ module XZ
|
|
300
246
|
#[in_file] The path to the file to read from.
|
301
247
|
#[out_file] The path of the file to write to. If it exists, it will be
|
302
248
|
# overwritten.
|
303
|
-
#For the other parameters, see the compress_stream method.
|
249
|
+
#For the other parameters, see the ::compress_stream method.
|
304
250
|
#===Return value
|
305
251
|
#The number of bytes written, i.e. the size of the archive.
|
306
252
|
#===Example
|
@@ -385,7 +331,11 @@ module XZ
|
|
385
331
|
def binary_size(str)
|
386
332
|
#Believe it or not, but this is faster than str.bytes.to_a.size.
|
387
333
|
#I benchmarked it, and it is as twice as fast.
|
388
|
-
str.
|
334
|
+
if str.respond_to? :force_encoding
|
335
|
+
str.dup.force_encoding("BINARY").size
|
336
|
+
else
|
337
|
+
str.bytes.to_a.size
|
338
|
+
end
|
389
339
|
end
|
390
340
|
|
391
341
|
#This method does the heavy work of (de-)compressing a stream. It takes
|
@@ -396,16 +346,16 @@ module XZ
|
|
396
346
|
#(de-)compressing of very large files that can't be loaded fully into
|
397
347
|
#memory.
|
398
348
|
def lzma_code(io, stream)
|
399
|
-
input_buffer_p
|
349
|
+
input_buffer_p = FFI::MemoryPointer.new(CHUNK_SIZE)
|
400
350
|
output_buffer_p = FFI::MemoryPointer.new(CHUNK_SIZE)
|
401
351
|
|
402
352
|
while str = io.read(CHUNK_SIZE)
|
403
353
|
input_buffer_p.write_string(str)
|
404
354
|
|
405
355
|
#Set the data for compressing
|
406
|
-
stream[:next_in]
|
356
|
+
stream[:next_in] = input_buffer_p
|
407
357
|
stream[:avail_in] = binary_size(str)
|
408
|
-
|
358
|
+
|
409
359
|
#Now loop until we gathered all the data in stream[:next_out]. Depending on the
|
410
360
|
#amount of data, this may not fit into the buffer, meaning that we have to
|
411
361
|
#provide a pointer to a "new" buffer that liblzma can write into. Since
|
@@ -413,10 +363,10 @@ module XZ
|
|
413
363
|
#lzma_code() function doesn't hurt (indeed the pipe_comp example from
|
414
364
|
#liblzma handles it this way too). Sometimes it happens that the compressed data
|
415
365
|
#is bigger than the original (notably when the amount of data to compress
|
416
|
-
#is small)
|
366
|
+
#is small).
|
417
367
|
loop do
|
418
368
|
#Prepare for getting the compressed_data
|
419
|
-
stream[:next_out]
|
369
|
+
stream[:next_out] = output_buffer_p
|
420
370
|
stream[:avail_out] = CHUNK_SIZE
|
421
371
|
|
422
372
|
#Compress the data
|
@@ -444,9 +394,9 @@ module XZ
|
|
444
394
|
def check_lzma_code_retval(code)
|
445
395
|
e = LibLZMA::LZMA_RET
|
446
396
|
case code
|
447
|
-
when e[:lzma_no_check]
|
397
|
+
when e[:lzma_no_check] then warn("Couldn't verify archive integrity--archive has not integrity checksum.")
|
448
398
|
when e[:lzma_unsupported_check] then warn("Couldn't verify archive integrity--archive has an unsupported integrity checksum.")
|
449
|
-
when e[:lzma_get_check]
|
399
|
+
when e[:lzma_get_check] then nil #This isn't useful for us. It indicates that the checksum type is now known.
|
450
400
|
else
|
451
401
|
LZMAError.raise_if_necessary(code)
|
452
402
|
end
|
@@ -455,3 +405,8 @@ module XZ
|
|
455
405
|
end #class << self
|
456
406
|
|
457
407
|
end
|
408
|
+
|
409
|
+
require_relative "xz/lib_lzma"
|
410
|
+
require_relative "xz/stream"
|
411
|
+
require_relative "xz/stream_writer"
|
412
|
+
require_relative "xz/stream_reader"
|
data/lib/xz/lib_lzma.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# 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
|
+
|
26
|
+
module XZ
|
27
|
+
|
28
|
+
#This module wraps functions and enums used by liblzma.
|
29
|
+
module LibLZMA
|
30
|
+
extend FFI::Library
|
31
|
+
|
32
|
+
#The maximum value of an uint64_t, as defined by liblzma.
|
33
|
+
#Should be the same as
|
34
|
+
# (2 ** 64) - 1
|
35
|
+
UINT64_MAX = 18446744073709551615
|
36
|
+
|
37
|
+
#Activates extreme compression. Same as xz's "-e" commandline switch.
|
38
|
+
LZMA_PRESET_EXTREME = 1 << 31
|
39
|
+
|
40
|
+
LZMA_TELL_NO_CHECK = 0x02
|
41
|
+
LZMA_TELL_UNSUPPORTED_CHECK = 0x02
|
42
|
+
LZMA_TELL_ANY_CHECK = 0x04
|
43
|
+
LZMA_CONCATENATED = 0x08
|
44
|
+
|
45
|
+
#Placeholder enum used by liblzma for later additions.
|
46
|
+
LZMA_RESERVED_ENUM = enum :lzma_reserved_enum, 0
|
47
|
+
|
48
|
+
#Actions that can be passed to the lzma_code() function.
|
49
|
+
LZMA_ACTION = enum :lzma_run, 0,
|
50
|
+
:lzma_sync_flush,
|
51
|
+
:lzma_full_flush,
|
52
|
+
:lzma_finish
|
53
|
+
|
54
|
+
#Integrity check algorithms supported by liblzma.
|
55
|
+
LZMA_CHECK = enum :lzma_check_none, 0,
|
56
|
+
:lzma_check_crc32, 1,
|
57
|
+
:lzma_check_crc64, 4,
|
58
|
+
:lzma_check_sha256, 10
|
59
|
+
|
60
|
+
#Possible return values of liblzma functions.
|
61
|
+
LZMA_RET = enum :lzma_ok, 0,
|
62
|
+
:lzma_stream_end,
|
63
|
+
:lzma_no_check,
|
64
|
+
:lzma_unsupported_check,
|
65
|
+
:lzma_get_check,
|
66
|
+
:lzma_mem_error,
|
67
|
+
:lzma_memlimit_error,
|
68
|
+
:lzma_format_error,
|
69
|
+
:lzma_options_error,
|
70
|
+
:lzma_data_error,
|
71
|
+
:lzma_buf_error,
|
72
|
+
:lzma_prog_error
|
73
|
+
|
74
|
+
ffi_lib ['lzma.so.2', 'lzma.so', 'lzma']
|
75
|
+
|
76
|
+
attach_function :lzma_easy_encoder, [:pointer, :uint32, :int], :int
|
77
|
+
attach_function :lzma_code, [:pointer, :int], :int
|
78
|
+
attach_function :lzma_stream_decoder, [:pointer, :uint64, :uint32], :int
|
79
|
+
attach_function :lzma_end, [:pointer], :void
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
#The class of the error that this library raises.
|
84
|
+
class LZMAError < StandardError
|
85
|
+
|
86
|
+
#Raises an appropriate exception if +val+ isn't a liblzma success code.
|
87
|
+
def self.raise_if_necessary(val)
|
88
|
+
case LibLZMA::LZMA_RET[val]
|
89
|
+
when :lzma_mem_error then raise(self, "Couldn't allocate memory!")
|
90
|
+
when :lzma_memlimit_error then raise(self, "Decoder ran out of (allowed) memory!")
|
91
|
+
when :lzma_format_error then raise(self, "Unrecognized file format!")
|
92
|
+
when :lzma_options_error then raise(self, "Invalid options passed!")
|
93
|
+
when :lzma_data_error then raise(self, "Archive is currupt.")
|
94
|
+
when :lzma_buf_error then raise(self, "Buffer unusable!")
|
95
|
+
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.")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
#The main struct of the liblzma library.
|
102
|
+
class LZMAStream < FFI::Struct
|
103
|
+
layout :next_in, :pointer, #uint8
|
104
|
+
:avail_in, :size_t,
|
105
|
+
:total_in, :uint64,
|
106
|
+
:next_out, :pointer, #uint8
|
107
|
+
:avail_out, :size_t,
|
108
|
+
:total_out, :uint64,
|
109
|
+
:lzma_allocator, :pointer,
|
110
|
+
:lzma_internal, :pointer,
|
111
|
+
:reserved_ptr1, :pointer,
|
112
|
+
:reserved_ptr2, :pointer,
|
113
|
+
:reserved_ptr3, :pointer,
|
114
|
+
:reserved_ptr4, :pointer,
|
115
|
+
:reserved_int1, :uint64,
|
116
|
+
:reserved_int2, :uint64,
|
117
|
+
:reserved_int3, :size_t,
|
118
|
+
:reserved_int4, :size_t,
|
119
|
+
:reserved_enum1, :int,
|
120
|
+
:reserved_enum2, :int
|
121
|
+
|
122
|
+
#This method does basicly the same thing as the
|
123
|
+
#LZMA_STREAM_INIT macro of liblzma. Creates a new LZMAStream
|
124
|
+
#that has been initialized for usage. If any argument is passed,
|
125
|
+
#it is assumed to be a FFI::Pointer to a lzma_stream structure
|
126
|
+
#and that structure is wrapped.
|
127
|
+
def initialize(*args)
|
128
|
+
if args.empty? #Got a pointer, want to wrap it
|
129
|
+
super
|
130
|
+
else
|
131
|
+
s = super()
|
132
|
+
s[:next] = nil
|
133
|
+
s[:avail_in] = 0
|
134
|
+
s[:total_in] = 0
|
135
|
+
s[:next_out] = nil
|
136
|
+
s[:avail_out] = 0
|
137
|
+
s[:total_out] = 0
|
138
|
+
s[:lzma_allocator] = nil
|
139
|
+
s[:lzma_internal] = nil
|
140
|
+
s[:reserved_ptr1] = nil
|
141
|
+
s[:reserved_ptr2] = nil
|
142
|
+
s[:reserved_ptr3] = nil
|
143
|
+
s[:reserved_ptr4] = nil
|
144
|
+
s[:reserved_int1] = 0
|
145
|
+
s[:reserved_int2] = 0
|
146
|
+
s[:reserved_int3] = 0
|
147
|
+
s[:reserved_int4] = 0
|
148
|
+
s[:reserved_enum1] = LibLZMA::LZMA_RESERVED_ENUM[:lzma_reserved_enum]
|
149
|
+
s[:reserved_enum2] = LibLZMA::LZMA_RESERVED_ENUM[:lzma_reserved_enum]
|
150
|
+
s
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
data/lib/xz/stream.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# (The MIT license)
|
3
|
+
#
|
4
|
+
# Basic liblzma-bindings for Ruby.
|
5
|
+
#
|
6
|
+
# Copyright © 2012 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
|
+
|
26
|
+
#The base class for XZ::StreamReader and XZ::StreamWriter.
|
27
|
+
#This is an abstract class that is not meant to be used
|
28
|
+
#directly; if you try, you will soon recognise that you’ve
|
29
|
+
#created a quite limited object ;-). You can, however, test
|
30
|
+
#against this class in <tt>kind_of?</tt> tests.
|
31
|
+
#
|
32
|
+
#XZ::StreamReader and XZ::StreamWriter are IO-like classes that
|
33
|
+
#allow you to access XZ-compressed data the same way you access
|
34
|
+
#an IO-object, easily allowing to fool other libraries that expect
|
35
|
+
#IO objects. The most noticable example for this may be reading
|
36
|
+
#and writing XZ-compressed tarballs; see XZ::StreamReader and
|
37
|
+
#XZ::StreamWriter for respective examples.
|
38
|
+
#
|
39
|
+
#Neither this class nor its subclasses document the IO-methods
|
40
|
+
#they contain--this is due to the reason that they include the
|
41
|
+
#great IO::Like module that provides all the necessary IO methods
|
42
|
+
#based on a few methods you define. For all defined IO methods,
|
43
|
+
#see the +io-like+ gem’s documentation.
|
44
|
+
class XZ::Stream
|
45
|
+
include IO::Like
|
46
|
+
|
47
|
+
#Creates a new instance of this class. Don’t use this directly,
|
48
|
+
#it’s only called by subclasses’ ::new methods.
|
49
|
+
def initialize(delegate_io)
|
50
|
+
@delegate_io = delegate_io
|
51
|
+
@lzma_stream = XZ::LZMAStream.new
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
#This method returns the size of +str+ in bytes.
|
57
|
+
def binary_size(str)
|
58
|
+
#Believe it or not, but this is faster than str.bytes.to_a.size.
|
59
|
+
#I benchmarked it, and it is as twice as fast.
|
60
|
+
if str.respond_to? :force_encoding
|
61
|
+
str.dup.force_encoding("BINARY").size
|
62
|
+
else
|
63
|
+
str.bytes.to_a.size
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,285 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# (The MIT license)
|
3
|
+
#
|
4
|
+
# Basic liblzma-bindings for Ruby.
|
5
|
+
#
|
6
|
+
# Copyright © 2012 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
|
+
|
26
|
+
#An IO-like reader class for XZ-compressed data, allowing you to
|
27
|
+
#access XZ-compressed data as if it was a normal IO object, but
|
28
|
+
#please note you can’t seek in the data--this doesn’t make much
|
29
|
+
#sense anyway. Where would you want to seek? The plain or the XZ
|
30
|
+
#data?
|
31
|
+
#
|
32
|
+
#A StreamReader object actually wraps another IO object it reads
|
33
|
+
#the compressed data from; you can either pass this IO object directly
|
34
|
+
#to the ::new method, effectively allowing you to pass any IO-like thing
|
35
|
+
#you can imagine (just ensure it is readable), or you can pass a path
|
36
|
+
#to a filename to ::new, in which case StreamReader takes care of both
|
37
|
+
#opening and closing the file correctly. You can even take it one step
|
38
|
+
#further and use the block form of ::new which will automatically call
|
39
|
+
#the #close method for you after the block finished. However, if you pass
|
40
|
+
#an IO, remember you have to close:
|
41
|
+
#
|
42
|
+
#1. The StreamReader instance.
|
43
|
+
#2. The IO object you passed to ::new.
|
44
|
+
#
|
45
|
+
#Do it <b>in exactly that order</b>, otherwise you may lose data.
|
46
|
+
#
|
47
|
+
#See the +io-like+ gem’s documentation for the IO-reading methods
|
48
|
+
#available for this class (although you’re probably familiar with
|
49
|
+
#them through Ruby’s own IO class ;-)).
|
50
|
+
#
|
51
|
+
#==Example
|
52
|
+
#In this example, we’re going to use ruby-xz together with the
|
53
|
+
#+archive-tar-minitar+ gem that allows to read tarballs. Used
|
54
|
+
#together, the two libraries allow us to read XZ-compressed tarballs.
|
55
|
+
#
|
56
|
+
# require "xz"
|
57
|
+
# require "archive/tar/minitar"
|
58
|
+
#
|
59
|
+
# XZ::StreamReader.open("foo.tar.xz") do |txz|
|
60
|
+
# # This automatically closes txz
|
61
|
+
# Archive::Tar::Minitar.unpack(txz, "foo")
|
62
|
+
# end
|
63
|
+
class XZ::StreamReader < XZ::Stream
|
64
|
+
|
65
|
+
#The memory limit you set for this reader (in ::new).
|
66
|
+
attr_reader :memory_limit
|
67
|
+
#The flags you set for this reader (in ::new).
|
68
|
+
attr_reader :flags
|
69
|
+
|
70
|
+
#call-seq:
|
71
|
+
# new(delegate, memory_limit = XZ::LibLZMA::UINT64_MAX, flags = [:tell_unsupported_check]) → a_stream_reader
|
72
|
+
# open(delegate, memory_limit = XZ::LibLZMA::UINT64_MAX, flags = [:tell_unsupported_check]) → a_stream_reader
|
73
|
+
#
|
74
|
+
#Creates a new StreamReader instance. If you pass an IO,
|
75
|
+
#remember you have to close *both* the resulting instance
|
76
|
+
#(via the #close method) and the IO object you pass to flush
|
77
|
+
#any internal buffers in order to be able to read all decompressed
|
78
|
+
#data.
|
79
|
+
#==Parameters
|
80
|
+
#[delegate] An IO object to read the data from, or a path
|
81
|
+
# to a file to open. If you’re in an urgent need to
|
82
|
+
# pass a plain string, use StringIO from Ruby’s
|
83
|
+
# standard library. If this is an IO, it must be
|
84
|
+
# opened for reading.
|
85
|
+
#The other parameters are identical to what the XZ::decompress_stream
|
86
|
+
#method expects.
|
87
|
+
#==Return value
|
88
|
+
#The newly created instance.
|
89
|
+
#==Example
|
90
|
+
# # Wrap it around a file
|
91
|
+
# f = File.open("foo.xz")
|
92
|
+
# r = XZ::StreamReader.new(f)
|
93
|
+
#
|
94
|
+
# # Ignore any XZ checksums (may result in invalid data being read!)
|
95
|
+
# File.open("foo.xz") do |f|
|
96
|
+
# r = XZ::StreamReader.new(f, XZ::LibLZMA::UINT64_MAX, [:tell_no_check]
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# # Let StreamReader handle file closing automatically
|
100
|
+
# XZ::StreamReader.new("myfile.xz"){|r| r.raed}
|
101
|
+
def initialize(delegate, memory_limit = XZ::LibLZMA::UINT64_MAX, flags = [:tell_unsupported_check])
|
102
|
+
raise(ArgumentError, "Invalid memory limit set!") unless (0..XZ::LibLZMA::UINT64_MAX).include?(memory_limit)
|
103
|
+
flags.each do |flag|
|
104
|
+
raise(ArgumentError, "Unknown flag #{flag}!") unless [:tell_no_check, :tell_unsupported_check, :tell_any_check, :concatenated].include?(flag)
|
105
|
+
end
|
106
|
+
|
107
|
+
if delegate.respond_to?(:to_io)
|
108
|
+
super(delegate)
|
109
|
+
else
|
110
|
+
@file = File.open(delegate, "rb")
|
111
|
+
super(@file)
|
112
|
+
end
|
113
|
+
|
114
|
+
@memory_limit = memory_limit
|
115
|
+
@flags = flags
|
116
|
+
|
117
|
+
res = XZ::LibLZMA.lzma_stream_decoder(@lzma_stream,
|
118
|
+
@memory_limit,
|
119
|
+
@flags.inject(0){|val, flag| val | XZ::LibLZMA.const_get(:"LZMA_#{flag.to_s.upcase}")})
|
120
|
+
XZ::LZMAError.raise_if_necessary(res)
|
121
|
+
|
122
|
+
@input_buffer_p = FFI::MemoryPointer.new(XZ::CHUNK_SIZE)
|
123
|
+
|
124
|
+
# These two are only used in #unbuffered read.
|
125
|
+
@__lzma_finished = false
|
126
|
+
@__lzma_action = nil
|
127
|
+
|
128
|
+
if block_given?
|
129
|
+
begin
|
130
|
+
yield(self)
|
131
|
+
ensure
|
132
|
+
close unless closed?
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
self.class.send(:alias_method, :open, :new)
|
137
|
+
|
138
|
+
#Closes this StreamReader instance. Don’t use it afterwards
|
139
|
+
#anymore.
|
140
|
+
#==Return value
|
141
|
+
#The total number of bytes decompressed.
|
142
|
+
#==Example
|
143
|
+
# r.close #=> 6468
|
144
|
+
#==Remarks
|
145
|
+
#If you passed an IO to ::new, this method doesn’t close it, so
|
146
|
+
#you have to close it yourself.
|
147
|
+
def close
|
148
|
+
super
|
149
|
+
|
150
|
+
# Close the XZ stream
|
151
|
+
res = XZ::LibLZMA.lzma_end(@lzma_stream.pointer)
|
152
|
+
XZ::LZMAError.raise_if_necessary(res)
|
153
|
+
|
154
|
+
#If we created a File object, close this as well.
|
155
|
+
@file.close if @file
|
156
|
+
|
157
|
+
# Return the number of bytes written in total.
|
158
|
+
@lzma_stream[:total_out]
|
159
|
+
end
|
160
|
+
|
161
|
+
#call-seq:
|
162
|
+
# pos() → an_integer
|
163
|
+
# tell() → an_integer
|
164
|
+
#
|
165
|
+
#Total number of output bytes provided to you yet.
|
166
|
+
def pos
|
167
|
+
@lzma_stream[:total_out]
|
168
|
+
end
|
169
|
+
alias tell pos
|
170
|
+
|
171
|
+
#Instrcuts liblzma to immediately stop decompression,
|
172
|
+
#rewinds the wrapped IO object and reinitalizes the
|
173
|
+
#StreamReader instance with the same values passed
|
174
|
+
#originally to the ::new method. The wrapped IO object
|
175
|
+
#must support the +rewind+ method for this method to
|
176
|
+
#work; if it doesn’t, this method throws an IOError.
|
177
|
+
#After the exception was thrown, the StreamReader instance
|
178
|
+
#is in an unusable state. You cannot continue using it
|
179
|
+
#(don’t call #close on it either); close the wrapped IO
|
180
|
+
#stream and create another instance of this class.
|
181
|
+
#==Raises
|
182
|
+
#[IOError] The wrapped IO doesn’t support rewinding.
|
183
|
+
# Do not use the StreamReader instance anymore
|
184
|
+
# after receiving this exception.
|
185
|
+
#==Remarks
|
186
|
+
#I don’t really like this method, it uses several dirty
|
187
|
+
#tricks to circumvent both io-like’s and liblzma’s control
|
188
|
+
#mechanisms. I only implemented this because the
|
189
|
+
#<tt>archive-tar-minitar</tt> gem calls this method when
|
190
|
+
#unpacking a TAR archive from a stream.
|
191
|
+
def rewind
|
192
|
+
# HACK: Wipe all data from io-like’s internal read buffer.
|
193
|
+
# This heavily relies on io-like’s internal structure.
|
194
|
+
# Be always sure to test this when a new version of
|
195
|
+
# io-like is released!
|
196
|
+
__io_like__internal_read_buffer.clear
|
197
|
+
|
198
|
+
# Forcibly close the XZ stream (internally frees it!)
|
199
|
+
res = XZ::LibLZMA.lzma_end(@lzma_stream.pointer)
|
200
|
+
XZ::LZMAError.raise_if_necessary(res)
|
201
|
+
|
202
|
+
# Rewind the wrapped IO
|
203
|
+
begin
|
204
|
+
@delegate_io.rewind
|
205
|
+
rescue => e
|
206
|
+
raise(IOError, "Delegate IO failed to rewind! Original message: #{e.message}")
|
207
|
+
end
|
208
|
+
|
209
|
+
# Reinitialize everything. Note this doesn’t affect @file as it
|
210
|
+
# is already set and stays so (we don’t pass a filename here,
|
211
|
+
# but rather an IO)
|
212
|
+
initialize(@delegate_io, @memory_limit, @flags)
|
213
|
+
end
|
214
|
+
|
215
|
+
#NO, you CANNOT seek in this object!!
|
216
|
+
#io-like’s default behaviour is to raise Errno::ESPIPE
|
217
|
+
#when calling a non-defined seek, which is not what some
|
218
|
+
#libraries such as RubyGem’s TarReader expect (they expect
|
219
|
+
#a NoMethodError/NameError instead).
|
220
|
+
undef seek
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
#Called by io-like’s read methods such as #read. Does the heavy work
|
225
|
+
#of feeding liblzma the compressed data and reading the returned
|
226
|
+
#uncompressed data.
|
227
|
+
def unbuffered_read(length)
|
228
|
+
raise(EOFError, "Input data completely processed!") if @__lzma_finished
|
229
|
+
|
230
|
+
output_buffer_p = FFI::MemoryPointer.new(length) # User guarantees that this fits into RAM
|
231
|
+
|
232
|
+
@lzma_stream[:next_out] = output_buffer_p
|
233
|
+
@lzma_stream[:avail_out] = output_buffer_p.size
|
234
|
+
|
235
|
+
loop do
|
236
|
+
# DON’T overwrite any not yet consumed input from any previous
|
237
|
+
# run! Instead, wait until the last input data is entirely
|
238
|
+
# consumed, then provide new data.
|
239
|
+
# TODO: Theoretically, one could move the remaining data to the
|
240
|
+
# beginning of the pointer and fill the rest with new data,
|
241
|
+
# being a tiny bit more performant.
|
242
|
+
if @lzma_stream[:avail_in].zero?
|
243
|
+
compressed_data = @delegate_io.read(@input_buffer_p.size) || "" # nil at EOS → ""
|
244
|
+
@input_buffer_p.write_string(compressed_data)
|
245
|
+
@lzma_stream[:next_in] = @input_buffer_p
|
246
|
+
@lzma_stream[:avail_in] = binary_size(compressed_data)
|
247
|
+
|
248
|
+
# Now check if we’re at the last bytes of data and set accordingly the
|
249
|
+
# LZMA-action to carry out (for any subsequent runs until
|
250
|
+
# all input data has been consumed and the above condition
|
251
|
+
# is triggered again).
|
252
|
+
#
|
253
|
+
# The @__lzma_action variable is only used in this method
|
254
|
+
# and is _not_ supposed to be accessed from any other method.
|
255
|
+
if compressed_data.empty?
|
256
|
+
@__lzma_action = XZ::LibLZMA::LZMA_ACTION[:lzma_finish]
|
257
|
+
else
|
258
|
+
@__lzma_action = XZ::LibLZMA::LZMA_ACTION[:lzma_run]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
res = XZ::LibLZMA.lzma_code(@lzma_stream.pointer, @__lzma_action)
|
263
|
+
|
264
|
+
# liblzma signals LZMA_BUF_ERROR when the output buffer is
|
265
|
+
# completely filled, which means we can return now.
|
266
|
+
# When it signals LZMA_STREAM_END, the buffer won’t be filled
|
267
|
+
# completely anymore as the whole input data has been consumed.
|
268
|
+
if res == XZ::LibLZMA::LZMA_RET[:lzma_buf_error]
|
269
|
+
# @lzma_stream[:avail_out] holds the number of free bytes _behind_
|
270
|
+
# the produced output!
|
271
|
+
return output_buffer_p.read_string(output_buffer_p.size - @lzma_stream[:avail_out])
|
272
|
+
elsif res == XZ::LibLZMA::LZMA_RET[:lzma_stream_end]
|
273
|
+
# @__lzma_finished is not supposed to be used outside this method!
|
274
|
+
@__lzma_finished = true
|
275
|
+
return output_buffer_p.read_string(output_buffer_p.size - @lzma_stream[:avail_out])
|
276
|
+
else
|
277
|
+
XZ::LZMAError.raise_if_necessary(res)
|
278
|
+
end
|
279
|
+
end #loop
|
280
|
+
|
281
|
+
rescue XZ::LZMAError => e
|
282
|
+
raise(SystemCallError, e.message)
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# (The MIT license)
|
3
|
+
#
|
4
|
+
# Basic liblzma-bindings for Ruby.
|
5
|
+
#
|
6
|
+
# Copyright © 2012 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
|
+
|
26
|
+
#An IO-like writer class for XZ-compressed data, allowing you to write
|
27
|
+
#uncompressed data to a stream which ends up as compressed data in
|
28
|
+
#a wrapped stream such as a file.
|
29
|
+
#
|
30
|
+
#A StreamWriter object actually wraps another IO object it writes the
|
31
|
+
#XZ-compressed data to. Here’s an ASCII art image to demonstrate
|
32
|
+
#way data flows when using StreamWriter to write to a compressed
|
33
|
+
#file:
|
34
|
+
#
|
35
|
+
# +----------------+ +------------+
|
36
|
+
# YOUR =>|StreamWriter's |=>|Wrapped IO's|=> ACTUAL
|
37
|
+
# DATA =>|internal buffers|=>|buffers |=> FILE
|
38
|
+
# +----------------+ +------------+
|
39
|
+
#
|
40
|
+
#This graphic also illustrates why it is unlikely to see written
|
41
|
+
#data directly appear in the file on your harddisk; the data is
|
42
|
+
#cached at least twice before it actually gets written out. Regarding
|
43
|
+
#file closing that means that before you can be sure any pending data
|
44
|
+
#has been written to the file you have to close both the StreamWriter
|
45
|
+
#instance and then the wrapped IO object (in *exactly* that order, otherwise
|
46
|
+
#data loss and unexpected exceptions may occur!).
|
47
|
+
#
|
48
|
+
#As it might be tedious to always remember the correct closing order,
|
49
|
+
#it’s possible to pass a filename to the ::new method. In this case,
|
50
|
+
#StreamWriter will open the file internally and also takes care closing
|
51
|
+
#it when you call the #close method.
|
52
|
+
#
|
53
|
+
#See the +io-like+ gem’s documentation for the IO-writing methods
|
54
|
+
#available for this class (although you’re probably familiar with
|
55
|
+
#them through Ruby’s own IO class ;-)).
|
56
|
+
#
|
57
|
+
#==Example
|
58
|
+
#Together with the <tt>archive-tar-minitar</tt> gem, this library
|
59
|
+
#can be used to create XZ-compressed TAR archives (these commonly
|
60
|
+
#use a file extension of <tt>.tar.xz</tt> or rarely <tt>.txz</tt>).
|
61
|
+
#
|
62
|
+
# XZ::StreamWriter.open("foo.tar.xz") do |txz|
|
63
|
+
# # This automatically closes txz
|
64
|
+
# Archive::Tar::Minitar.pack("foo", txz)
|
65
|
+
# end
|
66
|
+
class XZ::StreamWriter < XZ::Stream
|
67
|
+
|
68
|
+
#call-seq:
|
69
|
+
# open(delegate, compression_level = 6, check = :crc64, extreme = false) → a_stream_writer
|
70
|
+
# new(delegate, compression_level = 6, check = :crc64, extreme = false) → a_stream_writer
|
71
|
+
#
|
72
|
+
#Creates a new StreamWriter instance. The block form automatically
|
73
|
+
#calls the #close method when the block has finished executing.
|
74
|
+
#==Parameters
|
75
|
+
#[delegate] An IO object to write the data to or a filename
|
76
|
+
# which will be opened internally. If you pass an IO,
|
77
|
+
# the #close method won’t close the passed IO object;
|
78
|
+
# if you passed a filename, the created internal file
|
79
|
+
# of course gets closed.
|
80
|
+
#The other parameters are identical to what the XZ::compress_stream
|
81
|
+
#method expects.
|
82
|
+
#==Return value
|
83
|
+
#The newly created instance.
|
84
|
+
#==Example
|
85
|
+
# # Wrap it around a file
|
86
|
+
# f = File.open("data.xz")
|
87
|
+
# w = XZ::StreamWriter.new(f)
|
88
|
+
#
|
89
|
+
# # Use SHA256 as the checksum and use a higher compression level
|
90
|
+
# # than the default (6)
|
91
|
+
# f = File.open("data.xz")
|
92
|
+
# w = XZ::StreamWriter.new(f, 8, :sha256)
|
93
|
+
#
|
94
|
+
# # Instruct liblzma to use ultra-really-high compression
|
95
|
+
# # (may take eternity)
|
96
|
+
# f = File.open("data.xz")
|
97
|
+
# w = XZ::StreamWriter.new(f, 9, :crc64, true)
|
98
|
+
#
|
99
|
+
# # Passing a filename
|
100
|
+
# w = XZ::StreamWriter.new("compressed_data.xz")
|
101
|
+
def initialize(delegate, compression_level = 6, check = :crc64, extreme = false)
|
102
|
+
if delegate.respond_to?(:to_io)
|
103
|
+
super(delegate)
|
104
|
+
else
|
105
|
+
@file = File.open(delegate, "wb")
|
106
|
+
super(@file)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Initialize the internal LZMA stream for encoding
|
110
|
+
res = XZ::LibLZMA.lzma_easy_encoder(@lzma_stream.pointer,
|
111
|
+
compression_level | (extreme ? XZ::LibLZMA::LZMA_PRESET_EXTREME : 0),
|
112
|
+
XZ::LibLZMA::LZMA_CHECK[:"lzma_check_#{check}"])
|
113
|
+
XZ::LZMAError.raise_if_necessary(res)
|
114
|
+
|
115
|
+
if block_given?
|
116
|
+
begin
|
117
|
+
yield(self)
|
118
|
+
ensure
|
119
|
+
close unless closed?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
self.class.send(:alias_method, :open, :new)
|
124
|
+
|
125
|
+
#Closes this StreamWriter instance and flushes all internal buffers.
|
126
|
+
#Don’t use it afterwards anymore.
|
127
|
+
#==Return vaule
|
128
|
+
#The total number of bytes written, i.e. the size of the compressed
|
129
|
+
#data.
|
130
|
+
#==Example
|
131
|
+
# w.close #=> 424
|
132
|
+
#==Remarks
|
133
|
+
#If you passed an IO object to ::new, this method doesn’t close it,
|
134
|
+
#you have to do that yourself.
|
135
|
+
def close
|
136
|
+
super
|
137
|
+
|
138
|
+
#1. Close the current block ("file") (an XZ stream may actually include
|
139
|
+
# multiple compressed files, which however is not supported by
|
140
|
+
# this library). For this we have to tell liblzma that
|
141
|
+
# the next bytes we pass to it are the last bytes (by means of
|
142
|
+
# the FINISH action). Just that we don’t pass any new input ;-)
|
143
|
+
|
144
|
+
output_buffer_p = FFI::MemoryPointer.new(XZ::CHUNK_SIZE)
|
145
|
+
|
146
|
+
# Get any pending data (LZMA_FINISH causes libzlma to flush its
|
147
|
+
# internal buffers) and write it out to our wrapped IO.
|
148
|
+
loop do
|
149
|
+
@lzma_stream[:next_out] = output_buffer_p
|
150
|
+
@lzma_stream[:avail_out] = output_buffer_p.size
|
151
|
+
|
152
|
+
res = XZ::LibLZMA.lzma_code(@lzma_stream.pointer, XZ::LibLZMA::LZMA_ACTION[:lzma_finish])
|
153
|
+
XZ::LZMAError.raise_if_necessary(res)
|
154
|
+
|
155
|
+
@delegate_io.write(output_buffer_p.read_string(output_buffer_p.size - @lzma_stream[:avail_out]))
|
156
|
+
|
157
|
+
break unless @lzma_stream[:avail_out] == 0
|
158
|
+
end
|
159
|
+
|
160
|
+
#2. Close the whole XZ stream.
|
161
|
+
res = XZ::LibLZMA.lzma_end(@lzma_stream.pointer)
|
162
|
+
XZ::LZMAError.raise_if_necessary(res)
|
163
|
+
|
164
|
+
#2b. If we wrapped a file automatically, close it.
|
165
|
+
@file.close if @file
|
166
|
+
|
167
|
+
#3. Return the number of bytes written in total.
|
168
|
+
@lzma_stream[:total_out]
|
169
|
+
end
|
170
|
+
|
171
|
+
#call-seq:
|
172
|
+
# pos() → an_integer
|
173
|
+
# tell() → an_integer
|
174
|
+
#
|
175
|
+
#Total number of input bytes read so far from what you
|
176
|
+
#supplied to any writer method.
|
177
|
+
def pos
|
178
|
+
@lzma_stream[:total_in]
|
179
|
+
end
|
180
|
+
alias tell pos
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
#Called by io-like’s write methods such as #write. Does the heavy
|
185
|
+
#work of feeding liblzma the uncompressed data and reading the
|
186
|
+
#returned compressed data.
|
187
|
+
def unbuffered_write(data)
|
188
|
+
output_buffer_p = FFI::MemoryPointer.new(XZ::CHUNK_SIZE)
|
189
|
+
input_buffer_p = FFI::MemoryPointer.from_string(data) # This adds a terminating NUL byte we don’t want to compress!
|
190
|
+
|
191
|
+
@lzma_stream[:next_in] = input_buffer_p
|
192
|
+
@lzma_stream[:avail_in] = input_buffer_p.size - 1 # Don’t hand the terminating NUL
|
193
|
+
|
194
|
+
loop do
|
195
|
+
@lzma_stream[:next_out] = output_buffer_p
|
196
|
+
@lzma_stream[:avail_out] = output_buffer_p.size
|
197
|
+
|
198
|
+
# Compress the data
|
199
|
+
res = XZ::LibLZMA.lzma_code(@lzma_stream.pointer, XZ::LibLZMA::LZMA_ACTION[:lzma_run])
|
200
|
+
XZ::LZMAError.raise_if_necessary(res) # TODO: Warnings
|
201
|
+
|
202
|
+
# Write the compressed data
|
203
|
+
result = output_buffer_p.read_string(output_buffer_p.size - @lzma_stream[:avail_out])
|
204
|
+
@delegate_io.write(result)
|
205
|
+
|
206
|
+
# Loop until liblzma ate the whole data.
|
207
|
+
break if @lzma_stream[:avail_in] == 0
|
208
|
+
end
|
209
|
+
|
210
|
+
binary_size(data)
|
211
|
+
rescue XZ::LZMAError => e
|
212
|
+
raise(SystemCallError, e.message)
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-xz
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,12 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
default_executable:
|
12
|
+
date: 2012-02-17 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: ffi
|
17
|
-
requirement: &
|
16
|
+
requirement: &12835000 !ruby/object:Gem::Requirement
|
18
17
|
none: false
|
19
18
|
requirements:
|
20
19
|
- - ! '>='
|
@@ -22,10 +21,10 @@ dependencies:
|
|
22
21
|
version: '0'
|
23
22
|
type: :runtime
|
24
23
|
prerelease: false
|
25
|
-
version_requirements: *
|
24
|
+
version_requirements: *12835000
|
26
25
|
- !ruby/object:Gem::Dependency
|
27
26
|
name: hanna-nouveau
|
28
|
-
requirement: &
|
27
|
+
requirement: &12834200 !ruby/object:Gem::Requirement
|
29
28
|
none: false
|
30
29
|
requirements:
|
31
30
|
- - ! '>='
|
@@ -33,7 +32,7 @@ dependencies:
|
|
33
32
|
version: '0'
|
34
33
|
type: :development
|
35
34
|
prerelease: false
|
36
|
-
version_requirements: *
|
35
|
+
version_requirements: *12834200
|
37
36
|
description: ! 'This is a basic binding for liblzma that allows you to
|
38
37
|
|
39
38
|
create and extract XZ-compressed archives. It can cope with big
|
@@ -49,9 +48,14 @@ extensions: []
|
|
49
48
|
extra_rdoc_files:
|
50
49
|
- README.rdoc
|
51
50
|
files:
|
51
|
+
- lib/xz/stream_reader.rb
|
52
|
+
- lib/xz/stream.rb
|
53
|
+
- lib/xz/lib_lzma.rb
|
54
|
+
- lib/xz/stream_writer.rb
|
52
55
|
- lib/xz.rb
|
53
56
|
- README.rdoc
|
54
|
-
|
57
|
+
- HISTORY.rdoc
|
58
|
+
- VERSION
|
55
59
|
homepage:
|
56
60
|
licenses: []
|
57
61
|
post_install_message:
|
@@ -67,7 +71,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
71
|
requirements:
|
68
72
|
- - ! '>='
|
69
73
|
- !ruby/object:Gem::Version
|
70
|
-
version:
|
74
|
+
version: 1.8.7
|
71
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
76
|
none: false
|
73
77
|
requirements:
|
@@ -76,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
80
|
version: '0'
|
77
81
|
requirements: []
|
78
82
|
rubyforge_project:
|
79
|
-
rubygems_version: 1.
|
83
|
+
rubygems_version: 1.8.16
|
80
84
|
signing_key:
|
81
85
|
specification_version: 3
|
82
86
|
summary: XZ compression via liblzma for Ruby.
|