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.
@@ -0,0 +1,6 @@
1
+ == 0.1.0
2
+
3
+ * <b>Add XZ::StreamReader and XZ::StreamWriter for io-like behaviour.</b>
4
+ * New dependency on the +io-like+ gem.
5
+ * <b>Add Ruby 1.8 compatibility.</b> Thanks to Christoph Plank.
6
+ * We now have proper unit tests.
@@ -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
- #Encoding: UTF-8
2
- =begin (The MIT License)
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
- Basic liblzma-bindings for Ruby.
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
- Copyright © 2011 Marvin Gülker
72
+ file = caller.first.split(/:\d/,2).first
7
73
 
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:
74
+ raise LoadError, "require_relative is called in #{$1}" if /\A\((.*)\)/ =~ file
14
75
 
15
- The above copyright notice and this permission notice shall be included in all
16
- copies or substantial portions of the Software.
76
+ require File.expand_path(relative_feature, File.dirname(file))
77
+ end
78
+ end
17
79
 
18
- THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
- THE SOFTWARE.
25
- =end
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 = "0.0.1".freeze
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
- # itnegrity checksum.
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 your read in RAM then as in the
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
- stream.pointer,
279
- compression_level | (extreme ? LibLZMA::LZMA_PRESET_EXTREME : 0),
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.dup.force_encoding("BINARY").size
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 = FFI::MemoryPointer.new(CHUNK_SIZE)
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] = input_buffer_p
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] = output_buffer_p
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] then warn("Couldn't verify archive integrity--archive has not integrity checksum.")
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] then nil #This isn't useful for us. It indicates that the checksum type is now known.
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"
@@ -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
@@ -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.1
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: 2011-03-16 00:00:00.000000000 +01:00
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: &15267800 !ruby/object:Gem::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: *15267800
24
+ version_requirements: *12835000
26
25
  - !ruby/object:Gem::Dependency
27
26
  name: hanna-nouveau
28
- requirement: &15267300 !ruby/object:Gem::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: *15267300
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
- has_rdoc: true
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: '1.9'
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.6.2
83
+ rubygems_version: 1.8.16
80
84
  signing_key:
81
85
  specification_version: 3
82
86
  summary: XZ compression via liblzma for Ruby.