rubyzip 2.4.rc1 → 3.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +368 -0
  3. data/README.md +112 -37
  4. data/Rakefile +11 -7
  5. data/lib/zip/central_directory.rb +164 -118
  6. data/lib/zip/compressor.rb +3 -1
  7. data/lib/zip/constants.rb +25 -21
  8. data/lib/zip/crypto/decrypted_io.rb +3 -1
  9. data/lib/zip/crypto/encryption.rb +4 -2
  10. data/lib/zip/crypto/null_encryption.rb +5 -3
  11. data/lib/zip/crypto/traditional_encryption.rb +5 -3
  12. data/lib/zip/decompressor.rb +4 -3
  13. data/lib/zip/deflater.rb +10 -8
  14. data/lib/zip/dirtyable.rb +32 -0
  15. data/lib/zip/dos_time.rb +32 -3
  16. data/lib/zip/entry.rb +263 -199
  17. data/lib/zip/entry_set.rb +9 -7
  18. data/lib/zip/errors.rb +115 -16
  19. data/lib/zip/extra_field/generic.rb +3 -10
  20. data/lib/zip/extra_field/ntfs.rb +4 -2
  21. data/lib/zip/extra_field/old_unix.rb +3 -1
  22. data/lib/zip/extra_field/universal_time.rb +3 -1
  23. data/lib/zip/extra_field/unix.rb +5 -3
  24. data/lib/zip/extra_field/unknown.rb +33 -0
  25. data/lib/zip/extra_field/zip64.rb +12 -5
  26. data/lib/zip/extra_field.rb +15 -21
  27. data/lib/zip/file.rb +143 -264
  28. data/lib/zip/file_split.rb +97 -0
  29. data/lib/zip/filesystem/dir.rb +86 -0
  30. data/lib/zip/filesystem/directory_iterator.rb +48 -0
  31. data/lib/zip/filesystem/file.rb +262 -0
  32. data/lib/zip/filesystem/file_stat.rb +110 -0
  33. data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
  34. data/lib/zip/filesystem.rb +26 -595
  35. data/lib/zip/inflater.rb +7 -5
  36. data/lib/zip/input_stream.rb +44 -39
  37. data/lib/zip/ioextras/abstract_input_stream.rb +14 -9
  38. data/lib/zip/ioextras/abstract_output_stream.rb +5 -3
  39. data/lib/zip/ioextras.rb +6 -6
  40. data/lib/zip/null_compressor.rb +3 -1
  41. data/lib/zip/null_decompressor.rb +3 -1
  42. data/lib/zip/null_input_stream.rb +3 -1
  43. data/lib/zip/output_stream.rb +47 -48
  44. data/lib/zip/pass_thru_compressor.rb +3 -1
  45. data/lib/zip/pass_thru_decompressor.rb +4 -2
  46. data/lib/zip/streamable_directory.rb +3 -1
  47. data/lib/zip/streamable_stream.rb +3 -0
  48. data/lib/zip/version.rb +3 -1
  49. data/lib/zip.rb +15 -16
  50. data/rubyzip.gemspec +38 -0
  51. data/samples/example.rb +8 -3
  52. data/samples/example_filesystem.rb +2 -1
  53. data/samples/example_recursive.rb +3 -1
  54. data/samples/gtk_ruby_zip.rb +4 -2
  55. data/samples/qtzip.rb +6 -5
  56. data/samples/write_simple.rb +1 -0
  57. data/samples/zipfind.rb +1 -0
  58. metadata +81 -46
  59. data/TODO +0 -15
  60. data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
data/lib/zip/file.rb CHANGED
@@ -1,132 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require_relative 'file_split'
6
+
1
7
  module Zip
2
- # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK.
3
- # The most important methods are those inherited from
4
- # ZipCentralDirectory for accessing information about the entries in
5
- # the archive and methods such as get_input_stream and
6
- # get_output_stream for reading from and writing entries to the
8
+ # Zip::File is modeled after java.util.zip.ZipFile from the Java SDK.
9
+ # The most important methods are those for accessing information about
10
+ # the entries in
11
+ # the archive and methods such as `get_input_stream` and
12
+ # `get_output_stream` for reading from and writing entries to the
7
13
  # archive. The class includes a few convenience methods such as
