multi_zip 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.travis.yml +17 -0
- data/BACKEND_CONSTANTS.md +26 -0
- data/Gemfile +19 -0
- data/Guardfile +77 -0
- data/LICENSE.txt +22 -0
- data/README.md +294 -0
- data/Rakefile +11 -0
- data/lib/multi_zip/backend/archive_zip.rb +111 -0
- data/lib/multi_zip/backend/rubyzip.rb +73 -0
- data/lib/multi_zip/backend/zipruby.rb +94 -0
- data/lib/multi_zip/errors.rb +35 -0
- data/lib/multi_zip/version.rb +3 -0
- data/lib/multi_zip.rb +195 -0
- data/multi_zip.gemspec +24 -0
- data/spec/backend_shared_example.rb +487 -0
- data/spec/fixtures/invalid.zip +1 -0
- data/spec/fixtures/test.zip +0 -0
- data/spec/lib/multi_zip/backend/archive_zip_spec.rb +6 -0
- data/spec/lib/multi_zip/backend/rubyzip_spec.rb +8 -0
- data/spec/lib/multi_zip/backend/zipruby_spec.rb +8 -0
- data/spec/lib/multi_zip_spec.rb +53 -0
- data/spec/spec_helper.rb +142 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1e4e927a532bd99e7fc59c1a6f84da9bb8369694
|
4
|
+
data.tar.gz: 753a83ee20f222d33b04e29f4edd131062976ada
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 11ba062b2125374d543c50f05c48045d51e5b62fc01ed0f8348a26a1b65159b9f40fa2702ad61600a07e3a75fab5fdbaf11815b4cf595edaf87984ab7dd8ee2b
|
7
|
+
data.tar.gz: 3a261d116078bc97320ff789ef2eeb7052dfe1d407c911036a462069535fb9a713420b65bb148c75c4f9644d0d0a66ff394360d066b2e102590c10f5255a5f78
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# rubyzip
|
2
|
+
|
3
|
+
```ruby
|
4
|
+
> require 'zip'
|
5
|
+
=> true
|
6
|
+
> Zip.constants
|
7
|
+
=> [:DOSTime, :IOExtras, :Entry, :ExtraField, :EntrySet, :CentralDirectory, :File, :InputStream, :OutputStream, :Decompressor, :Compressor, :NullDecompressor, :NullCompressor, :NullInputStream, :PassThruCompressor, :PassThruDecompressor, :Inflater, :Deflater, :StreamableStream, :StreamableDirectory, :RUNNING_ON_WINDOWS, :CENTRAL_DIRECTORY_ENTRY_SIGNATURE, :CDIR_ENTRY_STATIC_HEADER_LENGTH, :LOCAL_ENTRY_SIGNATURE, :LOCAL_ENTRY_STATIC_HEADER_LENGTH, :LOCAL_ENTRY_TRAILING_DESCRIPTOR_LENGTH, :VERSION_MADE_BY, :VERSION_NEEDED_TO_EXTRACT, :VERSION_NEEDED_TO_EXTRACT_ZIP64, :FILE_TYPE_FILE, :FILE_TYPE_DIR, :FILE_TYPE_SYMLINK, :FSTYPE_FAT, :FSTYPE_AMIGA, :FSTYPE_VMS, :FSTYPE_UNIX, :FSTYPE_VM_CMS, :FSTYPE_ATARI, :FSTYPE_HPFS, :FSTYPE_MAC, :FSTYPE_Z_SYSTEM, :FSTYPE_CPM, :FSTYPE_TOPS20, :FSTYPE_NTFS, :FSTYPE_QDOS, :FSTYPE_ACORN, :FSTYPE_VFAT, :FSTYPE_MVS, :FSTYPE_BEOS, :FSTYPE_TANDEM, :FSTYPE_THEOS, :FSTYPE_MAC_OSX, :FSTYPE_ATHEOS, :FSTYPES, :Error, :EntryExistsError, :DestinationFileExistsError, :CompressionMethodError, :EntryNameError, :InternalError, :ZipError, :ZipEntryExistsError, :ZipDestinationFileExistsError, :ZipCompressionMethodError, :ZipEntryNameError, :ZipInternalError]
|
8
|
+
```
|
9
|
+
|
10
|
+
# zipruby
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
> require 'zipruby'
|
14
|
+
=> true
|
15
|
+
> Zip.constants
|
16
|
+
=> [:VERSION, :CREATE, :EXCL, :CHECKCONS, :TRUNC, :FL_NOCASE, :FL_NODIR, :FL_COMPRESSED, :FL_UNCHANGED, :CM_DEFAULT, :CM_STORE, :CM_SHRINK, :CM_REDUCE_1, :CM_REDUCE_2, :CM_REDUCE_3, :CM_REDUCE_4, :CM_IMPLODE, :CM_DEFLATE, :CM_DEFLATE64, :CM_PKWARE_IMPLODE, :CM_BZIP2, :EM_NONE, :EM_TRAD_PKWARE, :NO_COMPRESSION, :BEST_SPEED, :BEST_COMPRESSION, :DEFAULT_COMPRESSION, :Archive, :File, :Stat, :Error]
|
17
|
+
```
|
18
|
+
|
19
|
+
# archive/zip
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
> require 'archive/zip'
|
23
|
+
=> true
|
24
|
+
> Archive::Zip.constants
|
25
|
+
=> [:Codec, :DataDescriptor, :Error, :EntryError, :ExtraFieldError, :IOError, :UnzipError, :ExtraField, :Entry, :EOCD_SIGNATURE, :DS_SIGNATURE, :Z64EOCD_SIGNATURE, :Z64EOCDL_SIGNATURE, :CFH_SIGNATURE, :LFH_SIGNATURE, :DD_SIGNATURE]
|
26
|
+
```
|
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
# can't use pry on old rubies or on rubinius
|
6
|
+
if RUBY_VERSION.to_f >= 2.0 && RUBY_ENGINE == 'ruby'
|
7
|
+
gem 'pry', :require => false
|
8
|
+
gem 'pry-byebug', :require => false
|
9
|
+
gem 'guard-rspec', :require => false
|
10
|
+
end
|
11
|
+
|
12
|
+
# rubyzip requires ruby >= 1.9.2
|
13
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("1.9.2")
|
14
|
+
gem 'rubyzip', '~> 1.1.6', :require => nil, :platforms => :ruby
|
15
|
+
gem 'zip-zip', '~>0.3', :require => nil, :platforms => :ruby
|
16
|
+
end
|
17
|
+
|
18
|
+
gem 'zipruby', '0.3.6', :require => nil, :platforms => :ruby
|
19
|
+
gem 'archive-zip', '~> 0.7.0', :require => nil
|
data/Guardfile
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features)
|
6
|
+
|
7
|
+
## Uncomment to clear the screen before every task
|
8
|
+
# clearing :on
|
9
|
+
|
10
|
+
## Guard internally checks for changes in the Guardfile and exits.
|
11
|
+
## If you want Guard to automatically start up again, run guard in a
|
12
|
+
## shell loop, e.g.:
|
13
|
+
##
|
14
|
+
## $ while bundle exec guard; do echo "Restarting Guard..."; done
|
15
|
+
##
|
16
|
+
## Note: if you are using the `directories` clause above and you are not
|
17
|
+
## watching the project directory ('.'), then you will want to move
|
18
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
19
|
+
#
|
20
|
+
# $ mkdir config
|
21
|
+
# $ mv Guardfile config/
|
22
|
+
# $ ln -s config/Guardfile .
|
23
|
+
#
|
24
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
25
|
+
|
26
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
27
|
+
# rspec may be run, below are examples of the most common uses.
|
28
|
+
# * bundler: 'bundle exec rspec'
|
29
|
+
# * bundler binstubs: 'bin/rspec'
|
30
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
31
|
+
# installed the spring binstubs per the docs)
|
32
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
33
|
+
# * 'just' rspec: 'rspec'
|
34
|
+
|
35
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
36
|
+
require "guard/rspec/dsl"
|
37
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
38
|
+
|
39
|
+
# Feel free to open issues for suggestions and improvements
|
40
|
+
|
41
|
+
# RSpec files
|
42
|
+
rspec = dsl.rspec
|
43
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
44
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
45
|
+
watch(rspec.spec_files)
|
46
|
+
|
47
|
+
# Ruby files
|
48
|
+
ruby = dsl.ruby
|
49
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
50
|
+
|
51
|
+
# Rails files
|
52
|
+
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
53
|
+
dsl.watch_spec_files_for(rails.app_files)
|
54
|
+
dsl.watch_spec_files_for(rails.views)
|
55
|
+
|
56
|
+
watch(rails.controllers) do |m|
|
57
|
+
[
|
58
|
+
rspec.spec.("routing/#{m[1]}_routing"),
|
59
|
+
rspec.spec.("controllers/#{m[1]}_controller"),
|
60
|
+
rspec.spec.("acceptance/#{m[1]}")
|
61
|
+
]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Rails config changes
|
65
|
+
watch(rails.spec_helper) { rspec.spec_dir }
|
66
|
+
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
67
|
+
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
68
|
+
|
69
|
+
# Capybara features specs
|
70
|
+
watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
|
71
|
+
|
72
|
+
# Turnip features and steps
|
73
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
74
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
75
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
76
|
+
end
|
77
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Matthew Nielsen
|
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,294 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/xunker/multi_zip.png?branch=master)](https://travis-ci.org/xunker/multi_zip)
|
2
|
+
# [MultiZip](https://github.com/xunker/multi_zip)
|
3
|
+
|
4
|
+
MultiZip is a Ruby Gem that abstracts other zipping/unzipping gems. It
|
5
|
+
automatically detects what gems are available and provides a consistent
|
6
|
+
interface regardless of which is being used. This allows for code that is more
|
7
|
+
portable and helps to avoid namespace collisions (zipruby vs. rubyzip for example)
|
8
|
+
and other implementation restrictions (MRI vs. Jruby, Unix vs. Windows, etc,).
|
9
|
+
|
10
|
+
It currently supports `.zip` archives only. See TODO for info on others.
|
11
|
+
|
12
|
+
MultiZip provides a very small and focused set of functions:
|
13
|
+
|
14
|
+
* Create a new zip archive or open existing one.
|
15
|
+
* Add files to a archive from using content from a variable.
|
16
|
+
* Read files from a archive in to a variable.
|
17
|
+
* Extract files from an archive to a local file.
|
18
|
+
* List files contained in an archive.
|
19
|
+
* Delete files from an archive.
|
20
|
+
* Get information for a file in an archive. (Pending TODO)
|
21
|
+
* Add a local file to an archive. (Pending TODO).
|
22
|
+
|
23
|
+
It is meant for most common zip/unzip tasks. For anything more
|
24
|
+
complicated than these basics, you should use a specific (un)zipping library
|
25
|
+
instead.
|
26
|
+
|
27
|
+
Rubies supported (see [CI status](https://travis-ci.org/xunker/multi_zip) for more detail):
|
28
|
+
* MRI 2.x.x, 1.9.3, 1.8.7 and REE.
|
29
|
+
* Jruby
|
30
|
+
* Rubinius 2
|
31
|
+
|
32
|
+
For information about which backend gems work in which ruby, see [Supported Backend Gems](#supported-backend-gems).
|
33
|
+
|
34
|
+
This work is inspired by [multi_json](https://github.com/intridea/multi_json)
|
35
|
+
and [multi_xml](https://github.com/sferik/multi_xml). Work began while I was
|
36
|
+
visiting Japan in 2014 and is dedicated to the Rubyists of
|
37
|
+
[Asukusa.rb](https://asakusarb.doorkeeper.jp/) and
|
38
|
+
[John Mettraux](https://twitter.com/jmettraux).
|
39
|
+
|
40
|
+
## Installation
|
41
|
+
|
42
|
+
Do the standard dance: Either add `gem 'multi_zip'` to your Gemfile or run
|
43
|
+
`gem install multi_zip`.
|
44
|
+
|
45
|
+
__IMPORTANT NEXT STEP:__ You will also need a zip backend gem installed and
|
46
|
+
required. See `Supported Backends` for of which ones can be used.
|
47
|
+
|
48
|
+
## Getting started
|
49
|
+
|
50
|
+
`multi_zip` will try to use the available gem backends in the following order:
|
51
|
+
|
52
|
+
* rubyzip
|
53
|
+
* archive/zip
|
54
|
+
* zipruby
|
55
|
+
|
56
|
+
If no usable backends are loaded a `MultiZip::NoSupportedBackendError` will be
|
57
|
+
raised for any operation.
|
58
|
+
|
59
|
+
If you have multiple gems available and want to choose your backend, you can
|
60
|
+
do that in the initializer:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
zip = MultiZip.new(filename, backend: :rubyzip) }
|
64
|
+
```
|
65
|
+
|
66
|
+
..or by calling `#backend=` on a MultiZip instance:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
zip = MultiZip.new(filename)
|
70
|
+
zip.backend = :rubyzip
|
71
|
+
```
|
72
|
+
|
73
|
+
You can see what backends are supported:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
> MultiZip.supported_backends
|
77
|
+
=> [:rubyzip, :archive_zip, :zipruby]
|
78
|
+
```
|
79
|
+
|
80
|
+
You can also check which of these supported backends is currently available:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
> MultiZip.available_backends
|
84
|
+
=> []
|
85
|
+
> require 'archive/zip'
|
86
|
+
=> true
|
87
|
+
> MultiZip.available_backends
|
88
|
+
=> [:archive_zip]
|
89
|
+
> require 'zip'
|
90
|
+
=> true
|
91
|
+
> MultiZip.available_backends
|
92
|
+
=> [:rubyzip, :archive_zip]
|
93
|
+
```
|
94
|
+
|
95
|
+
### Examples
|
96
|
+
|
97
|
+
For all the examples below, assume this:
|
98
|
+
```ruby
|
99
|
+
zip = MultiZip.new('/path/to/archive.zip')
|
100
|
+
```
|
101
|
+
|
102
|
+
#### List files in a zip archive
|
103
|
+
|
104
|
+
Response array of file names within the archive.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
zip.list_members
|
108
|
+
# => [
|
109
|
+
# '/path/inside/archive/to/file_1.txt',
|
110
|
+
# '/path/inside/archive/to/file_2.txt'
|
111
|
+
# ]
|
112
|
+
```
|
113
|
+
|
114
|
+
#### Read file from zip archive
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
file = zip.read_member('/path/inside/archive/to/file.txt')
|
118
|
+
# => "This is the content of the file from the archive."
|
119
|
+
```
|
120
|
+
|
121
|
+
#### Read multiple files from zip archive
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
files = zip.read_members([
|
125
|
+
'/path/inside/archive/to/file_1.txt',
|
126
|
+
'/path/inside/archive/to/file_2.txt'
|
127
|
+
])
|
128
|
+
# => ["File one content.", "File two content."]
|
129
|
+
```
|
130
|
+
|
131
|
+
#### Extract file from zip archive to filesystem path
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
file = zip.extract_member(
|
135
|
+
'/path/inside/archive/to/file.txt',
|
136
|
+
'path/on/local/filesystem/file.txt'
|
137
|
+
)
|
138
|
+
# => 'path/on/local/filesystem/file.txt'
|
139
|
+
```
|
140
|
+
|
141
|
+
#### Write file to zip archive from string
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
zip.write_member('/path/inside/archive/to/file.txt', 'File one content.')
|
145
|
+
# => true
|
146
|
+
```
|
147
|
+
|
148
|
+
#### Remove a file from a zip archive
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
file = zip.remove_member('/path/inside/archive/to/file.txt')
|
152
|
+
# => true
|
153
|
+
```
|
154
|
+
|
155
|
+
#### Remove multiple files from a zip archive
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
file = zip.remove_members([
|
159
|
+
'/path/inside/archive/to/file_1.txt',
|
160
|
+
'/path/inside/archive/to/file_2.txt'
|
161
|
+
])
|
162
|
+
# => true
|
163
|
+
```
|
164
|
+
|
165
|
+
#### Creating a new instance and passing a block
|
166
|
+
|
167
|
+
`.new` can accept a block:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
MultiZip.new('/path/to/archive.zip') do |archive|
|
171
|
+
# commands
|
172
|
+
end
|
173
|
+
```
|
174
|
+
|
175
|
+
## Supported backend gems
|
176
|
+
|
177
|
+
* [rubyzip](https://rubygems.org/gems/rubyzip)
|
178
|
+
- Works in MRI 1.9.3 and 2.x.x.
|
179
|
+
- Gem doesn't support MRI 1.8.7, Jruby or Rubinius.
|
180
|
+
* [archive-zip](https://rubygems.org/gems/archive-zip)
|
181
|
+
- Works in all MRI, Jruby and Rubinius.
|
182
|
+
* [zipruby](https://rubygems.org/gems/zipruby)
|
183
|
+
- Works in all MRI.
|
184
|
+
- Gem doesn't support Jruby or Rubinius.
|
185
|
+
|
186
|
+
Planned for the future:
|
187
|
+
|
188
|
+
* [archive](https://rubygems.org/gems/archive)
|
189
|
+
* [unix_utils](https://rubygems.org/gems/unix_utils)
|
190
|
+
* Other archive formats like gzip, bzip2, 7zip, tar, etc.
|
191
|
+
* Jruby-specific gems
|
192
|
+
* Others (please suggest them in a [new issue](https://github.com/xunker/multi_zip/issues/new))
|
193
|
+
|
194
|
+
## Notes
|
195
|
+
|
196
|
+
#### No `#save` method?
|
197
|
+
|
198
|
+
You'll notice that there is no `#save` method. All changes are made to the
|
199
|
+
archive immediately when the given method is called. If there are errors
|
200
|
+
writing to an archive, an exception is raised immediately.
|
201
|
+
|
202
|
+
#### Archive objects are not kept open between method calls
|
203
|
+
|
204
|
+
The underlying archive object from the backend gem is not kept open open in
|
205
|
+
memory. That means when you call a method like `#read_member` the archive is
|
206
|
+
opened, the file is read and the archive is closed. If you do that method
|
207
|
+
twice, that cycle happens two times.
|
208
|
+
|
209
|
+
While this *is* inefficient and *may* be slow for large archives, the benefits are a simplified interface and normalized memory usage.
|
210
|
+
|
211
|
+
This behaviour is likely to change in future versions; see the below section
|
212
|
+
that talks about the `#close` method for more information.
|
213
|
+
|
214
|
+
#### `#close` method is currently a non-op
|
215
|
+
|
216
|
+
You'll notice that there is a `#close` method, but you may not know that it
|
217
|
+
doesn't yet do anything since the underlying archive is not kept open between
|
218
|
+
method calls.
|
219
|
+
|
220
|
+
However, you should still use `#close` where appropriate since this
|
221
|
+
behaviour is likely to change in the future.
|
222
|
+
|
223
|
+
#### Support for other Rubies
|
224
|
+
|
225
|
+
Supporting MRI, Jruby and Rubinius covers 95% of the production-ruby market.
|
226
|
+
However, In the future I plan on **trying** to support:
|
227
|
+
|
228
|
+
* maglev
|
229
|
+
* ironruby
|
230
|
+
* macruby
|
231
|
+
|
232
|
+
The current travis-ci configuration only tests on Linux. Adding macruby
|
233
|
+
support also means testing on OS X. I would like to one-day test with MRI, Ironruby and Jruby on Windows.
|
234
|
+
|
235
|
+
MultiZip is written in pure ruby and so it should be able to run on any
|
236
|
+
runtime that is compatible with MRI 1.8.7; however, the backend gems it uses
|
237
|
+
may or may not work on every platform -- which is one of the reasons this
|
238
|
+
gem exists in the first place! One day I would like to support backend gems
|
239
|
+
that are specific to Jruby/Java and Windows.
|
240
|
+
|
241
|
+
## TODO
|
242
|
+
|
243
|
+
Most important things, in order of importance:
|
244
|
+
|
245
|
+
* #add_member: add file to archive from filesystem (new method).
|
246
|
+
* Document exceptions raised, what they mean and how to use them.
|
247
|
+
* Add inline docs for methods.
|
248
|
+
* support *nix zip(1L)/unzip(1L) without needing backend gem (with warning).
|
249
|
+
|
250
|
+
Other things that need to be done, in no particular order:
|
251
|
+
|
252
|
+
* Add support for more backends.
|
253
|
+
* Support for backend gems to process other formats (gzip, bzip2, 7zip, tar, etc).
|
254
|
+
* Keep the backend archive open between method calls.
|
255
|
+
* Add soak tests for memory usage once archived are kept open.
|
256
|
+
* Ensure #close is executed when MultiZip instance goes out of scope.
|
257
|
+
* Option to overwrite and existing archive instead of adding to it.
|
258
|
+
* #extract_member: extract file to path using original member name.
|
259
|
+
* #write_member: support for reading from IO streams.
|
260
|
+
* test with different majour versions of current supported backends.
|
261
|
+
* Standardize Exception classes and when to raise them.
|
262
|
+
* #read_*, #extract_* and #write_* methods should accept a block.
|
263
|
+
* #extract_members: extract multiple files with one command (new method).
|
264
|
+
* #write_member: add entire directory (recursively or not) to archive.
|
265
|
+
* #write_members: add multiple files by wildcard (new method).
|
266
|
+
* #add_members: add multiple files to archive from filesystem (new method).
|
267
|
+
* #remove_members: remove multiple member files from the archive (new_method).
|
268
|
+
* #read_members: read multiple files wildcard.
|
269
|
+
* #read_members: read multiple files via prefix as #list_members does.
|
270
|
+
* #extract_members: extract multiple files via prefix as #list_members does (new method).
|
271
|
+
* #extract_members: extract multiple files wildcard (new method).
|
272
|
+
* #member_info: return information (name, size, etc) about member (new method).
|
273
|
+
* #read_member_stream: return member as IO Stream to keeping large amounts of data in memory (new method).
|
274
|
+
* Write guide to show others how they can add their own backends gems.
|
275
|
+
* #member_type: return the type of the member (new method).
|
276
|
+
* #member_exists?: accept an argument to specify the file type (file, dir, symlink, etc).
|
277
|
+
* Soak-test each backend to find memory leaks.
|
278
|
+
|
279
|
+
Things that I'd **like** to do, but that are probably not realistic because
|
280
|
+
they cannot be sufficiently abstracted across all backend gems:
|
281
|
+
|
282
|
+
* Ability to set location and compression format and level of archive.
|
283
|
+
* Ability to set compression format and level of individual members (for Epub compatibility).
|
284
|
+
* Ability to set archive location of individual members (for Epub compatibility).
|
285
|
+
* Support creating, reading from and writing to password-protected or encrypted archives.
|
286
|
+
|
287
|
+
## Contributing
|
288
|
+
|
289
|
+
1. Fork it ( https://github.com/xunker/multi_zip/fork )
|
290
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
291
|
+
3. Ensure existing tests pass and add tests to cover any new functionality
|
292
|
+
4. Commit your changes (`git commit -am 'Add some feature'`)
|
293
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
294
|
+
6. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
module MultiZip::Backend::ArchiveZip
|
2
|
+
BUFFER_SIZE = 8192
|
3
|
+
|
4
|
+
def read_member(member_path, options = {})
|
5
|
+
archive_operation do |zip|
|
6
|
+
member = zip.find{|m| m.zip_path == member_path}
|
7
|
+
if member && member.file?
|
8
|
+
return member.file_data.read.to_s
|
9
|
+
else
|
10
|
+
zip.close
|
11
|
+
member_not_found!(member_path)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def list_members(prefix=nil, options={})
|
17
|
+
archive_operation do |zip|
|
18
|
+
zip.entries.map(&:zip_path).select{|n|
|
19
|
+
prefix ? n =~ /^#{prefix}/ : true
|
20
|
+
}.sort
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def write_member(member_path, member_contents, options = {})
|
25
|
+
# archive/zip is really focused on adding content from the file system so
|
26
|
+
# instead of hacking around with a non-public API that may change in the
|
27
|
+
# future, we will just use tempfiles and let it read from disk like the
|
28
|
+
# documentation says.
|
29
|
+
tempfile = Tempfile.new('multizip_member')
|
30
|
+
tempfile.write(member_contents)
|
31
|
+
tempfile.close
|
32
|
+
|
33
|
+
zip = Archive::Zip.new(@filename, :w)
|
34
|
+
new_entry = Archive::Zip::Entry.from_file(tempfile.path, :zip_path => member_path)
|
35
|
+
zip.add_entry(new_entry)
|
36
|
+
|
37
|
+
# From the docs: The #close method must be called in order to save any
|
38
|
+
# modifications to the archive. Due to limitations in the Ruby finalization
|
39
|
+
# capabilities, the #close method is _not_ automatically called when this
|
40
|
+
# object is garbage collected. Make sure to call #close when finished with
|
41
|
+
# this object.
|
42
|
+
zip.close
|
43
|
+
true
|
44
|
+
ensure
|
45
|
+
if defined?(tempfile)
|
46
|
+
tempfile.delete
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def extract_member(member_path, destination_path, options = {})
|
51
|
+
archive_operation do |zip|
|
52
|
+
member = zip.find{|m| m.zip_path == member_path}
|
53
|
+
if member && member.file?
|
54
|
+
output_file = ::File.new(destination_path, 'wb')
|
55
|
+
while chunk = member.file_data.read(BUFFER_SIZE)
|
56
|
+
output_file.write chunk
|
57
|
+
end
|
58
|
+
output_file.close
|
59
|
+
return destination_path
|
60
|
+
else
|
61
|
+
zip.close
|
62
|
+
member_not_found!(member_path)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def remove_member(member_path, options = {})
|
68
|
+
remove_members([member_path], options)
|
69
|
+
end
|
70
|
+
|
71
|
+
def remove_members(member_paths, options = {})
|
72
|
+
archive_exists!
|
73
|
+
member_paths.each do |member_path|
|
74
|
+
exists!(member_path)
|
75
|
+
end
|
76
|
+
|
77
|
+
# archive-zip doesn't have the #remove_entry method any more, so we do
|
78
|
+
# this in a really slow way: we dump the entire dir to the filesystem,
|
79
|
+
# delete `member_path` and zip the whole thing up again.
|
80
|
+
|
81
|
+
Dir.mktmpdir do |tmp_dir|
|
82
|
+
Archive::Zip.extract(@filename, tmp_dir)
|
83
|
+
|
84
|
+
member_paths.each do |member_path|
|
85
|
+
FileUtils.rm("#{tmp_dir}/#{member_path}")
|
86
|
+
end
|
87
|
+
|
88
|
+
# create a tempfile and immediately delete it, we just want the name.
|
89
|
+
tempfile = Tempfile.new(['multizip_temp', '.zip'])
|
90
|
+
tempfile_path = tempfile.path
|
91
|
+
tempfile.close
|
92
|
+
tempfile.delete
|
93
|
+
|
94
|
+
Archive::Zip.archive(tempfile_path, "#{tmp_dir}/.")
|
95
|
+
FileUtils.mv(tempfile_path, @filename)
|
96
|
+
end
|
97
|
+
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def archive_operation(mode = :r) # mode is either :r or :w
|
104
|
+
archive_exists!
|
105
|
+
Archive::Zip.open(@filename, mode) do |zip|
|
106
|
+
yield(zip)
|
107
|
+
end
|
108
|
+
rescue Archive::Zip::UnzipError => e
|
109
|
+
raise MultiZip::InvalidArchiveError.new(@filename, e)
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module MultiZip::Backend::Rubyzip
|
2
|
+
BUFFER_SIZE = 8192
|
3
|
+
def read_member(member_path, options = {})
|
4
|
+
read_operation do |zip_file|
|
5
|
+
if member = zip_file.glob(member_path).first
|
6
|
+
member.get_input_stream.read
|
7
|
+
else
|
8
|
+
zip_file.close
|
9
|
+
member_not_found!(member_path)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def list_members(prefix=nil, options={})
|
15
|
+
read_operation do |zip_file|
|
16
|
+
zip_file.map(&:name).select{|n| prefix ? n =~ /^#{prefix}/ : true}.sort
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def write_member(member_path, member_contents, options = {})
|
21
|
+
flags = (File.exists?(@filename) ? nil : Zip::File::CREATE)
|
22
|
+
|
23
|
+
Zip::File.open(@filename, flags) do |zipfile|
|
24
|
+
zipfile.get_output_stream(member_path) do |os|
|
25
|
+
os.write member_contents
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def extract_member(member_path, destination_path, options = {})
|
31
|
+
read_operation do |zip_file|
|
32
|
+
if member = zip_file.glob(member_path).first
|
33
|
+
stream = member.get_input_stream
|
34
|
+
|
35
|
+
output_file = ::File.new(destination_path, 'wb')
|
36
|
+
|
37
|
+
while chunk = stream.read(BUFFER_SIZE)
|
38
|
+
output_file.write chunk
|
39
|
+
end
|
40
|
+
|
41
|
+
output_file.close
|
42
|
+
else
|
43
|
+
zip_file.close
|
44
|
+
member_not_found!(member_path)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
destination_path
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove_member(member_path, options = {})
|
51
|
+
archive_exists!
|
52
|
+
exists!(member_path)
|
53
|
+
Zip::File.open(@filename) do |zipfile|
|
54
|
+
zipfile.remove(member_path)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def read_operation(&blk)
|
61
|
+
archive_exists!
|
62
|
+
Zip::File.open(@filename) do |zip_file|
|
63
|
+
yield(zip_file)
|
64
|
+
end
|
65
|
+
rescue Zip::Error => e
|
66
|
+
# not the best way to detect the class of error.
|
67
|
+
if e.message.match('Zip end of central directory signature not found')
|
68
|
+
raise MultiZip::InvalidArchiveError.new(@filename, e)
|
69
|
+
else
|
70
|
+
raise
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|