archive 0.0.1
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
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/Guardfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +88 -0
- data/Rakefile +32 -0
- data/archive.gemspec +31 -0
- data/lib/archive.rb +79 -0
- data/lib/archive/compress.rb +126 -0
- data/lib/archive/extract.rb +125 -0
- data/lib/archive/libarchive.rb +202 -0
- data/lib/archive/version.rb +3 -0
- data/test.rb +4 -0
- metadata +183 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 03fd0b29dfe98ee0c8545cb831b85d56b62cf0f9
|
4
|
+
data.tar.gz: 7063adf82e2d6338c486670d91a87176d5b28d70
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 180d415f4639a31f50e2f90320e7c3af03bd3bad1495420a234d80064358e9f1f4176ebd76d8508c73b5dd1c4d19757570bd66e498628cd37e9b6699494d399f
|
7
|
+
data.tar.gz: 7d45612e9ee2e07b240a0c0b2008ff2ae7fd291fa7443c0f65a82fe2e716421ca366c4a5a367ca0b19b2fb188247277711460456a12500487dd93d82b68cacc4
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Erik Hollensbe
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Archive
|
2
|
+
|
3
|
+
Archive is a simple and effective way to utilize libarchive, the slicing,
|
4
|
+
dicing, all-in-one BSD archiving toolkit. It keeps it simple by only handling a
|
5
|
+
common subset of archive types:
|
6
|
+
|
7
|
+
* tar
|
8
|
+
* uncompressed
|
9
|
+
* gzipped
|
10
|
+
* bzip2'd
|
11
|
+
* zip (uncompressed, binary-only)
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'archive'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install archive
|
26
|
+
|
27
|
+
You will also need a copy of 'libarchive'. This comes with some operating
|
28
|
+
systems (OS X, mingw builds on windows) and others you have to use your package
|
29
|
+
manager to install them. It is not required to install the gem, but to use it.
|
30
|
+
|
31
|
+
You will also need a compiler to install FFI.
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
# stuff the contents of $HOME in /tmp/tmp.tar.gz
|
37
|
+
Archive.compress("/tmp/tmp.tar.gz", ENV["HOME"])
|
38
|
+
# same thing, only a zip file
|
39
|
+
Archive.compress("/tmp/tmp.zip", ENV["HOME"], :type => :zip)
|
40
|
+
# now bzip2
|
41
|
+
Archive.compress("/tmp/tmp.tar.bz2", ENV["HOME"], :type => :tar, :compression => :bzip2)
|
42
|
+
|
43
|
+
# OOP interface:
|
44
|
+
ac = Archive::Compress.new("/tmp/my_files.tar.gz", :type => :tar, :compression => :gzip)
|
45
|
+
ac.compress(["some", "files"])
|
46
|
+
|
47
|
+
# let's extract those files
|
48
|
+
require 'fileutils'
|
49
|
+
FileUtils.mkdir_p("/tmp/woot")
|
50
|
+
# this works for any kind of archive we support -- no need to express which
|
51
|
+
# type
|
52
|
+
Archive.extract("/tmp/tmp.tar.gz", "/tmp/woot")
|
53
|
+
|
54
|
+
# OOP interface:
|
55
|
+
ae = Archive::Extract.new("/tmp/tmp.tar.gz", "/tmp/woot")
|
56
|
+
ae.extract
|
57
|
+
```
|
58
|
+
|
59
|
+
## Testing
|
60
|
+
|
61
|
+
Tests do not come with the gem because of file sizes of the archives and test
|
62
|
+
data in the repository.
|
63
|
+
|
64
|
+
Tests require bundler. Run `bundle exec rake test` to run the tests.
|
65
|
+
|
66
|
+
We have verified that archive works as intended on these platforms:
|
67
|
+
|
68
|
+
* Mac OS X 10.8
|
69
|
+
* Ubuntu Linux 12.04 LTS
|
70
|
+
|
71
|
+
And these Rubies:
|
72
|
+
|
73
|
+
* 1.9.3
|
74
|
+
* 2.0.0
|
75
|
+
* JRuby 1.7 with Java 7
|
76
|
+
|
77
|
+
Please let us know if your operating system isn't working! It'll likely
|
78
|
+
complain about a call called `stat` which varies wildly on different POSIX
|
79
|
+
systems. We just need to know what platform you're on and how we can install it
|
80
|
+
ourselves to move forward. Thanks!
|
81
|
+
|
82
|
+
## Contributing
|
83
|
+
|
84
|
+
1. Fork it
|
85
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
86
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
87
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
88
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rdoc/task'
|
4
|
+
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs << "test"
|
7
|
+
t.test_files = FileList["test/test_*.rb"]
|
8
|
+
t.verbose = true
|
9
|
+
end
|
10
|
+
|
11
|
+
RDoc::Task.new do |rdoc|
|
12
|
+
rdoc.title = "Simple library to manage tar and zip archives with libarchive and FFI"
|
13
|
+
rdoc.main = "README.md"
|
14
|
+
rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
|
15
|
+
rdoc.rdoc_files -= ["lib/archive/version.rb"]
|
16
|
+
if ENV["RDOC_COVER"]
|
17
|
+
rdoc.options << "-C"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "run tests with coverage report"
|
22
|
+
task "test:coverage" do
|
23
|
+
ENV["COVERAGE"] = "1"
|
24
|
+
Rake::Task["test"].invoke
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "run rdoc with coverage report"
|
28
|
+
task :rdoc_cov do
|
29
|
+
# ugh
|
30
|
+
ENV["RDOC_COVER"] = "1"
|
31
|
+
ruby "-S rake rerdoc"
|
32
|
+
end
|
data/archive.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'archive/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "archive"
|
8
|
+
spec.version = Archive::VERSION
|
9
|
+
spec.authors = ["Erik Hollensbe"]
|
10
|
+
spec.email = ["erik+github@hollensbe.org"]
|
11
|
+
spec.description = %q{Simple library to manage tar and zip archives with libarchive and FFI}
|
12
|
+
spec.summary = %q{Simple library to manage tar and zip archives with libarchive and FFI}
|
13
|
+
spec.homepage = "https://github.com/erikh/archive"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/).reject { |f| File.dirname(f) =~ /^test/ }
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = []
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'ffi', '~> 1.8.1'
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency 'minitest'
|
26
|
+
spec.add_development_dependency 'guard-minitest'
|
27
|
+
spec.add_development_dependency 'guard-rake', '~> 0.0.8'
|
28
|
+
spec.add_development_dependency 'rdoc', '~> 4.0'
|
29
|
+
spec.add_development_dependency 'rb-fsevent'
|
30
|
+
spec.add_development_dependency 'simplecov'
|
31
|
+
end
|
data/lib/archive.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'archive/version'
|
2
|
+
require 'archive/libarchive'
|
3
|
+
require 'archive/extract'
|
4
|
+
require 'archive/compress'
|
5
|
+
|
6
|
+
#
|
7
|
+
# Archive is a small library to leverage the FreeBSD project's libarchive to
|
8
|
+
# unpack tarballs and zip files.
|
9
|
+
#
|
10
|
+
# See the helper methods in this module, or Archive::Extract and
|
11
|
+
# Archive::Compress if you want an OOP interface.
|
12
|
+
#
|
13
|
+
module Archive
|
14
|
+
|
15
|
+
#
|
16
|
+
# Extract a file into a directory. The default directory is the current
|
17
|
+
# directory.
|
18
|
+
#
|
19
|
+
# Format and compression will be auto-detected.
|
20
|
+
#
|
21
|
+
# See Archive::Extract for more information.
|
22
|
+
#
|
23
|
+
def self.extract(filename, dir=Dir.pwd)
|
24
|
+
Archive::Extract.new(filename, dir).extract
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Just like ::extract, but prints the filenames extracted to stdout.
|
29
|
+
#
|
30
|
+
def self.extract_and_print(filename, dir=Dir.pwd)
|
31
|
+
Archive::Extract.new(filename, dir).extract(true)
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Compress a directory's files into the filename provided. The default
|
36
|
+
# directory is the current directory.
|
37
|
+
#
|
38
|
+
# args is a hash that contains two values, :type and :compression.
|
39
|
+
#
|
40
|
+
# * :type may be :tar or :zip
|
41
|
+
# * :compression may be :gzip, :bzip2, or nil (no compression)
|
42
|
+
#
|
43
|
+
# If the type :zip is selected, no compression will be used. Additionally,
|
44
|
+
# files in the .zip will all be stored as binary files.
|
45
|
+
#
|
46
|
+
# No files in your directory that are not *real files* will be added to the
|
47
|
+
# archive.
|
48
|
+
#
|
49
|
+
# See Archive::Compress for more information, and a way to compress just the
|
50
|
+
# files you want.
|
51
|
+
#
|
52
|
+
def self.compress(filename, dir=Dir.pwd, args={ :type => :tar, :compression => :gzip })
|
53
|
+
Archive::Compress.new(filename, args).compress(get_files(dir))
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Similar to ::compress, but outputs the files it's compressing.
|
58
|
+
#
|
59
|
+
def self.compress_and_print(filename, dir=Dir.pwd, args={ :type => :tar, :compression => :gzip })
|
60
|
+
Archive::Compress.new(filename, args).compress(get_files(dir), true)
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
#
|
66
|
+
# Finds the files for the dir passed to ::compress.
|
67
|
+
#
|
68
|
+
def self.get_files(dir)
|
69
|
+
require 'find'
|
70
|
+
|
71
|
+
files = []
|
72
|
+
|
73
|
+
Find.find(dir) do |path|
|
74
|
+
files.push(path) if File.file?(path)
|
75
|
+
end
|
76
|
+
|
77
|
+
return files
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Archive # :nodoc:
|
4
|
+
#
|
5
|
+
# Compression OOP interface for Archive. See ::new and #compress for more information.
|
6
|
+
#
|
7
|
+
class Compress
|
8
|
+
# the filename of the compressed archive
|
9
|
+
attr_reader :filename
|
10
|
+
# the type of the archive. See ::new.
|
11
|
+
attr_reader :type
|
12
|
+
# the compression type of the archive. See ::new.
|
13
|
+
attr_reader :compression
|
14
|
+
|
15
|
+
# The buffer size for reading content.
|
16
|
+
BUFSIZE = 32767
|
17
|
+
|
18
|
+
#
|
19
|
+
# Create a new Compress object. Takes a filename as string, and args as
|
20
|
+
# hash.
|
21
|
+
#
|
22
|
+
# args is a hash that contains two values, :type and :compression.
|
23
|
+
#
|
24
|
+
# * :type may be :tar or :zip
|
25
|
+
# * :compression may be :gzip, :bzip2, or nil (no compression)
|
26
|
+
#
|
27
|
+
# If the type :zip is selected, no compression will be used. Additionally,
|
28
|
+
# files in the .zip will all be stored as binary files.
|
29
|
+
#
|
30
|
+
# The default set of arguments is
|
31
|
+
#
|
32
|
+
# { :type => :tar, :compression => :gzip }
|
33
|
+
#
|
34
|
+
def initialize(filename, args={ :type => :tar, :compression => :gzip })
|
35
|
+
@filename = filename
|
36
|
+
@type = args[:type] || :tar
|
37
|
+
@compression = args[:compression]
|
38
|
+
|
39
|
+
if type == :zip
|
40
|
+
@compression = nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Run the compression. Files are an array of filenames. Optional flag for
|
46
|
+
# verbosity; if true, will print each file it adds to the archive to
|
47
|
+
# stdout.
|
48
|
+
#
|
49
|
+
# Files must be real files. No symlinks, directories, unix sockets,
|
50
|
+
# character devices, etc. This method will raise ArgumentError if you
|
51
|
+
# provide any.
|
52
|
+
#
|
53
|
+
def compress(files, verbose=false)
|
54
|
+
if files.any? { |f| !File.file?(f) }
|
55
|
+
raise ArgumentError, "Files supplied must all be real, actual files -- not directories or symlinks."
|
56
|
+
end
|
57
|
+
|
58
|
+
configure_archive
|
59
|
+
compress_files(files, verbose)
|
60
|
+
free_archive
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def configure_archive # :nodoc:
|
66
|
+
@archive = LibArchive.archive_write_new
|
67
|
+
LibArchive.enable_output_compression(@archive, @compression)
|
68
|
+
LibArchive.enable_output_archive(@archive, @type)
|
69
|
+
|
70
|
+
result = LibArchive.archive_write_open_filename(@archive, @filename)
|
71
|
+
if result != LibArchive::ARCHIVE_OK
|
72
|
+
raise LibArchive.archive_error_string(@archive)
|
73
|
+
end
|
74
|
+
|
75
|
+
@disk = LibArchive.archive_read_disk_new
|
76
|
+
LibArchive.archive_read_disk_set_standard_lookup(@disk)
|
77
|
+
end
|
78
|
+
|
79
|
+
def compress_files(files, verbose) # :nodoc:
|
80
|
+
buff = FFI::Buffer.new BUFSIZE
|
81
|
+
|
82
|
+
# truncate our archive, this solves a few issues.
|
83
|
+
File.open(filename, 'w').close
|
84
|
+
|
85
|
+
files.reject { |f| Pathname.new(f).realpath == Pathname.new(filename).realpath }.each do |file|
|
86
|
+
# TODO return value maybe?
|
87
|
+
puts file if verbose
|
88
|
+
|
89
|
+
stat = FFI::MemoryPointer.new Stat, 1, true
|
90
|
+
entry = LibArchive.archive_entry_new
|
91
|
+
|
92
|
+
LibArchive.archive_entry_set_pathname(entry, file)
|
93
|
+
result = LibArchive.stat(File.join(Dir.pwd, file), stat)
|
94
|
+
|
95
|
+
if result != 0
|
96
|
+
raise "Error while calling stat(): #{LibArchive.strerror(FFI.errno)}"
|
97
|
+
end
|
98
|
+
|
99
|
+
LibArchive.archive_read_disk_entry_from_file(@disk, entry, -1, stat)
|
100
|
+
|
101
|
+
result = LibArchive.archive_write_header(@archive, entry)
|
102
|
+
|
103
|
+
if result != LibArchive::ARCHIVE_OK
|
104
|
+
raise "archive error: #{LibArchive.archive_error_string(@archive)}"
|
105
|
+
end
|
106
|
+
|
107
|
+
File.open(file, 'r') do |f|
|
108
|
+
loop do
|
109
|
+
len = FFI::IO.native_read(f, buff, BUFSIZE)
|
110
|
+
LibArchive.archive_write_data(@archive, buff, len)
|
111
|
+
break if f.eof?
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
LibArchive.archive_entry_free(entry)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def free_archive # :nodoc:
|
120
|
+
LibArchive.archive_read_close(@disk)
|
121
|
+
LibArchive.archive_read_free(@disk)
|
122
|
+
LibArchive.archive_write_close(@archive)
|
123
|
+
LibArchive.archive_write_free(@archive)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Archive # :nodoc:
|
2
|
+
#
|
3
|
+
# Extraction OOP interface for Archive. See ::new and #extract for more information.
|
4
|
+
#
|
5
|
+
class Extract
|
6
|
+
|
7
|
+
# The filename of the compressed archive. See ::new.
|
8
|
+
attr_reader :filename
|
9
|
+
# The extraction directory target. See ::new.
|
10
|
+
attr_reader :dir
|
11
|
+
|
12
|
+
#
|
13
|
+
# Create a new Extract object. Takes a filename as string containing the
|
14
|
+
# archive name, and a directory name as string containing the target path
|
15
|
+
# to extract to. The default target is the current directory.
|
16
|
+
#
|
17
|
+
# If either the filename or directory name do not already exist,
|
18
|
+
# ArgumentError will be raised.
|
19
|
+
#
|
20
|
+
# Extraction tries to preserve timestamps and permissions, but not uid/gid.
|
21
|
+
# Note that this is format-dependent -- e.g., .zip files will always be
|
22
|
+
# extracted as mode 0777.
|
23
|
+
#
|
24
|
+
def initialize(filename, dir=Dir.pwd)
|
25
|
+
unless File.exist?(filename)
|
26
|
+
raise ArgumentError, "File '#{filename}' does not exist!"
|
27
|
+
end
|
28
|
+
|
29
|
+
unless File.directory?(dir)
|
30
|
+
raise ArgumentError, "Directory '#{dir}' does not exist!"
|
31
|
+
end
|
32
|
+
|
33
|
+
@filename = filename
|
34
|
+
@dir = dir
|
35
|
+
|
36
|
+
@extract_flags =
|
37
|
+
LibArchive::ARCHIVE_EXTRACT_PERM |
|
38
|
+
LibArchive::ARCHIVE_EXTRACT_TIME
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Perform the extraction. Takes an optional value that when true prints
|
43
|
+
# each filename extracted to stdout.
|
44
|
+
#
|
45
|
+
def extract(verbose=false)
|
46
|
+
create_io_objects
|
47
|
+
open_file
|
48
|
+
|
49
|
+
header_loop(verbose)
|
50
|
+
|
51
|
+
close
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def create_io_objects # :nodoc:
|
57
|
+
@in = LibArchive.archive_read_new
|
58
|
+
@out = LibArchive.archive_write_disk_new
|
59
|
+
LibArchive.archive_write_disk_set_options(@out, @extract_flags)
|
60
|
+
@entry = FFI::MemoryPointer.new :pointer
|
61
|
+
LibArchive.enable_input_formats(@in)
|
62
|
+
end
|
63
|
+
|
64
|
+
def open_file # :nodoc:
|
65
|
+
LibArchive.archive_read_open_filename(@in, @filename, 10240)
|
66
|
+
end
|
67
|
+
|
68
|
+
def header_loop(verbose) # :nodoc:
|
69
|
+
while ((result = LibArchive.archive_read_next_header(@in, @entry)) != LibArchive::ARCHIVE_EOF)
|
70
|
+
|
71
|
+
if result != LibArchive::ARCHIVE_OK
|
72
|
+
raise LibArchive.archive_error_string(@in)
|
73
|
+
end
|
74
|
+
|
75
|
+
entry_pointer = @entry.get_pointer(0)
|
76
|
+
|
77
|
+
full_path = File.join(@dir, LibArchive.archive_entry_pathname(entry_pointer))
|
78
|
+
LibArchive.archive_entry_set_pathname(entry_pointer, full_path)
|
79
|
+
|
80
|
+
# TODO return value maybe?
|
81
|
+
puts LibArchive.archive_entry_pathname(entry_pointer) if verbose
|
82
|
+
|
83
|
+
if ((result = LibArchive.archive_write_header(@out, entry_pointer)) != LibArchive::ARCHIVE_OK)
|
84
|
+
raise LibArchive.archive_error_string(@out)
|
85
|
+
end
|
86
|
+
|
87
|
+
unpack_loop
|
88
|
+
|
89
|
+
LibArchive.archive_write_finish_entry(@out)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def unpack_loop # :nodoc:
|
94
|
+
loop do
|
95
|
+
buffer = FFI::MemoryPointer.new :pointer, 1
|
96
|
+
size = FFI::MemoryPointer.new :size_t
|
97
|
+
offset = FFI::MemoryPointer.new :long_long
|
98
|
+
|
99
|
+
result = LibArchive.archive_read_data_block(@in, buffer, size, offset)
|
100
|
+
|
101
|
+
break if result == LibArchive::ARCHIVE_EOF
|
102
|
+
|
103
|
+
unless result == LibArchive::ARCHIVE_OK
|
104
|
+
raise LibArchive.archive_error_string(@in)
|
105
|
+
end
|
106
|
+
|
107
|
+
result = LibArchive.archive_write_data_block(@out, buffer.get_pointer(0), size.read_ulong_long, offset.read_long_long);
|
108
|
+
|
109
|
+
if result != LibArchive::ARCHIVE_OK
|
110
|
+
raise LibArchive.archive_error_string(@out)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def close # :nodoc:
|
116
|
+
LibArchive.archive_read_close(@in)
|
117
|
+
LibArchive.archive_read_free(@in)
|
118
|
+
LibArchive.archive_write_close(@out)
|
119
|
+
LibArchive.archive_write_free(@out)
|
120
|
+
@in = nil
|
121
|
+
@out = nil
|
122
|
+
@entry = nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module Archive # :nodoc:
|
4
|
+
class Stat < ::FFI::Struct # :nodoc:
|
5
|
+
#--
|
6
|
+
# this is necessary to pass rdoc's coverage tests
|
7
|
+
#++
|
8
|
+
module FFI # :nodoc:
|
9
|
+
end
|
10
|
+
class Timespec < ::FFI::Struct
|
11
|
+
layout :tv_sec, :long,
|
12
|
+
:tv_nsec, :long
|
13
|
+
end
|
14
|
+
|
15
|
+
if ::FFI::Platform.mac?
|
16
|
+
layout(
|
17
|
+
:st_dev, :dev_t,
|
18
|
+
:st_mode, :mode_t,
|
19
|
+
:st_nlink, :nlink_t,
|
20
|
+
:st_ino, :ino_t,
|
21
|
+
:st_uid, :uid_t,
|
22
|
+
:st_gid, :gid_t,
|
23
|
+
:st_rdev, :dev_t,
|
24
|
+
:st_atimespec, Timespec,
|
25
|
+
:st_mtimespec, Timespec,
|
26
|
+
:st_ctimespec, Timespec,
|
27
|
+
:st_birthtimespec, Timespec,
|
28
|
+
:st_size, :off_t,
|
29
|
+
:st_blocks, :quad_t,
|
30
|
+
:st_blksize, :ulong,
|
31
|
+
:st_flags, :ulong,
|
32
|
+
:st_gen, :ulong,
|
33
|
+
:st_lspare, :long,
|
34
|
+
:st_qspare, :long_long
|
35
|
+
)
|
36
|
+
else
|
37
|
+
layout(
|
38
|
+
:st_dev, :dev_t,
|
39
|
+
:st_ino, :ino_t,
|
40
|
+
:st_mode, :mode_t,
|
41
|
+
:st_nlink, :nlink_t,
|
42
|
+
:st_uid, :uid_t,
|
43
|
+
:st_gid, :gid_t,
|
44
|
+
:st_rdev, :dev_t,
|
45
|
+
:st_atimespec, Timespec,
|
46
|
+
:st_mtimespec, Timespec,
|
47
|
+
:st_ctimespec, Timespec,
|
48
|
+
:st_size, :off_t,
|
49
|
+
:st_blocks, :quad_t,
|
50
|
+
:st_blksize, :ulong,
|
51
|
+
:st_flags, :ulong,
|
52
|
+
:st_gen, :ulong
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
module LibArchive # :nodoc:
|
57
|
+
#--
|
58
|
+
# this is necessary to pass rdoc's coverage tests
|
59
|
+
#++
|
60
|
+
module FFI # :nodoc:
|
61
|
+
module Library # :nodoc:
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
extend ::FFI::Library # :nodoc:
|
66
|
+
|
67
|
+
#
|
68
|
+
# needed by archiving entry functions
|
69
|
+
#
|
70
|
+
ffi_lib ::FFI::Library::LIBC
|
71
|
+
|
72
|
+
attach_function :strerror, [:int], :string
|
73
|
+
|
74
|
+
if ::FFI::Platform.mac?
|
75
|
+
attach_function :stat64, [:string, :pointer], :int
|
76
|
+
def self.stat(*args) # :nodoc:
|
77
|
+
stat64(*args)
|
78
|
+
end
|
79
|
+
elsif RbConfig::CONFIG['host_os'] =~ /linux/
|
80
|
+
attach_function :__xstat, [:int, :string, :pointer], :int
|
81
|
+
def self.stat(*args) # :nodoc:
|
82
|
+
__xstat(0, *args)
|
83
|
+
end
|
84
|
+
else
|
85
|
+
attach_function :stat, [:string, :pointer], :int
|
86
|
+
end
|
87
|
+
|
88
|
+
ffi_lib 'archive'
|
89
|
+
|
90
|
+
ARCHIVE_OK = 0 # :nodoc:
|
91
|
+
ARCHIVE_EOF = 1 # :nodoc:
|
92
|
+
ARCHIVE_RETRY = -10 # :nodoc:
|
93
|
+
ARCHIVE_WARN = -20 # :nodoc:
|
94
|
+
ARCHIVE_FAILED = -25 # :nodoc:
|
95
|
+
ARCHIVE_FATAL = -30 # :nodoc:
|
96
|
+
|
97
|
+
ARCHIVE_EXTRACT_OWNER = 0x0001 # :nodoc:
|
98
|
+
ARCHIVE_EXTRACT_PERM = 0x0002 # :nodoc:
|
99
|
+
ARCHIVE_EXTRACT_TIME = 0x0004 # :nodoc:
|
100
|
+
|
101
|
+
attach_function :archive_read_new, [], :pointer
|
102
|
+
attach_function :archive_read_disk_new, [], :pointer
|
103
|
+
attach_function :archive_read_disk_set_standard_lookup, [:pointer], :void
|
104
|
+
attach_function :archive_write_new, [], :pointer
|
105
|
+
attach_function :archive_write_disk_new, [], :pointer
|
106
|
+
attach_function :archive_write_disk_set_options, [:pointer, :int], :void
|
107
|
+
attach_function :archive_read_support_format_tar, [:pointer], :void
|
108
|
+
attach_function :archive_read_support_format_gnutar, [:pointer], :void
|
109
|
+
attach_function :archive_read_support_format_zip, [:pointer], :void
|
110
|
+
|
111
|
+
def self.enable_input_formats(arg) # :nodoc:
|
112
|
+
archive_read_support_format_gnutar(arg)
|
113
|
+
archive_read_support_format_zip(arg)
|
114
|
+
archive_read_support_format_zip(arg)
|
115
|
+
enable_input_compression(arg)
|
116
|
+
end
|
117
|
+
|
118
|
+
attach_function :archive_write_set_format_ustar, [:pointer], :void
|
119
|
+
attach_function :archive_write_set_format_zip, [:pointer], :void
|
120
|
+
|
121
|
+
def self.enable_output_archive(arg, type=:tar)
|
122
|
+
case type
|
123
|
+
when :tar
|
124
|
+
archive_write_set_format_ustar(arg)
|
125
|
+
when :zip
|
126
|
+
archive_write_set_format_zip(arg)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
begin
|
131
|
+
attach_function :archive_read_support_filter_all, [:pointer], :void
|
132
|
+
def self.enable_input_compression(arg) # :nodoc:
|
133
|
+
archive_read_support_filter_all(arg)
|
134
|
+
end
|
135
|
+
rescue ::FFI::NotFoundError
|
136
|
+
attach_function :archive_read_support_compression_all, [:pointer], :void
|
137
|
+
def self.enable_input_compression(arg) # :nodoc:
|
138
|
+
archive_read_support_compression_all(arg)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
begin
|
143
|
+
attach_function :archive_write_add_filter_gzip, [:pointer], :void
|
144
|
+
attach_function :archive_write_add_filter_bzip2, [:pointer], :void
|
145
|
+
def self.enable_output_compression(arg, type=:gzip) # :nodoc:
|
146
|
+
case type
|
147
|
+
when :gzip
|
148
|
+
archive_write_add_filter_gzip(arg)
|
149
|
+
when :bzip2
|
150
|
+
archive_write_add_filter_bzip2(arg)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
rescue ::FFI::NotFoundError
|
154
|
+
attach_function :archive_write_set_compression_gzip, [:pointer], :void
|
155
|
+
attach_function :archive_write_set_compression_bzip2, [:pointer], :void
|
156
|
+
def self.enable_output_compression(arg, type=:gzip) # :nodoc:
|
157
|
+
case type
|
158
|
+
when :gzip
|
159
|
+
archive_write_set_compression_gzip(arg)
|
160
|
+
when :bzip2
|
161
|
+
archive_write_set_compression_bzip2(arg)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
attach_function :archive_read_open_filename, [:pointer, :string, :size_t], :int
|
167
|
+
attach_function :archive_write_open_filename, [:pointer, :string], :int
|
168
|
+
|
169
|
+
attach_function :archive_entry_new, [], :pointer
|
170
|
+
attach_function :archive_entry_free, [:pointer], :void
|
171
|
+
attach_function :archive_read_disk_entry_from_file, [:pointer, :pointer, :int, :pointer], :int
|
172
|
+
|
173
|
+
attach_function :archive_error_string, [:pointer], :string
|
174
|
+
attach_function :archive_read_next_header, [:pointer, :pointer], :int
|
175
|
+
attach_function :archive_write_header, [:pointer, :pointer], :int
|
176
|
+
attach_function :archive_write_finish_entry, [:pointer], :int
|
177
|
+
attach_function :archive_read_close, [:pointer], :void
|
178
|
+
attach_function :archive_write_close, [:pointer], :void
|
179
|
+
|
180
|
+
begin
|
181
|
+
attach_function :archive_read_free, [:pointer], :void
|
182
|
+
attach_function :archive_write_free, [:pointer], :void
|
183
|
+
rescue ::FFI::NotFoundError
|
184
|
+
attach_function :archive_read_finish, [:pointer], :void
|
185
|
+
def self.archive_read_free(arg) # :nodoc:
|
186
|
+
archive_read_finish(arg)
|
187
|
+
end
|
188
|
+
|
189
|
+
attach_function :archive_write_finish, [:pointer], :void
|
190
|
+
def self.archive_write_free(arg) # :nodoc:
|
191
|
+
archive_write_finish(arg)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
attach_function :archive_read_data_block, [:pointer, :pointer, :pointer, :pointer], :int
|
196
|
+
attach_function :archive_write_data_block, [:pointer, :pointer, :size_t, :long_long], :int
|
197
|
+
attach_function :archive_write_data, [:pointer, :pointer, :size_t], :void
|
198
|
+
|
199
|
+
attach_function :archive_entry_pathname, [:pointer], :string
|
200
|
+
attach_function :archive_entry_set_pathname, [:pointer, :string], :void
|
201
|
+
end
|
202
|
+
end
|
data/test.rb
ADDED
metadata
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: archive
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Erik Hollensbe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-04-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.8.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.8.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard-rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.0.8
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.0.8
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rdoc
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '4.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '4.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rb-fsevent
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: Simple library to manage tar and zip archives with libarchive and FFI
|
140
|
+
email:
|
141
|
+
- erik+github@hollensbe.org
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- .gitignore
|
147
|
+
- Gemfile
|
148
|
+
- Guardfile
|
149
|
+
- LICENSE.txt
|
150
|
+
- README.md
|
151
|
+
- Rakefile
|
152
|
+
- archive.gemspec
|
153
|
+
- lib/archive.rb
|
154
|
+
- lib/archive/compress.rb
|
155
|
+
- lib/archive/extract.rb
|
156
|
+
- lib/archive/libarchive.rb
|
157
|
+
- lib/archive/version.rb
|
158
|
+
- test.rb
|
159
|
+
homepage: https://github.com/erikh/archive
|
160
|
+
licenses:
|
161
|
+
- MIT
|
162
|
+
metadata: {}
|
163
|
+
post_install_message:
|
164
|
+
rdoc_options: []
|
165
|
+
require_paths:
|
166
|
+
- lib
|
167
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - '>='
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - '>='
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: '0'
|
177
|
+
requirements: []
|
178
|
+
rubyforge_project:
|
179
|
+
rubygems_version: 2.0.3
|
180
|
+
signing_key:
|
181
|
+
specification_version: 4
|
182
|
+
summary: Simple library to manage tar and zip archives with libarchive and FFI
|
183
|
+
test_files: []
|