ruby-xz 0.0.1 → 0.1.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.
- 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.
|