8
- # #extract for extracting entries to the filesystem, and #remove,
9
- # #replace, #rename and #mkdir for making simple modifications to
14
+ # `extract` for extracting entries to the filesystem, and `remove`,
15
+ # `replace`, `rename` and `mkdir` for making simple modifications to
10
16
  # the archive.
11
17
  #
12
- # Modifications to a zip archive are not committed until #commit or
13
- # #close is called. The method #open accepts a block following
14
- # the pattern from File.open offering a simple way to
18
+ # Modifications to a zip archive are not committed until `commit` or
19
+ # `close` is called. The method `open` accepts a block following
20
+ # the pattern from ::File.open offering a simple way to
15
21
  # automatically close the archive when the block returns.
16
22
  #
17
- # The following example opens zip archive <code>my.zip</code>
23
+ # The following example opens zip archive `my.zip`
18
24
  # (creating it if it doesn't exist) and adds an entry
19
- # <code>first.txt</code> and a directory entry <code>a_dir</code>
25
+ # `first.txt` and a directory entry `a_dir`
20
26
  # to it.
21
27
  #
22
- # require 'zip'
28
+ # ```
29
+ # require 'zip'
23
30
  #
24
- # Zip::File.open("my.zip", Zip::File::CREATE) {
25
- # |zipfile|
26
- # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" }
27
- # zipfile.mkdir("a_dir")
28
- # }
31
+ # Zip::File.open('my.zip', create: true) do |zipfile|
32
+ # zipfile.get_output_stream('first.txt') { |f| f.puts 'Hello from Zip::File' }
33
+ # zipfile.mkdir('a_dir')
34
+ # end
35
+ # ```
29
36
  #
30
- # The next example reopens <code>my.zip</code> writes the contents of
31
- # <code>first.txt</code> to standard out and deletes the entry from
37
+ # The next example reopens `my.zip`, writes the contents of
38
+ # `first.txt` to standard out and deletes the entry from
32
39
  # the archive.
33
40
  #
34
- # require 'zip'
41
+ # ```
42
+ # require 'zip'
35
43
  #
36
- # Zip::File.open("my.zip", Zip::File::CREATE) {
37
- # |zipfile|
38
- # puts zipfile.read("first.txt")
39
- # zipfile.remove("first.txt")
40
- # }
44
+ # Zip::File.open('my.zip', create: true) do |zipfile|
45
+ # puts zipfile.read('first.txt')
46
+ # zipfile.remove('first.txt')
47
+ # end
41
48
  #
42
- # ZipFileSystem offers an alternative API that emulates ruby's
43
- # interface for accessing the filesystem, ie. the File and Dir classes.
44
-
45
- class File < CentralDirectory
46
- CREATE = true
47
- SPLIT_SIGNATURE = 0x08074b50
48
- ZIP64_EOCD_SIGNATURE = 0x06064b50
49
- MAX_SEGMENT_SIZE = 3_221_225_472
50
- MIN_SEGMENT_SIZE = 65_536
51
- DATA_BUFFER_SIZE = 8192
52
- IO_METHODS = [:tell, :seek, :read, :eof, :close]
53
-
54
- DEFAULT_OPTIONS = {
55
- restore_ownership: false,
56
- restore_permissions: false,
57
- restore_times: false
58
- }.freeze
49
+ # Zip::FileSystem offers an alternative API that emulates ruby's
50
+ # interface for accessing the filesystem, ie. the ::File and ::Dir classes.
51
+ class File
52
+ extend Forwardable
53
+ extend FileSplit
54
+
55
+ IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze
59
56
 
60
57
  attr_reader :name
61
58
 
62
59
  # default -> false.
63
60
  attr_accessor :restore_ownership
64
61
 
65
- # default -> false, but will be set to true in a future version.
62
+ # default -> true.
66
63
  attr_accessor :restore_permissions
67
64
 
68
- # default -> false, but will be set to true in a future version.
65
+ # default -> true.
69
66
  attr_accessor :restore_times
70
67
 
