bzip2-ffi 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +2 -0
- data.tar.gz.sig +1 -0
- data/.yardopts +7 -0
- data/CHANGES.md +4 -0
- data/Gemfile +14 -0
- data/LICENSE +19 -0
- data/README.md +144 -0
- data/Rakefile +62 -0
- data/bzip2-ffi.gemspec +30 -0
- data/lib/bzip2/ffi.rb +14 -0
- data/lib/bzip2/ffi/error.rb +100 -0
- data/lib/bzip2/ffi/io.rb +250 -0
- data/lib/bzip2/ffi/libbz2.rb +75 -0
- data/lib/bzip2/ffi/reader.rb +422 -0
- data/lib/bzip2/ffi/version.rb +6 -0
- data/lib/bzip2/ffi/writer.rb +373 -0
- data/test/error_test.rb +38 -0
- data/test/fixtures/bzipped +0 -0
- data/test/fixtures/lorem.txt +2440 -0
- data/test/fixtures/moon.tiff +0 -0
- data/test/fixtures/zero.txt +512 -0
- data/test/io_test.rb +382 -0
- data/test/reader_test.rb +498 -0
- data/test/test_helper.rb +98 -0
- data/test/version_test.rb +7 -0
- data/test/writer_test.rb +439 -0
- metadata +117 -0
- metadata.gz.sig +1 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 91dd417ec50336680acdedba3bc7eb9c80a268f6
|
4
|
+
data.tar.gz: 637b7684138b2e615ce3720dd8d9a927029481bb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a74b7c8e55a23de078c58b0d1c10b9f1addba8867a3b91bac0fbd7c4f22a663e5dccbbc1523814c5e6fdd779728c2d6acb92ad08bce3c05102a42eb97dd743b4
|
7
|
+
data.tar.gz: 10a5f0950a4bb5e4005632679097bcfdfb838bfcebf120ea9f986b9d30c5d1042ebb01bb7e866cb1891a919cbc90746f2a30df5dbce58bc3aa0c570cd828c750
|
checksums.yaml.gz.sig
ADDED
data.tar.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
D �h��pX�曦���3�2��镀W��S��F0�'�����^3T{�0o�8�3��Ğ�|A��o!�j�K��������My�� v�)�8+X��G�f������}[2j�p��1�=i�Kņ��;��'o�@�IP!u�-���p��.�=�r1��� �/��iԇ�t��qy�+:[ʠ�7q��Q�l@,�F������62\�we��ep��������&�ѫ 8]�דzֶ��9t) �'iX'�
|
data/.yardopts
ADDED
data/CHANGES.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem 'rake', '~> 10.0'
|
7
|
+
gem 'git', '~> 1.2', require: false
|
8
|
+
end
|
9
|
+
|
10
|
+
group :test do
|
11
|
+
gem 'minitest', '~> 5.0'
|
12
|
+
gem 'simplecov', '~> 0.9', require: false
|
13
|
+
gem 'coveralls', '~> 0.7', require: false
|
14
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2015 Philip Ross
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
# Bzip2::FFI #
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/bzip2-ffi) [](https://travis-ci.org/philr/bzip2-ffi) [](https://coveralls.io/r/philr/bzip2-ffi?branch=master)
|
4
|
+
|
5
|
+
Bzip2::FFI is a Ruby wrapper for libbz2 using FFI bindings.
|
6
|
+
|
7
|
+
The Bzip2::FFI Reader and Writer classes support reading and writing bzip2
|
8
|
+
compressed data as an `IO`-like stream.
|
9
|
+
|
10
|
+
|
11
|
+
## Installation ##
|
12
|
+
|
13
|
+
To install the Bzip2::FFI gem, run the following command:
|
14
|
+
|
15
|
+
gem install bzip2-ffi
|
16
|
+
|
17
|
+
To add Bzip2::FFI as a Bundler dependency, add the following line to your
|
18
|
+
`Gemfile`:
|
19
|
+
|
20
|
+
gem 'bzip2-ffi'
|
21
|
+
|
22
|
+
|
23
|
+
## Compatibility ##
|
24
|
+
|
25
|
+
Bzip2::FFI is tested on Ruby MRI 1.9.3+, JRuby 1.7+ and Rubinius 2+.
|
26
|
+
|
27
|
+
|
28
|
+
## Runtime Dependencies ##
|
29
|
+
|
30
|
+
Bzip2::FFI is a pure-Ruby library that uses
|
31
|
+
[Ruby-FFI](https://rubygems.org/gems/ffi) (Foreign Function Interface) to load
|
32
|
+
the libbz2 dynamic library at runtime.
|
33
|
+
|
34
|
+
libbz2 is available as a package on most UNIX-based systems (for example,
|
35
|
+
`libbz2-1.0` on Debian and Ubuntu, or `bzip2-libs` on Fedora, Red Hat, and
|
36
|
+
CentOS).
|
37
|
+
|
38
|
+
|
39
|
+
### Windows ###
|
40
|
+
|
41
|
+
On Windows, you will need to have `libbz2.dll` or `bz2.dll` available on the
|
42
|
+
`PATH` or in the Ruby `bin` directory.
|
43
|
+
|
44
|
+
Suitable builds of `libbz2.dll` are available from the
|
45
|
+
[bzip2-windows project](https://github.com/philr/bzip2-windows/releases).
|
46
|
+
Download the DLL only package that matches your Ruby installation (x86 or x64)
|
47
|
+
and extract to your `ruby\bin` directory.
|
48
|
+
|
49
|
+
Builds from the bzip2-windows project depend on the Visual Studio 2013 C Runtime
|
50
|
+
Library (msvcr120.dll). This can be installed using the
|
51
|
+
[Visual C++ Redistributable Packages for Visual Studio 2013 installer](http://www.microsoft.com/en-gb/download/details.aspx?id=40784).
|
52
|
+
|
53
|
+
|
54
|
+
## Usage ##
|
55
|
+
|
56
|
+
To use Bzip2::FFI, it must first be loaded with:
|
57
|
+
|
58
|
+
require 'bzip2/ffi'
|
59
|
+
|
60
|
+
|
61
|
+
### Compressing ###
|
62
|
+
|
63
|
+
Data can be compressed using the `Bzip2::FFI::Writer` class. For example, the
|
64
|
+
following compresses lines read from standard input (`ARGF`):
|
65
|
+
|
66
|
+
Bzip2::FFI::Writer.open(io_or_path) do |writer|
|
67
|
+
ARGF.each_line do |line|
|
68
|
+
writer.write(line)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Alternatively, without passing a block to `open`:
|
73
|
+
|
74
|
+
writer = Bzip2::FFI::Writer.open(io_or_path)
|
75
|
+
begin
|
76
|
+
ARGF.each_line do |line|
|
77
|
+
writer.write(line)
|
78
|
+
end
|
79
|
+
ensure
|
80
|
+
writer.close
|
81
|
+
end
|
82
|
+
|
83
|
+
An entire bzip2 structure can also be written in a single step:
|
84
|
+
|
85
|
+
Bzip2::FFI::Writer.write(io_or_path, 'Hello, World!')
|
86
|
+
|
87
|
+
In each of the examples above, `io_or_path` can either be a path to a file to
|
88
|
+
write to or an `IO`-like object that has a `write` method.
|
89
|
+
|
90
|
+
|
91
|
+
### Decompressing ###
|
92
|
+
|
93
|
+
Data can be decompressed using the `Bzip2::FFI::Reader` class. For example:
|
94
|
+
|
95
|
+
Bzip2::FFI::Reader.open(io_or_path) do |reader|
|
96
|
+
while buffer = reader.read(1024) do
|
97
|
+
# process uncompressed bytes in buffer
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
Alternatively, without passing a block to `open`:
|
102
|
+
|
103
|
+
reader = Bzip2::FFI::Reader.open(io_or_path)
|
104
|
+
begin
|
105
|
+
while buffer = reader.read(1024) do
|
106
|
+
# process uncompressed bytes in buffer
|
107
|
+
end
|
108
|
+
ensure
|
109
|
+
reader.close
|
110
|
+
end
|
111
|
+
|
112
|
+
An entire bzip2 structure can be read and decompressed in a single step:
|
113
|
+
|
114
|
+
uncompressed = Bzip2::FFI::Reader.read(io_or_path)
|
115
|
+
|
116
|
+
In each of the examples above, `io_or_path` can either be a path to a file to
|
117
|
+
read from or an `IO`-like object that has a `read` method.
|
118
|
+
|
119
|
+
|
120
|
+
### Character Encoding ###
|
121
|
+
|
122
|
+
Bzip2::FFI does not perform any encoding conversion when reading or writing.
|
123
|
+
Data read using `Bzip2::FFI::Reader` is returned as `String` instances with
|
124
|
+
ASCII-8BIT (BINARY) encoding representing the raw decompressed bytes.
|
125
|
+
`Bzip2::FFI::Writer` compresses the raw bytes from the `Strings` passed to the
|
126
|
+
`write` method (using the encoding of the `String`).
|
127
|
+
|
128
|
+
|
129
|
+
## Documentation ##
|
130
|
+
|
131
|
+
Documentation for Bzip2::FFI is available on
|
132
|
+
[RubyDoc.info](http://www.rubydoc.info/gems/bzip2-ffi).
|
133
|
+
|
134
|
+
|
135
|
+
## License ##
|
136
|
+
|
137
|
+
Bzip2::FFI is distributed under the terms of the MIT license. A copy of this
|
138
|
+
license can be found in the included LICENSE file.
|
139
|
+
|
140
|
+
|
141
|
+
## GitHub Project ##
|
142
|
+
|
143
|
+
Source code, release information and the issue tracker can be found on the
|
144
|
+
[Bzip2::FFI GitHub project page](https://github.com/philr/bzip2-ffi).
|
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/package_task'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
BASE_DIR = File.expand_path(File.dirname(__FILE__))
|
6
|
+
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
def spec
|
10
|
+
@spec ||= eval(File.read('bzip2-ffi.gemspec'))
|
11
|
+
end
|
12
|
+
|
13
|
+
# Attempt to find the private key and return a spec with added options for
|
14
|
+
# signing the gem if found.
|
15
|
+
def add_signing_key(spec)
|
16
|
+
private_key_path = File.expand_path(File.join(BASE_DIR, '..', 'key', 'gem-private_key.pem'))
|
17
|
+
|
18
|
+
if File.exist?(private_key_path)
|
19
|
+
spec = spec.clone
|
20
|
+
spec.signing_key = private_key_path
|
21
|
+
spec.cert_chain = [File.join(BASE_DIR, 'gem-public_cert.pem')]
|
22
|
+
else
|
23
|
+
puts 'WARNING: Private key not found. Not signing gem file.'
|
24
|
+
end
|
25
|
+
|
26
|
+
spec
|
27
|
+
end
|
28
|
+
|
29
|
+
package_task = Gem::PackageTask.new(add_signing_key(spec)) do
|
30
|
+
end
|
31
|
+
|
32
|
+
# Ensure files are world-readable before packaging.
|
33
|
+
Rake::Task[package_task.package_dir_path].enhance do
|
34
|
+
recurse_chmod(package_task.package_dir_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def recurse_chmod(dir)
|
38
|
+
File.chmod(0755, dir)
|
39
|
+
|
40
|
+
Dir.entries(dir).each do |entry|
|
41
|
+
if entry != '.' && entry != '..'
|
42
|
+
path = File.join(dir, entry)
|
43
|
+
if File.directory?(path)
|
44
|
+
recurse_chmod(path)
|
45
|
+
else
|
46
|
+
File.chmod(0644, path)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
task :tag do
|
53
|
+
require 'git'
|
54
|
+
g = Git.init(BASE_DIR)
|
55
|
+
g.add_tag("v#{spec.version}", annotate: true, message: "Tagging v#{spec.version}")
|
56
|
+
end
|
57
|
+
|
58
|
+
Rake::TestTask.new do |t|
|
59
|
+
t.libs = [File.join(BASE_DIR, 'test')]
|
60
|
+
t.pattern = File.join(BASE_DIR, 'test', '**', '*_test.rb')
|
61
|
+
t.warning = true
|
62
|
+
end
|
data/bzip2-ffi.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path(File.join('..', 'lib', 'bzip2', 'ffi', 'version'), __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'bzip2-ffi'
|
5
|
+
s.version = Bzip2::FFI::VERSION
|
6
|
+
s.summary = 'Reads and writes bzip2 compressed data using FFI bindings for libbz2.'
|
7
|
+
s.description = <<-EOF
|
8
|
+
Bzip2::FFI is a Ruby wrapper for libbz2 using FFI bindings.
|
9
|
+
|
10
|
+
The Bzip2::FFI Reader and Writer classes support reading and writing bzip2
|
11
|
+
compressed data as an IO-like stream.
|
12
|
+
EOF
|
13
|
+
s.author = 'Philip Ross'
|
14
|
+
s.email = 'phil.ross@gmail.com'
|
15
|
+
s.homepage = 'https://github.com/philr/bzip2-ffi'
|
16
|
+
s.license = 'MIT'
|
17
|
+
s.files = %w(CHANGES.md Gemfile LICENSE README.md Rakefile bzip2-ffi.gemspec .yardopts) +
|
18
|
+
Dir['lib/**/*.rb'] +
|
19
|
+
Dir['test/**/*.rb'] +
|
20
|
+
Dir['test/fixtures/*']
|
21
|
+
s.platform = Gem::Platform::RUBY
|
22
|
+
s.require_path = 'lib'
|
23
|
+
s.rdoc_options << '--title' << 'Bzip2::FFI' <<
|
24
|
+
'--main' << 'README.md' <<
|
25
|
+
'--markup' << 'markdown'
|
26
|
+
s.extra_rdoc_files = ['CHANGES.md', 'LICENSE', 'README.md']
|
27
|
+
s.required_ruby_version = '>= 1.9.3'
|
28
|
+
s.add_runtime_dependency 'ffi', '~> 1.0'
|
29
|
+
s.requirements << 'libbz2.(so|dll|dylib) available on the library search path'
|
30
|
+
end
|
data/lib/bzip2/ffi.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Bzip2
|
2
|
+
# Bzip2::FFI is a wrapper for libbz2 using FFI bindings. Bzip2 compressed data
|
3
|
+
# can be read and written as a stream using the Reader and Writer classes.
|
4
|
+
module FFI
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'bzip2/ffi/libbz2'
|
9
|
+
require 'bzip2/ffi/error'
|
10
|
+
require 'bzip2/ffi/io'
|
11
|
+
require 'bzip2/ffi/reader'
|
12
|
+
require 'bzip2/ffi/writer'
|
13
|
+
require 'bzip2/ffi/version'
|
14
|
+
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Bzip2
|
2
|
+
module FFI
|
3
|
+
# The Bzip2::FFI::Error namespace contains exception classes that are raised
|
4
|
+
# if an error occurs whilst compressing or decompressing data.
|
5
|
+
module Error
|
6
|
+
# Base class for Bzip2::FFI exceptions.
|
7
|
+
class Bzip2Error < IOError
|
8
|
+
end
|
9
|
+
|
10
|
+
# Raised if libbz2 functions were called out of sequence or with data
|
11
|
+
# structures in incorrect states.
|
12
|
+
class SequenceError < Bzip2Error
|
13
|
+
# Initializes a new instance of SequenceError.
|
14
|
+
#
|
15
|
+
# @private
|
16
|
+
def initialize #:nodoc:
|
17
|
+
super('libbz2 functions called out of sequence or with data structures in incorrect states (this is likely to be caused by a bug in Bzip2::FFI)')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Raised if a parameter passed to libbz2 was out of range or incorrect.
|
22
|
+
class ParamError < Bzip2Error
|
23
|
+
# Initializes a new instance of ParamError.
|
24
|
+
#
|
25
|
+
# @private
|
26
|
+
def initialize #:nodoc:
|
27
|
+
super('A parameter passed to libbz2 is out of range or incorrect (this may indicate a bug in Bzip2::FFI)')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Raised if a failure occurred allocating memory to complete a request.
|
32
|
+
class MemoryError < Bzip2Error
|
33
|
+
# Initializes a new instance of MemoryError.
|
34
|
+
#
|
35
|
+
# @private
|
36
|
+
def initialize #:nodoc:
|
37
|
+
super('Could not allocate enough memory to perform this request')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Raised if a data integrity error is detected (a mismatch between
|
42
|
+
# stored and computed CRCs or another anomaly in the compressed data).
|
43
|
+
class DataError < Bzip2Error
|
44
|
+
# Initializes a new instance of DataError.
|
45
|
+
#
|
46
|
+
# @param message [String] Exception message (overrides the default).
|
47
|
+
# @private
|
48
|
+
def initialize(message = nil) #:nodoc:
|
49
|
+
super(message || 'Data integrity error detected (mismatch between stored and computed CRCs, or other anomaly in the compressed data)')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Raised if the compressed data does not start with the correct magic
|
54
|
+
# bytes ('BZh').
|
55
|
+
class MagicDataError < DataError
|
56
|
+
# Initializes a new instance of MagicDataError.
|
57
|
+
#
|
58
|
+
# @private
|
59
|
+
def initialize #:nodoc:
|
60
|
+
super('Compressed data does not start with the correct magic bytes (\'BZh\')')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Raised if libbz2 detects that it has been improperly compiled.
|
65
|
+
class ConfigError < Bzip2Error
|
66
|
+
# Initializes a new instance of ConfigError.
|
67
|
+
#
|
68
|
+
# @private
|
69
|
+
def initialize #:nodoc:
|
70
|
+
super('libbz2 has been improperly compiled on your platform')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Raised if an end of file (EOF) condition was detected before the end
|
75
|
+
# of the logical bzip2 stream.
|
76
|
+
class UnexpectedEofError < Bzip2Error
|
77
|
+
# UnexpectedEofError is raised directly by Reader. It does not map to
|
78
|
+
# a libbz2 low-level interface error code.
|
79
|
+
|
80
|
+
# Initializes a new instance of UnexpectedEofError.
|
81
|
+
#
|
82
|
+
# @private
|
83
|
+
def initialize #:nodoc:
|
84
|
+
super('EOF was detected before the end of the logical stream')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Raised if libbz2 reported an unexpected error code.
|
89
|
+
class UnexpectedError < Bzip2Error
|
90
|
+
# Initializes a new instance of UnexpectedError.
|
91
|
+
#
|
92
|
+
# @param error_code [Integer] The error_code reported by libbz2.
|
93
|
+
# @private
|
94
|
+
def initialize(error_code) #:nodoc:
|
95
|
+
super("An unexpected error was detected (error code: #{error_code})")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/bzip2/ffi/io.rb
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
module Bzip2
|
2
|
+
module FFI
|
3
|
+
# `IO` is a base class providing common functionality for the {Reader} and
|
4
|
+
# {Writer} subclasses.
|
5
|
+
#
|
6
|
+
# `Bzip2::FFI::IO` holds a reference to an underlying `IO`-like stream
|
7
|
+
# representing the bzip2-compressed data to be read from or written to.
|
8
|
+
class IO
|
9
|
+
class << self
|
10
|
+
protected :new
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
# If no block is provided, returns a new `IO`. If a block is provided,
|
15
|
+
# a new `IO` is created and yielded to the block. After the block has
|
16
|
+
# executed, the `IO` is closed and the result of the block is returned.
|
17
|
+
#
|
18
|
+
# If `io_or_proc` is a `Proc`, it is called to obtain an IO-like
|
19
|
+
# instance to pass to `new`. Otherwise `io_or_proc` is passed directly
|
20
|
+
# to `new`.
|
21
|
+
#
|
22
|
+
# @param io_or_proc [Object] An IO-like object or a `Proc` that returns
|
23
|
+
# an IO-like object when called.
|
24
|
+
# @param options [Hash] Options to pass to `new`.
|
25
|
+
def open(io_or_proc, options = {})
|
26
|
+
if io_or_proc.kind_of?(Proc)
|
27
|
+
io = io_or_proc.call
|
28
|
+
begin
|
29
|
+
bz_io = new(io, options)
|
30
|
+
rescue
|
31
|
+
io.close if io.respond_to?(:close)
|
32
|
+
raise
|
33
|
+
end
|
34
|
+
else
|
35
|
+
bz_io = new(io_or_proc, options)
|
36
|
+
end
|
37
|
+
|
38
|
+
if block_given?
|
39
|
+
begin
|
40
|
+
yield bz_io
|
41
|
+
ensure
|
42
|
+
bz_io.close unless bz_io.closed?
|
43
|
+
end
|
44
|
+
else
|
45
|
+
bz_io
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Opens and returns a bzip `File` using the specified mode. The system
|
50
|
+
# is advised that the file will be accessed once sequentially.
|
51
|
+
#
|
52
|
+
# @param path [String] The path to open.
|
53
|
+
# @param mode [String] The file open mode to use.
|
54
|
+
# @return [File] An open `File` object for `path` opened using `mode`.
|
55
|
+
def open_bzip_file(path, mode)
|
56
|
+
io = File.open(path, mode)
|
57
|
+
|
58
|
+
begin
|
59
|
+
after_open_file(io)
|
60
|
+
rescue
|
61
|
+
io.close
|
62
|
+
raise
|
63
|
+
end
|
64
|
+
|
65
|
+
io
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Advises the system that an `IO` will be accessed once sequentially.
|
71
|
+
#
|
72
|
+
# @param io [IO] An `IO` instance to advise.
|
73
|
+
def after_open_file(io)
|
74
|
+
# JRuby 1.7.18 doesn't have a File#advise method (in any mode).
|
75
|
+
if io.respond_to?(:advise)
|
76
|
+
io.advise(:sequential)
|
77
|
+
io.advise(:noreuse)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns `true` if the underlying compressed `IO` instance will be closed
|
83
|
+
# when {#close} is called, otherwise `false`.
|
84
|
+
#
|
85
|
+
# @return [Boolean] `true` if the underlying compressed IO instance will
|
86
|
+
# be closed when {#close} is closed, otherwise `false`.
|
87
|
+
# @raise [IOError] If the instance has been closed.
|
88
|
+
def autoclose?
|
89
|
+
check_closed
|
90
|
+
@autoclose
|
91
|
+
end
|
92
|
+
|
93
|
+
# Sets whether the underlying compressed `IO` instance should be closed
|
94
|
+
# when {#close} is called (`true`) or left open (`false`).
|
95
|
+
#
|
96
|
+
# @param autoclose [Boolean] `true` if the underlying compressed `IO`
|
97
|
+
# instance should be closed when {#close} is
|
98
|
+
# called, or `false` if it should be left open.
|
99
|
+
# @raise [IOError] If the instance has been closed.
|
100
|
+
def autoclose=(autoclose)
|
101
|
+
check_closed
|
102
|
+
@autoclose = !!autoclose
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns `true` to indicate that the `IO` is operating in binary mode
|
106
|
+
# (as is always the case).
|
107
|
+
#
|
108
|
+
# @return [Boolean] `true`.
|
109
|
+
# @raise [IOError] If the `IO` has been closed.
|
110
|
+
def binmode?
|
111
|
+
check_closed
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
# Puts the `IO` into binary mode.
|
116
|
+
#
|
117
|
+
# Note that `Bzip2::FFI::IO` and subclasses always operate in binary mode,
|
118
|
+
# so calling `binmode` has no effect.
|
119
|
+
#
|
120
|
+
# @return [IO] `self`.
|
121
|
+
# @raise [IOError] If the `IO` has been closed.
|
122
|
+
def binmode
|
123
|
+
check_closed
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
127
|
+
# Closes the `IO`.
|
128
|
+
#
|
129
|
+
# If {#autoclose?} is true and the underlying compressed `IO` responds to
|
130
|
+
# `close`, it will also be closed.
|
131
|
+
#
|
132
|
+
# @return [NilClass] `nil`.
|
133
|
+
# @raise [IOError] If the `IO` has already been closed.
|
134
|
+
def close
|
135
|
+
check_closed
|
136
|
+
@io.close if autoclose? && @io.respond_to?(:close)
|
137
|
+
@stream = nil
|
138
|
+
end
|
139
|
+
|
140
|
+
# Indicates whether the `IO` has been closed by calling {#close}.
|
141
|
+
#
|
142
|
+
# @return [Boolean] `true` if the `IO` has been closed, otherwise `false`.
|
143
|
+
def closed?
|
144
|
+
!@stream
|
145
|
+
end
|
146
|
+
|
147
|
+
# Returns the `Encoding` object that represents the encoding of data
|
148
|
+
# prior to being compressed or after being decompressed.
|
149
|
+
#
|
150
|
+
# No character conversion is performed, so `external_encoding` always
|
151
|
+
# returns `Encoding::ASCII_8BIT` (also known as `Encoding::BINARY`).
|
152
|
+
#
|
153
|
+
# @return [Encoding] `Encoding::ASCII_8BIT`.
|
154
|
+
# @raise [IOError] If the `IO` has been closed.
|
155
|
+
def external_encoding
|
156
|
+
check_closed
|
157
|
+
Encoding::ASCII_8BIT
|
158
|
+
end
|
159
|
+
|
160
|
+
# The internal encoding for character conversions.
|
161
|
+
#
|
162
|
+
# No character conversion is performed, so `internal_encoding` always
|
163
|
+
# returns `Encoding::ASCII_8BIT` (also known as `Encoding::BINARY`).
|
164
|
+
#
|
165
|
+
# @return [Encoding] `Encoding::ASCII_8BIT`.
|
166
|
+
# @raise [IOError] If the `IO` has been closed.
|
167
|
+
def internal_encoding
|
168
|
+
check_closed
|
169
|
+
Encoding::ASCII_8BIT
|
170
|
+
end
|
171
|
+
|
172
|
+
protected
|
173
|
+
|
174
|
+
# The underlying compressed `IO` instance.
|
175
|
+
attr_reader :io
|
176
|
+
|
177
|
+
# Initializes a new {Bzip2::FFI::IO} instance with an underlying
|
178
|
+
# compressed `IO` instance and `options` `Hash`.
|
179
|
+
#
|
180
|
+
# `binmode` is called on `io` if `io` responds to `binmode`.
|
181
|
+
#
|
182
|
+
# A single `:autoclose` option is supported. Set `:autoclose` to true
|
183
|
+
# to close the underlying compressed `IO` instance when {#close} is
|
184
|
+
# called.
|
185
|
+
#
|
186
|
+
# @param io [IO] An `IO`-like object that represents the compressed data.
|
187
|
+
# @param options [Hash] Optional parameters (:autoclose).
|
188
|
+
# @raise [ArgumentError] If `io` is nil.
|
189
|
+
def initialize(io, options = {})
|
190
|
+
raise ArgumentError, 'io is required' unless io
|
191
|
+
|
192
|
+
@io = io
|
193
|
+
@io.binmode if @io.respond_to?(:binmode)
|
194
|
+
|
195
|
+
@autoclose = !!options[:autoclose]
|
196
|
+
|
197
|
+
@stream = Libbz2::BzStream.new
|
198
|
+
end
|
199
|
+
|
200
|
+
# Returns the {Libbz2::BzStream} instance being used to interface with
|
201
|
+
# libbz2.
|
202
|
+
#
|
203
|
+
# @return [Libbz2::BzStream] The {Libbz2::BzStream} instance being used
|
204
|
+
# to interface with libbz2.
|
205
|
+
# @raise [IOError] If the `IO` has been closed.
|
206
|
+
def stream
|
207
|
+
check_closed
|
208
|
+
@stream
|
209
|
+
end
|
210
|
+
|
211
|
+
# Raises an `IOError` if {#close} has been called to close the {IO}.
|
212
|
+
#
|
213
|
+
# @raise [IOError] If the `IO` has been closed.
|
214
|
+
def check_closed
|
215
|
+
raise IOError, 'closed stream' if closed?
|
216
|
+
end
|
217
|
+
|
218
|
+
# Checks a return code from a libbz2 function. If it is greater than or
|
219
|
+
# equal to 0 (success), the return code is returned. If it is less than
|
220
|
+
# zero (an error), the appropriate {Bzip2::Bzip2Error} sub-class is
|
221
|
+
# raised.
|
222
|
+
#
|
223
|
+
# @param res [Integer] The result of a call to a libbz2 function.
|
224
|
+
# @return [Integer] `res` if `res` is greater than or equal to 0.
|
225
|
+
# @raise [Error::Bzip2Error] if `res` is less than 0.
|
226
|
+
def check_error(res)
|
227
|
+
return res if res >= 0
|
228
|
+
|
229
|
+
error_class = case res
|
230
|
+
when Libbz2::BZ_SEQUENCE_ERROR
|
231
|
+
Error::SequenceError
|
232
|
+
when Libbz2::BZ_PARAM_ERROR
|
233
|
+
Error::ParamError
|
234
|
+
when Libbz2::BZ_MEM_ERROR
|
235
|
+
Error::MemoryError
|
236
|
+
when Libbz2::BZ_DATA_ERROR
|
237
|
+
Error::DataError
|
238
|
+
when Libbz2::BZ_DATA_ERROR_MAGIC
|
239
|
+
Error::MagicDataError
|
240
|
+
when Libbz2::BZ_CONFIG_ERROR
|
241
|
+
Error::ConfigError
|
242
|
+
else
|
243
|
+
raise Error::UnexpectedError.new(res)
|
244
|
+
end
|
245
|
+
|
246
|
+
raise error_class.new
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|