multi_zip 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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