71
- # Returns the zip files comment, if it has one
72
- attr_accessor :comment
68
+ def_delegators :@cdir, :comment, :comment=, :each, :entries, :glob, :size
73
69
 
74
- # Opens a zip archive. Pass true as the second parameter to create
70
+ # Opens a zip archive. Pass create: true to create
75
71
  # a new archive if it doesn't exist already.
76
- def initialize(path_or_io, dep_create = false, dep_buffer = false,
77
- create: false, buffer: false, **options)
72
+ def initialize(path_or_io, create: false, buffer: false, **options)
78
73
  super()
79
-
80
- Zip.warn_about_v3_api('File#new') if dep_create || dep_buffer
81
-
82
- options = DEFAULT_OPTIONS.merge(options)
74
+ options = DEFAULT_RESTORE_OPTIONS
75
+ .merge(compression_level: ::Zip.default_compression)
76
+ .merge(options)
83
77
  @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
84
- @comment = ''
85
- @create = create || dep_create ? true : false # allow any truthy value to mean true
86
- buffer ||= dep_buffer
87
-
88
- if ::File.size?(@name.to_s)
89
- # There is a file, which exists, that is associated with this zip.
90
- @create = false
91
- @file_permissions = ::File.stat(@name).mode
78
+ @create = create ? true : false # allow any truthy value to mean true
92
79
 
93
- if buffer
94
- read_from_stream(path_or_io)
95
- else
96
- ::File.open(@name, 'rb') do |f|
97
- read_from_stream(f)
98
- end
99
- end
100
- elsif buffer && path_or_io.size > 0
101
- # This zip is probably a non-empty StringIO.
102
- @create = false
103
- read_from_stream(path_or_io)
104
- elsif @create
105
- # This zip is completely new/empty and is to be created.
106
- @entry_set = EntrySet.new
107
- elsif ::File.zero?(@name)
108
- # A file exists, but it is empty.
109
- raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
110
- else
111
- # Everything is wrong.
112
- raise Error, "File #{@name} not found"
113
- end
80
+ initialize_cdir(path_or_io, buffer: buffer)
114
81
 
115
- @stored_entries = @entry_set.dup
116
- @stored_comment = @comment
117
82
  @restore_ownership = options[:restore_ownership]
118
83
  @restore_permissions = options[:restore_permissions]
119
84
  @restore_times = options[:restore_times]
85
+ @compression_level = options[:compression_level]
120
86
  end
121
87
 
122
88
  class << self
123
89
  # Similar to ::new. If a block is passed the Zip::File object is passed
124
90
  # to the block and is automatically closed afterwards, just as with
125
91
  # ruby's builtin File::open method.
126
- def open(file_name, dep_create = false, create: false, **options)
127
- Zip.warn_about_v3_api('Zip::File.open') if dep_create
128
-
129
- zf = ::Zip::File.new(file_name, create: (dep_create || create), buffer: false, **options)
92
+ def open(file_name, create: false, **options)
93
+ zf = ::Zip::File.new(file_name, create: create, **options)
130
94
  return zf unless block_given?
131
95
 
132
96
  begin
@@ -136,31 +100,19 @@ module Zip
136
100
  end
137
101
  end
138
102
 
139
- # Same as #open. But outputs data to a buffer instead of a file
140
- def add_buffer
141
- Zip.warn_about_v3_api('Zip::File.add_buffer')
142
-
143
- io = ::StringIO.new
144
- zf = ::Zip::File.new(io, true, true)
145
- yield zf
146
- zf.write_buffer(io)
147
- end
148
-
149
103
  # Like #open, but reads zip archive contents from a String or open IO
150
104
  # stream, and outputs data to a buffer.
151
105
  # (This can be used to extract data from a
152
106
  # downloaded zip archive without first saving it to disk.)
153
- def open_buffer(io, **options)
107
+ def open_buffer(io = ::StringIO.new, create: false, **options)
154
108
  unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
155
- raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
109
+ raise 'Zip::File.open_buffer expects a String or IO-like argument' \
110
+ "(responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
156
111
  end
157
112
 
158
113
  io = ::StringIO.new(io) if io.kind_of?(::String)
