multi_zip 0.1.3

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 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
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .ruby-*
16
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,17 @@
1
+ language: ruby
2
+ rvm:
3
+ - ruby-head
4
+ - ree
5
+ - 1.8.7
6
+ - 1.9.3
7
+ - 2.0.0
8
+ - 2.1.5
9
+ - 2.2.1
10
+ - jruby-18mode
11
+ - jruby-19mode
12
+ - rbx-2
13
+ matrix:
14
+ allow_failures:
15
+ - rvm:
16
+ - ruby-head
17
+ - rbx-2
@@ -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,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+ require "rspec/core/version"
5
+
6
+ task :default => :spec
7
+
8
+ desc "Run all examples"
9
+ RSpec::Core::RakeTask.new(:spec, :default) do |t|
10
+ t.ruby_opts = %w[-w]
11
+ end
@@ -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