rubyzip 2.4.1 → 3.0.0.alpha
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 +4 -4
- data/Changelog.md +368 -0
- data/README.md +112 -37
- data/Rakefile +11 -7
- data/lib/zip/central_directory.rb +164 -118
- data/lib/zip/compressor.rb +3 -1
- data/lib/zip/constants.rb +25 -21
- data/lib/zip/crypto/decrypted_io.rb +3 -1
- data/lib/zip/crypto/encryption.rb +4 -2
- data/lib/zip/crypto/null_encryption.rb +5 -3
- data/lib/zip/crypto/traditional_encryption.rb +5 -3
- data/lib/zip/decompressor.rb +4 -3
- data/lib/zip/deflater.rb +10 -8
- data/lib/zip/dirtyable.rb +32 -0
- data/lib/zip/dos_time.rb +32 -3
- data/lib/zip/entry.rb +262 -198
- data/lib/zip/entry_set.rb +9 -7
- data/lib/zip/errors.rb +115 -16
- data/lib/zip/extra_field/generic.rb +3 -10
- data/lib/zip/extra_field/ntfs.rb +4 -2
- data/lib/zip/extra_field/old_unix.rb +3 -1
- data/lib/zip/extra_field/universal_time.rb +3 -1
- data/lib/zip/extra_field/unix.rb +5 -3
- data/lib/zip/extra_field/unknown.rb +33 -0
- data/lib/zip/extra_field/zip64.rb +12 -5
- data/lib/zip/extra_field.rb +15 -21
- data/lib/zip/file.rb +144 -265
- data/lib/zip/file_split.rb +97 -0
- data/lib/zip/filesystem/dir.rb +86 -0
- data/lib/zip/filesystem/directory_iterator.rb +48 -0
- data/lib/zip/filesystem/file.rb +262 -0
- data/lib/zip/filesystem/file_stat.rb +110 -0
- data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
- data/lib/zip/filesystem.rb +26 -595
- data/lib/zip/inflater.rb +7 -5
- data/lib/zip/input_stream.rb +44 -39
- data/lib/zip/ioextras/abstract_input_stream.rb +14 -9
- data/lib/zip/ioextras/abstract_output_stream.rb +5 -3
- data/lib/zip/ioextras.rb +6 -6
- data/lib/zip/null_compressor.rb +3 -1
- data/lib/zip/null_decompressor.rb +3 -1
- data/lib/zip/null_input_stream.rb +3 -1
- data/lib/zip/output_stream.rb +47 -48
- data/lib/zip/pass_thru_compressor.rb +3 -1
- data/lib/zip/pass_thru_decompressor.rb +4 -2
- data/lib/zip/streamable_directory.rb +3 -1
- data/lib/zip/streamable_stream.rb +3 -0
- data/lib/zip/version.rb +3 -1
- data/lib/zip.rb +15 -20
- data/rubyzip.gemspec +38 -0
- data/samples/example.rb +8 -3
- data/samples/example_filesystem.rb +2 -1
- data/samples/example_recursive.rb +3 -1
- data/samples/gtk_ruby_zip.rb +4 -2
- data/samples/qtzip.rb +6 -5
- data/samples/write_simple.rb +1 -0
- data/samples/zipfind.rb +1 -0
- metadata +84 -50
- data/TODO +0 -15
- 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
|
-
#
|
3
|
-
# The most important methods are those
|
4
|
-
#
|
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
|
-
#
|
9
|
-
#
|
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
|
13
|
-
#
|
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
|
23
|
+
# The following example opens zip archive `my.zip`
|
18
24
|
# (creating it if it doesn't exist) and adds an entry
|
19
|
-
#
|
25
|
+
# `first.txt` and a directory entry `a_dir`
|
20
26
|
# to it.
|
21
27
|
#
|
22
|
-
#
|
28
|
+
# ```
|
29
|
+
# require 'zip'
|
23
30
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
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
|
31
|
-
#
|
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
|
-
#
|
41
|
+
# ```
|
42
|
+
# require 'zip'
|
35
43
|
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
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
|
-
#
|
43
|
-
# interface for accessing the filesystem, ie. the File and Dir classes.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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 ->
|
62
|
+
# default -> true.
|
66
63
|
attr_accessor :restore_permissions
|
67
64
|
|
68
|
-
# default ->
|
65
|
+
# default -> true.
|
69
66
|
attr_accessor :restore_times
|
70
67
|
|
71
|
-
|
72
|
-
attr_accessor :comment
|
68
|
+
def_delegators :@cdir, :comment, :comment=, :each, :entries, :glob, :size
|
73
69
|
|
74
|
-
# Opens a zip archive. Pass true
|
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,
|
77
|
-
create: false, buffer: false, **options)
|
72
|
+
def initialize(path_or_io, create: false, buffer: false, **options)
|
78
73
|
super()
|
79
|
-
|
80
|
-
|
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
|
-
@
|
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
|
-
|
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,
|
127
|
-
Zip
|
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
|
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
|
-
|
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
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
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
|
-
|
252
|
-
|
253
|
-
|
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
|
-
|
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,
|
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(
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
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 =
|
186
|
+
new_entry.unix_perms = permissions
|
318
187
|
zip_streamable_entry = StreamableStream.new(new_entry)
|
319
|
-
@
|
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)
|
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
|
-
|
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(
|
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
|
-
@
|
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
|
-
@
|
236
|
+
@cdir.delete(found_entry)
|
361
237
|
found_entry.name = new_name
|
362
|
-
@
|
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
|
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.
|
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,24 +265,23 @@ module Zip
|
|
398
265
|
|
399
266
|
on_success_replace do |tmp_file|
|
400
267
|
::Zip::OutputStream.open(tmp_file) do |zos|
|
401
|
-
@
|
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
|
-
|
276
|
+
initialize_cdir(@name)
|
411
277
|
end
|
412
278
|
|
413
279
|
# Write buffer write changes to buffer and return
|
414
280
|
def write_buffer(io = ::StringIO.new)
|
415
|
-
return
|
281
|
+
return unless commit_required?
|
416
282
|
|
417
283
|
::Zip::OutputStream.write_buffer(io) do |zos|
|
418
|
-
@
|
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
|
-
@
|
432
|
-
|
297
|
+
return true if @create || @cdir.dirty?
|
298
|
+
|
299
|
+
@cdir.each do |e|
|
300
|
+
return true if e.dirty?
|
433
301
|
end
|
434
|
-
|
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 = @
|
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
|
-
@
|
333
|
+
@cdir << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
|
470
334
|
end
|
471
335
|
|
472
336
|
private
|
473
337
|
|
474
|
-
def
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
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
|
-
|
374
|
+
raise ::Zip::EntryExistsError.new proc_name, entry_name unless continue_on_exists_proc.call
|
489
375
|
|
490
|
-
|
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
|
-
|
506
|
-
|
507
|
-
|
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
|