159
114
 
160
- # https://github.com/rubyzip/rubyzip/issues/119
161
- io.binmode if io.respond_to?(:binmode)
162
-
163
- zf = ::Zip::File.new(io, create: true, buffer: true, **options)
115
+ zf = ::Zip::File.new(io, create: create, buffer: true, **options)
164
116
  return zf unless block_given?
165
117
 
166
118
  yield zf
@@ -184,88 +136,18 @@ module Zip
184
136
  end
185
137
  end
186
138
 
187
- def get_segment_size_for_split(segment_size)
188
- if MIN_SEGMENT_SIZE > segment_size
189
- MIN_SEGMENT_SIZE
190
- elsif MAX_SEGMENT_SIZE < segment_size
191
- MAX_SEGMENT_SIZE
192
- else
193
- segment_size
194
- end
195
- end
196
-
197
- def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
198
- unless partial_zip_file_name.nil?
199
- partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/,
200
- partial_zip_file_name + ::File.extname(zip_file_name))
201
- end
202
- partial_zip_file_name ||= zip_file_name
203
- partial_zip_file_name
204
- end
205
-
206
- def get_segment_count_for_split(zip_file_size, segment_size)
207
- (zip_file_size / segment_size).to_i + (zip_file_size % segment_size == 0 ? 0 : 1)
208
- end
209
-
210
- def put_split_signature(szip_file, segment_size)
211
- signature_packed = [SPLIT_SIGNATURE].pack('V')
212
- szip_file << signature_packed
213
- segment_size - signature_packed.size
214
- end
215
-
216
- #
217
- # TODO: Make the code more understandable
218
- #
219
- def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
220
- ssegment_size = zip_file_size - zip_file.pos
221
- ssegment_size = segment_size if ssegment_size > segment_size
222
- szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}"
223
- ::File.open(szip_file_name, 'wb') do |szip_file|
224
- if szip_file_index == 1
225
- ssegment_size = put_split_signature(szip_file, segment_size)
226
- end
227
- chunk_bytes = 0
228
- until ssegment_size == chunk_bytes || zip_file.eof?
229
- segment_bytes_left = ssegment_size - chunk_bytes
230
- buffer_size = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE
231
- chunk = zip_file.read(buffer_size)
232
- chunk_bytes += buffer_size
233
- szip_file << chunk
234
- # Info for track splitting
235
- yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given?
236
- end
237
- end
238
- end
239
-
240
- # Splits an archive into parts with segment size
241
- def split(zip_file_name,
242
- dep_segment_size = MAX_SEGMENT_SIZE, dep_delete_zip_file = true, dep_partial_zip_file_name = nil,
243
- segment_size: MAX_SEGMENT_SIZE, delete_zip_file: nil, partial_zip_file_name: nil)
244
- raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name)
245
- raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
246
-
247
- if dep_segment_size != MAX_SEGMENT_SIZE || !dep_delete_zip_file || dep_partial_zip_file_name
248
- Zip.warn_about_v3_api('Zip::File.split')
249
- end
139
+ # Count the entries in a zip archive without reading the whole set of
140
+ # entry data into memory.
141
+ def count_entries(path_or_io)
142
+ cdir = ::Zip::CentralDirectory.new
250
143
 
251
- zip_file_size = ::File.size(zip_file_name)
252
- segment_size = get_segment_size_for_split(segment_size || dep_segment_size)
253
- return if zip_file_size <= segment_size
254
-
255
- segment_count = get_segment_count_for_split(zip_file_size, segment_size)
256
- # Checking for correct zip structure
257
- ::Zip::File.open(zip_file_name) {}
258
- partial_zip_file_name = get_partial_zip_file_name(zip_file_name, (partial_zip_file_name || dep_partial_zip_file_name))
259
- szip_file_index = 0
260
- ::File.open(zip_file_name, 'rb') do |zip_file|
261
- until zip_file.eof?
262
- szip_file_index += 1
263
- save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
144
+ if path_or_io.kind_of?(String)
145
+ ::File.open(path_or_io, 'rb') do |f|
146
+ cdir.count_entries(f)
264
147
  end
148
+ else
149
+ cdir.count_entries(path_or_io)
265
150
  end
266
- delete_zip_file = delete_zip_file.nil? ? dep_delete_zip_file : delete_zip_file
267
- ::File.delete(zip_file_name) if delete_zip_file
268
- szip_file_index
269
151
  end
270
152
  end
271
153
 
@@ -281,45 +163,31 @@ module Zip
281
163
  # specified. If a block is passed the stream object is passed to the block and
282
164
  # the stream is automatically closed afterwards just as with ruby's builtin
283
165
  # File.open method.
284
- # rubocop:disable Metrics/ParameterLists, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
285
- def get_output_stream(entry,
286
- dep_permission_int = nil, dep_comment = nil,
287
- dep_extra = nil, dep_compressed_size = nil, dep_crc = nil,
288
- dep_compression_method = nil, dep_size = nil, dep_time = nil,
289
- permission_int: nil, comment: nil,
166
+ def get_output_stream(entry, permissions: nil, comment: nil,
290
167
  extra: nil, compressed_size: nil, crc: nil,
291
- compression_method: nil, size: nil, time: nil,
292
- &a_proc)
293
-
294
- unless dep_permission_int.nil? && dep_comment.nil? && dep_extra.nil? &&
295
- dep_compressed_size.nil? && dep_crc.nil? && dep_compression_method.nil? &&
296
- dep_size.nil? && dep_time.nil?
297
- Zip.warn_about_v3_api('Zip::File#get_output_stream')
298
- end
168
+ compression_method: nil, compression_level: nil,
169
+ size: nil, time: nil, &a_proc)
299
170
 
300
171
  new_entry =
301
172
  if entry.kind_of?(Entry)
302
173
  entry
303
174
  else
304
- Entry.new(@name, entry.to_s,
305
- comment: (comment || dep_comment),
306
- extra: (extra || dep_extra),
307
- compressed_size: (compressed_size || dep_compressed_size),
308
- crc: (crc || dep_crc),
309
- compression_method: (compression_method || dep_compression_method),
310
- size: (size || dep_size),
311
- time: (time || dep_time))
175
+ Entry.new(
176
+ @name, entry.to_s, comment: comment, extra: extra,
177
+ compressed_size: compressed_size, crc: crc, size: size,
178
+ compression_method: compression_method,
179
+ compression_level: compression_level, time: time
180
+ )
312
181
  end
313
182
  if new_entry.directory?
314
183
  raise ArgumentError,
315
184
  "cannot open stream to directory entry - '#{new_entry}'"
316
185
  end
317
- new_entry.unix_perms = (permission_int || dep_permission_int)
186
+ new_entry.unix_perms = permissions
318
187
  zip_streamable_entry = StreamableStream.new(new_entry)
319
- @entry_set << zip_streamable_entry
188
+ @cdir << zip_streamable_entry
320
189
  zip_streamable_entry.get_output_stream(&a_proc)
321
190
  end
322
- # rubocop:enable Metrics/ParameterLists, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
323
191
 
324
192
  # Returns the name of the zip archive
325
193
  def to_s
@@ -335,31 +203,39 @@ module Zip
335
203
  def add(entry, src_path, &continue_on_exists_proc)
336
204
  continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc }
337
205
  check_entry_exists(entry, continue_on_exists_proc, 'add')
338
- new_entry = entry.kind_of?(::Zip::Entry) ? entry : ::Zip::Entry.new(@name, entry.to_s)
206
+ new_entry = if entry.kind_of?(::Zip::Entry)
207
+ entry
208
+ else
209
+ ::Zip::Entry.new(
210
+ @name, entry.to_s,
211
+ compression_level: @compression_level
212
+ )
213
+ end
339
214
  new_entry.gather_fileinfo_from_srcpath(src_path)
340
- new_entry.dirty = true
341
- @entry_set << new_entry
215
+ @cdir << new_entry
342
216
  end
343
217
 
344
218
  # Convenience method for adding the contents of a file to the archive
345
219
  # in Stored format (uncompressed)
346
220
  def add_stored(entry, src_path, &continue_on_exists_proc)
347
- entry = ::Zip::Entry.new(@name, entry.to_s, nil, nil, nil, nil, ::Zip::Entry::STORED)
221
+ entry = ::Zip::Entry.new(
222
+ @name, entry.to_s, compression_method: ::Zip::Entry::STORED
223
+ )
348
224
  add(entry, src_path, &continue_on_exists_proc)
349
225
  end
350
226
 
351
227
  # Removes the specified entry.
352
228
  def remove(entry)
353
- @entry_set.delete(get_entry(entry))
229
+ @cdir.delete(get_entry(entry))
354
230
  end
355
231
 
356
232
  # Renames the specified entry.
357
233
  def rename(entry, new_name, &continue_on_exists_proc)
358
234
  found_entry = get_entry(entry)
359
235
  check_entry_exists(new_name, continue_on_exists_proc, 'rename')
360
- @entry_set.delete(found_entry)
236
+ @cdir.delete(found_entry)
361
237
  found_entry.name = new_name
362
- @entry_set << found_entry
238
+ @cdir << found_entry
363
239
  end
364
240
 
365
241
  # Replaces the specified entry with the contents of src_path (from
@@ -370,25 +246,16 @@ module Zip
370
246
  add(entry, src_path)
371
247
  end
372
248
 
373
- # Extracts entry to file dest_path.
374
- def extract(entry, dest_path, &block)
375
- Zip.warn_about_v3_api('Zip::File#extract')
376
-
377
- block ||= proc { ::Zip.on_exists_proc }
378
- found_entry = get_entry(entry)
379
- found_entry.extract(dest_path, &block)
380
- end
381
-
382
249
  # Extracts `entry` to a file at `entry_path`, with `destination_directory`
383
250
  # as the base location in the filesystem.
384
251
  #
385
252
  # NB: The caller is responsible for making sure `destination_directory` is
386
253
  # safe, if it is passed.
387
- def extract_v3(entry, entry_path = nil, destination_directory: '.', &block)
254
+ def extract(entry, entry_path = nil, destination_directory: '.', &block)
388
255
  block ||= proc { ::Zip.on_exists_proc }
389
256
  found_entry = get_entry(entry)
390
257
  entry_path ||= found_entry.name
391
- found_entry.extract_v3(entry_path, destination_directory: destination_directory, &block)
258
+ found_entry.extract(entry_path, destination_directory: destination_directory, &block)
392
259
  end
393
260
 
394
261
  # Commits changes that has been made since the previous commit to
@@ -398,16 +265,15 @@ module Zip
398
265
 
399
266
  on_success_replace do |tmp_file|
400
267
  ::Zip::OutputStream.open(tmp_file) do |zos|
401
- @entry_set.each do |e|
268
+ @cdir.each do |e|
402
269
  e.write_to_zip_output_stream(zos)
403
- e.dirty = false
404
270
  e.clean_up
405
271
  end
406
272
  zos.comment = comment
407
273
  end
408
274
  true
409
275
  end
410
- initialize(name)
276
+ initialize_cdir(@name)
411
277
  end
412
278
 
413
279
  # Write buffer write changes to buffer and return
@@ -415,7 +281,7 @@ module Zip
415
281
  return unless commit_required?
416
282
 
417
283
  ::Zip::OutputStream.write_buffer(io) do |zos|
418
- @entry_set.each { |e| e.write_to_zip_output_stream(zos) }
284
+ @cdir.each { |e| e.write_to_zip_output_stream(zos) }
419
285
  zos.comment = comment
420
286
  end
421
287
  end
@@ -428,16 +294,19 @@ module Zip
428
294
  # Returns true if any changes has been made to this archive since
429
295
  # the previous commit
430
296
  def commit_required?
431
- @entry_set.each do |e|
432
- return true if e.dirty
297
+ return true if @create || @cdir.dirty?
298
+
299
+ @cdir.each do |e|
300
+ return true if e.dirty?
433
301
  end
434
- @comment != @stored_comment || @entry_set != @stored_entries || @create
302
+
303
+ false
435
304
  end
436
305
 
437
306
  # Searches for entry with the specified name. Returns nil if
438
307
  # no entry is found. See also get_entry
439
308
  def find_entry(entry_name)
440
- selected_entry = @entry_set.find_entry(entry_name)
309
+ selected_entry = @cdir.find_entry(entry_name)
441
310
  return if selected_entry.nil?
442
311
 
443
312
  selected_entry.restore_ownership = @restore_ownership
@@ -446,11 +315,6 @@ module Zip
446
315
  selected_entry
447
316
  end
448
317
 
449
- # Searches for entries given a glob
450
- def glob(*args, &block)
451
- @entry_set.glob(*args, &block)
452
- end
453
-
454
318
  # Searches for an entry just as find_entry, but throws Errno::ENOENT
455
319
  # if no entry is found.
456
320
  def get_entry(entry)
@@ -466,33 +330,50 @@ module Zip
466
330
 
467
331
  entry_name = entry_name.dup.to_s
468
332
  entry_name << '/' unless entry_name.end_with?('/')
469
- @entry_set << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
333
+ @cdir << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
470
334
  end
471
335
 
472
336
  private
473
337
 
474
- def directory?(new_entry, src_path)
475
- path_is_directory = ::File.directory?(src_path)
476
- if new_entry.directory? && !path_is_directory
477
- raise ArgumentError,
478
- "entry name '#{new_entry}' indicates directory entry, but " \
479
- "'#{src_path}' is not a directory"
480
- elsif !new_entry.directory? && path_is_directory
481
- new_entry.name += '/'
338
+ def initialize_cdir(path_or_io, buffer: false)
339
+ @cdir = ::Zip::CentralDirectory.new
340
+
341
+ if ::File.size?(@name.to_s)
342
+ # There is a file, which exists, that is associated with this zip.
343
+ @create = false
344
+ @file_permissions = ::File.stat(@name).mode
345
+
346
+ if buffer
347
+ # https://github.com/rubyzip/rubyzip/issues/119
348
+ path_or_io.binmode if path_or_io.respond_to?(:binmode)
349
+ @cdir.read_from_stream(path_or_io)
350
+ else
351
+ ::File.open(@name, 'rb') do |f|
352
+ @cdir.read_from_stream(f)
353
+ end
354
+ end
355
+ elsif buffer && path_or_io.size > 0
356
+ # This zip is probably a non-empty StringIO.
357
+ @create = false
358
+ @cdir.read_from_stream(path_or_io)
359
+ elsif !@create && ::File.zero?(@name)
360
+ # A file exists, but it is empty, and we've said we're
361
+ # NOT creating a new zip.
362
+ raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
363
+ elsif !@create
364
+ # If we get here, and we're not creating a new zip, then
365
+ # everything is wrong.
366
+ raise Error, "File #{@name} not found"
482
367
  end
483
- new_entry.directory? && path_is_directory
484
368
  end
485
369
 
486
370
  def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
371
+ return unless @cdir.include?(entry_name)
372
+
487
373
  continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
488
- return unless @entry_set.include?(entry_name)
374
+ raise ::Zip::EntryExistsError.new proc_name, entry_name unless continue_on_exists_proc.call
489
375
 
490
- if continue_on_exists_proc.call
491
- remove get_entry(entry_name)
492
- else
493
- raise ::Zip::EntryExistsError,
494
- proc_name + " failed. Entry #{entry_name} already exists"
495
- end
376
+ remove get_entry(entry_name)
496
377
  end
497
378
 
498
379
  def check_file(path)
@@ -502,14 +383,12 @@ module Zip
502
383
  def on_success_replace
503
384
  dirname, basename = ::File.split(name)
504
385
  ::Dir::Tmpname.create(basename, dirname) do |tmp_filename|
505
- begin
506
- if yield tmp_filename
507
- ::File.rename(tmp_filename, name)
508
- ::File.chmod(@file_permissions, name) unless @create
509
- end
510
- ensure
511
- ::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
386
+ if yield tmp_filename
387
+ ::File.rename(tmp_filename, name)
388
+ ::File.chmod(@file_permissions, name) unless @create
512
389
  end
390
+ ensure
391
+ ::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
513
392
  end
514
393
  end
515
394
  end