rubyzip 2.3.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 +122 -39
- 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 +41 -5
- data/lib/zip/entry.rb +273 -170
- 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 +152 -221
- 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 +6 -4
- data/lib/zip/input_stream.rb +43 -25
- data/lib/zip/ioextras/abstract_input_stream.rb +13 -8
- 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 +45 -39
- 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 -22
- 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 +83 -35
- data/TODO +0 -15
- data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
data/lib/zip/file.rb
CHANGED
@@ -1,124 +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, create
|
72
|
+
def initialize(path_or_io, create: false, buffer: false, **options)
|
77
73
|
super()
|
78
|
-
options =
|
74
|
+
options = DEFAULT_RESTORE_OPTIONS
|
75
|
+
.merge(compression_level: ::Zip.default_compression)
|
76
|
+
.merge(options)
|
79
77
|
@name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
|
80
|
-
@comment = ''
|
81
78
|
@create = create ? true : false # allow any truthy value to mean true
|
82
79
|
|
83
|
-
|
84
|
-
# There is a file, which exists, that is associated with this zip.
|
85
|
-
@create = false
|
86
|
-
@file_permissions = ::File.stat(@name).mode
|
80
|
+
initialize_cdir(path_or_io, buffer: buffer)
|
87
81
|
|
88
|
-
if buffer
|
89
|
-
read_from_stream(path_or_io)
|
90
|
-
else
|
91
|
-
::File.open(@name, 'rb') do |f|
|
92
|
-
read_from_stream(f)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
elsif buffer && path_or_io.size > 0
|
96
|
-
# This zip is probably a non-empty StringIO.
|
97
|
-
read_from_stream(path_or_io)
|
98
|
-
elsif @create
|
99
|
-
# This zip is completely new/empty and is to be created.
|
100
|
-
@entry_set = EntrySet.new
|
101
|
-
elsif ::File.zero?(@name)
|
102
|
-
# A file exists, but it is empty.
|
103
|
-
raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
|
104
|
-
else
|
105
|
-
# Everything is wrong.
|
106
|
-
raise Error, "File #{@name} not found"
|
107
|
-
end
|
108
|
-
|
109
|
-
@stored_entries = @entry_set.dup
|
110
|
-
@stored_comment = @comment
|
111
82
|
@restore_ownership = options[:restore_ownership]
|
112
83
|
@restore_permissions = options[:restore_permissions]
|
113
84
|
@restore_times = options[:restore_times]
|
85
|
+
@compression_level = options[:compression_level]
|
114
86
|
end
|
115
87
|
|
116
88
|
class << self
|
117
89
|
# Similar to ::new. If a block is passed the Zip::File object is passed
|
118
90
|
# to the block and is automatically closed afterwards, just as with
|
119
91
|
# ruby's builtin File::open method.
|
120
|
-
def open(file_name, create
|
121
|
-
zf = ::Zip::File.new(file_name, create
|
92
|
+
def open(file_name, create: false, **options)
|
93
|
+
zf = ::Zip::File.new(file_name, create: create, **options)
|
122
94
|
return zf unless block_given?
|
123
95
|
|
124
96
|
begin
|
@@ -128,29 +100,19 @@ module Zip
|
|
128
100
|
end
|
129
101
|
end
|
130
102
|
|
131
|
-
# Same as #open. But outputs data to a buffer instead of a file
|
132
|
-
def add_buffer
|
133
|
-
io = ::StringIO.new('')
|
134
|
-
zf = ::Zip::File.new(io, true, true)
|
135
|
-
yield zf
|
136
|
-
zf.write_buffer(io)
|
137
|
-
end
|
138
|
-
|
139
103
|
# Like #open, but reads zip archive contents from a String or open IO
|
140
104
|
# stream, and outputs data to a buffer.
|
141
105
|
# (This can be used to extract data from a
|
142
106
|
# downloaded zip archive without first saving it to disk.)
|
143
|
-
def open_buffer(io,
|
107
|
+
def open_buffer(io = ::StringIO.new, create: false, **options)
|
144
108
|
unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
|
145
|
-
raise
|
109
|
+
raise 'Zip::File.open_buffer expects a String or IO-like argument' \
|
110
|
+
"(responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
|
146
111
|
end
|
147
112
|
|
148
113
|
io = ::StringIO.new(io) if io.kind_of?(::String)
|
149
114
|
|
150
|
-
|
151
|
-
io.binmode if io.respond_to?(:binmode)
|
152
|
-
|
153
|
-
zf = ::Zip::File.new(io, true, true, options)
|
115
|
+
zf = ::Zip::File.new(io, create: create, buffer: true, **options)
|
154
116
|
return zf unless block_given?
|
155
117
|
|
156
118
|
yield zf
|
@@ -174,81 +136,18 @@ module Zip
|
|
174
136
|
end
|
175
137
|
end
|
176
138
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
MAX_SEGMENT_SIZE
|
182
|
-
else
|
183
|
-
segment_size
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
|
188
|
-
unless partial_zip_file_name.nil?
|
189
|
-
partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/,
|
190
|
-
partial_zip_file_name + ::File.extname(zip_file_name))
|
191
|
-
end
|
192
|
-
partial_zip_file_name ||= zip_file_name
|
193
|
-
partial_zip_file_name
|
194
|
-
end
|
195
|
-
|
196
|
-
def get_segment_count_for_split(zip_file_size, segment_size)
|
197
|
-
(zip_file_size / segment_size).to_i + (zip_file_size % segment_size == 0 ? 0 : 1)
|
198
|
-
end
|
199
|
-
|
200
|
-
def put_split_signature(szip_file, segment_size)
|
201
|
-
signature_packed = [SPLIT_SIGNATURE].pack('V')
|
202
|
-
szip_file << signature_packed
|
203
|
-
segment_size - signature_packed.size
|
204
|
-
end
|
205
|
-
|
206
|
-
#
|
207
|
-
# TODO: Make the code more understandable
|
208
|
-
#
|
209
|
-
def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
|
210
|
-
ssegment_size = zip_file_size - zip_file.pos
|
211
|
-
ssegment_size = segment_size if ssegment_size > segment_size
|
212
|
-
szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}"
|
213
|
-
::File.open(szip_file_name, 'wb') do |szip_file|
|
214
|
-
if szip_file_index == 1
|
215
|
-
ssegment_size = put_split_signature(szip_file, segment_size)
|
216
|
-
end
|
217
|
-
chunk_bytes = 0
|
218
|
-
until ssegment_size == chunk_bytes || zip_file.eof?
|
219
|
-
segment_bytes_left = ssegment_size - chunk_bytes
|
220
|
-
buffer_size = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE
|
221
|
-
chunk = zip_file.read(buffer_size)
|
222
|
-
chunk_bytes += buffer_size
|
223
|
-
szip_file << chunk
|
224
|
-
# Info for track splitting
|
225
|
-
yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given?
|
226
|
-
end
|
227
|
-
end
|
228
|
-
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
|
229
143
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
|
234
|
-
|
235
|
-
zip_file_size = ::File.size(zip_file_name)
|
236
|
-
segment_size = get_segment_size_for_split(segment_size)
|
237
|
-
return if zip_file_size <= segment_size
|
238
|
-
|
239
|
-
segment_count = get_segment_count_for_split(zip_file_size, segment_size)
|
240
|
-
# Checking for correct zip structure
|
241
|
-
::Zip::File.open(zip_file_name) {}
|
242
|
-
partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
|
243
|
-
szip_file_index = 0
|
244
|
-
::File.open(zip_file_name, 'rb') do |zip_file|
|
245
|
-
until zip_file.eof?
|
246
|
-
szip_file_index += 1
|
247
|
-
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)
|
248
147
|
end
|
148
|
+
else
|
149
|
+
cdir.count_entries(path_or_io)
|
249
150
|
end
|
250
|
-
::File.delete(zip_file_name) if delete_zip_file
|
251
|
-
szip_file_index
|
252
151
|
end
|
253
152
|
end
|
254
153
|
|
@@ -264,24 +163,29 @@ module Zip
|
|
264
163
|
# specified. If a block is passed the stream object is passed to the block and
|
265
164
|
# the stream is automatically closed afterwards just as with ruby's builtin
|
266
165
|
# File.open method.
|
267
|
-
def get_output_stream(entry,
|
268
|
-
extra
|
269
|
-
compression_method
|
270
|
-
&a_proc)
|
166
|
+
def get_output_stream(entry, permissions: nil, comment: nil,
|
167
|
+
extra: nil, compressed_size: nil, crc: nil,
|
168
|
+
compression_method: nil, compression_level: nil,
|
169
|
+
size: nil, time: nil, &a_proc)
|
271
170
|
|
272
171
|
new_entry =
|
273
172
|
if entry.kind_of?(Entry)
|
274
173
|
entry
|
275
174
|
else
|
276
|
-
Entry.new(
|
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
|
+
)
|
277
181
|
end
|
278
182
|
if new_entry.directory?
|
279
183
|
raise ArgumentError,
|
280
184
|
"cannot open stream to directory entry - '#{new_entry}'"
|
281
185
|
end
|
282
|
-
new_entry.unix_perms =
|
186
|
+
new_entry.unix_perms = permissions
|
283
187
|
zip_streamable_entry = StreamableStream.new(new_entry)
|
284
|
-
@
|
188
|
+
@cdir << zip_streamable_entry
|
285
189
|
zip_streamable_entry.get_output_stream(&a_proc)
|
286
190
|
end
|
287
191
|
|
@@ -299,31 +203,39 @@ module Zip
|
|
299
203
|
def add(entry, src_path, &continue_on_exists_proc)
|
300
204
|
continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc }
|
301
205
|
check_entry_exists(entry, continue_on_exists_proc, 'add')
|
302
|
-
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
|
303
214
|
new_entry.gather_fileinfo_from_srcpath(src_path)
|
304
|
-
|
305
|
-
@entry_set << new_entry
|
215
|
+
@cdir << new_entry
|
306
216
|
end
|
307
217
|
|
308
218
|
# Convenience method for adding the contents of a file to the archive
|
309
219
|
# in Stored format (uncompressed)
|
310
220
|
def add_stored(entry, src_path, &continue_on_exists_proc)
|
311
|
-
entry = ::Zip::Entry.new(
|
221
|
+
entry = ::Zip::Entry.new(
|
222
|
+
@name, entry.to_s, compression_method: ::Zip::Entry::STORED
|
223
|
+
)
|
312
224
|
add(entry, src_path, &continue_on_exists_proc)
|
313
225
|
end
|
314
226
|
|
315
227
|
# Removes the specified entry.
|
316
228
|
def remove(entry)
|
317
|
-
@
|
229
|
+
@cdir.delete(get_entry(entry))
|
318
230
|
end
|
319
231
|
|
320
232
|
# Renames the specified entry.
|
321
233
|
def rename(entry, new_name, &continue_on_exists_proc)
|
322
234
|
found_entry = get_entry(entry)
|
323
235
|
check_entry_exists(new_name, continue_on_exists_proc, 'rename')
|
324
|
-
@
|
236
|
+
@cdir.delete(found_entry)
|
325
237
|
found_entry.name = new_name
|
326
|
-
@
|
238
|
+
@cdir << found_entry
|
327
239
|
end
|
328
240
|
|
329
241
|
# Replaces the specified entry with the contents of src_path (from
|
@@ -334,11 +246,16 @@ module Zip
|
|
334
246
|
add(entry, src_path)
|
335
247
|
end
|
336
248
|
|
337
|
-
# Extracts entry to file
|
338
|
-
|
249
|
+
# Extracts `entry` to a file at `entry_path`, with `destination_directory`
|
250
|
+
# as the base location in the filesystem.
|
251
|
+
#
|
252
|
+
# NB: The caller is responsible for making sure `destination_directory` is
|
253
|
+
# safe, if it is passed.
|
254
|
+
def extract(entry, entry_path = nil, destination_directory: '.', &block)
|
339
255
|
block ||= proc { ::Zip.on_exists_proc }
|
340
256
|
found_entry = get_entry(entry)
|
341
|
-
found_entry.
|
257
|
+
entry_path ||= found_entry.name
|
258
|
+
found_entry.extract(entry_path, destination_directory: destination_directory, &block)
|
342
259
|
end
|
343
260
|
|
344
261
|
# Commits changes that has been made since the previous commit to
|
@@ -348,22 +265,23 @@ module Zip
|
|
348
265
|
|
349
266
|
on_success_replace do |tmp_file|
|
350
267
|
::Zip::OutputStream.open(tmp_file) do |zos|
|
351
|
-
@
|
268
|
+
@cdir.each do |e|
|
352
269
|
e.write_to_zip_output_stream(zos)
|
353
|
-
e.dirty = false
|
354
270
|
e.clean_up
|
355
271
|
end
|
356
272
|
zos.comment = comment
|
357
273
|
end
|
358
274
|
true
|
359
275
|
end
|
360
|
-
|
276
|
+
initialize_cdir(@name)
|
361
277
|
end
|
362
278
|
|
363
279
|
# Write buffer write changes to buffer and return
|
364
|
-
def write_buffer(io = ::StringIO.new
|
280
|
+
def write_buffer(io = ::StringIO.new)
|
281
|
+
return unless commit_required?
|
282
|
+
|
365
283
|
::Zip::OutputStream.write_buffer(io) do |zos|
|
366
|
-
@
|
284
|
+
@cdir.each { |e| e.write_to_zip_output_stream(zos) }
|
367
285
|
zos.comment = comment
|
368
286
|
end
|
369
287
|
end
|
@@ -376,16 +294,19 @@ module Zip
|
|
376
294
|
# Returns true if any changes has been made to this archive since
|
377
295
|
# the previous commit
|
378
296
|
def commit_required?
|
379
|
-
@
|
380
|
-
|
297
|
+
return true if @create || @cdir.dirty?
|
298
|
+
|
299
|
+
@cdir.each do |e|
|
300
|
+
return true if e.dirty?
|
381
301
|
end
|
382
|
-
|
302
|
+
|
303
|
+
false
|
383
304
|
end
|
384
305
|
|
385
306
|
# Searches for entry with the specified name. Returns nil if
|
386
307
|
# no entry is found. See also get_entry
|
387
308
|
def find_entry(entry_name)
|
388
|
-
selected_entry = @
|
309
|
+
selected_entry = @cdir.find_entry(entry_name)
|
389
310
|
return if selected_entry.nil?
|
390
311
|
|
391
312
|
selected_entry.restore_ownership = @restore_ownership
|
@@ -394,11 +315,6 @@ module Zip
|
|
394
315
|
selected_entry
|
395
316
|
end
|
396
317
|
|
397
|
-
# Searches for entries given a glob
|
398
|
-
def glob(*args, &block)
|
399
|
-
@entry_set.glob(*args, &block)
|
400
|
-
end
|
401
|
-
|
402
318
|
# Searches for an entry just as find_entry, but throws Errno::ENOENT
|
403
319
|
# if no entry is found.
|
404
320
|
def get_entry(entry)
|
@@ -414,33 +330,50 @@ module Zip
|
|
414
330
|
|
415
331
|
entry_name = entry_name.dup.to_s
|
416
332
|
entry_name << '/' unless entry_name.end_with?('/')
|
417
|
-
@
|
333
|
+
@cdir << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
|
418
334
|
end
|
419
335
|
|
420
336
|
private
|
421
337
|
|
422
|
-
def
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
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"
|
430
367
|
end
|
431
|
-
new_entry.directory? && path_is_directory
|
432
368
|
end
|
433
369
|
|
434
370
|
def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
|
371
|
+
return unless @cdir.include?(entry_name)
|
372
|
+
|
435
373
|
continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
|
436
|
-
|
374
|
+
raise ::Zip::EntryExistsError.new proc_name, entry_name unless continue_on_exists_proc.call
|
437
375
|
|
438
|
-
|
439
|
-
remove get_entry(entry_name)
|
440
|
-
else
|
441
|
-
raise ::Zip::EntryExistsError,
|
442
|
-
proc_name + " failed. Entry #{entry_name} already exists"
|
443
|
-
end
|
376
|
+
remove get_entry(entry_name)
|
444
377
|
end
|
445
378
|
|
446
379
|
def check_file(path)
|
@@ -450,14 +383,12 @@ module Zip
|
|
450
383
|
def on_success_replace
|
451
384
|
dirname, basename = ::File.split(name)
|
452
385
|
::Dir::Tmpname.create(basename, dirname) do |tmp_filename|
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
::File.chmod(@file_permissions, name) unless @create
|
457
|
-
end
|
458
|
-
ensure
|
459
|
-
::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
|
460
389
|
end
|
390
|
+
ensure
|
391
|
+
::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
|
461
392
|
end
|
462
393
|
end
|
463
394
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zip
|
4
|
+
module FileSplit # :nodoc:
|
5
|
+
MAX_SEGMENT_SIZE = 3_221_225_472
|
6
|
+
MIN_SEGMENT_SIZE = 65_536
|
7
|
+
DATA_BUFFER_SIZE = 8192
|
8
|
+
|
9
|
+
def get_segment_size_for_split(segment_size)
|
10
|
+
if MIN_SEGMENT_SIZE > segment_size
|
11
|
+
MIN_SEGMENT_SIZE
|
12
|
+
elsif MAX_SEGMENT_SIZE < segment_size
|
13
|
+
MAX_SEGMENT_SIZE
|
14
|
+
else
|
15
|
+
segment_size
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
|
20
|
+
unless partial_zip_file_name.nil?
|
21
|
+
partial_zip_file_name = zip_file_name.sub(
|
22
|
+
/#{::File.basename(zip_file_name)}\z/,
|
23
|
+
partial_zip_file_name + ::File.extname(zip_file_name)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
partial_zip_file_name ||= zip_file_name
|
27
|
+
partial_zip_file_name
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_segment_count_for_split(zip_file_size, segment_size)
|
31
|
+
(zip_file_size / segment_size).to_i +
|
32
|
+
((zip_file_size % segment_size).zero? ? 0 : 1)
|
33
|
+
end
|
34
|
+
|
35
|
+
def put_split_signature(szip_file, segment_size)
|
36
|
+
signature_packed = [SPLIT_FILE_SIGNATURE].pack('V')
|
37
|
+
szip_file << signature_packed
|
38
|
+
segment_size - signature_packed.size
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# TODO: Make the code more understandable
|
43
|
+
#
|
44
|
+
def save_splited_part(
|
45
|
+
zip_file, partial_zip_file_name, zip_file_size,
|
46
|
+
szip_file_index, segment_size, segment_count
|
47
|
+
)
|
48
|
+
ssegment_size = zip_file_size - zip_file.pos
|
49
|
+
ssegment_size = segment_size if ssegment_size > segment_size
|
50
|
+
szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}"
|
51
|
+
::File.open(szip_file_name, 'wb') do |szip_file|
|
52
|
+
if szip_file_index == 1
|
53
|
+
ssegment_size = put_split_signature(szip_file, segment_size)
|
54
|
+
end
|
55
|
+
chunk_bytes = 0
|
56
|
+
until ssegment_size == chunk_bytes || zip_file.eof?
|
57
|
+
segment_bytes_left = ssegment_size - chunk_bytes
|
58
|
+
buffer_size = [segment_bytes_left, DATA_BUFFER_SIZE].min
|
59
|
+
chunk = zip_file.read(buffer_size)
|
60
|
+
chunk_bytes += buffer_size
|
61
|
+
szip_file << chunk
|
62
|
+
# Info for track splitting
|
63
|
+
yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Splits an archive into parts with segment size
|
69
|
+
def split(
|
70
|
+
zip_file_name, segment_size: MAX_SEGMENT_SIZE,
|
71
|
+
delete_original: true, partial_zip_file_name: nil
|
72
|
+
)
|
73
|
+
raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name)
|
74
|
+
raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
|
75
|
+
|
76
|
+
zip_file_size = ::File.size(zip_file_name)
|
77
|
+
segment_size = get_segment_size_for_split(segment_size)
|
78
|
+
return if zip_file_size <= segment_size
|
79
|
+
|
80
|
+
segment_count = get_segment_count_for_split(zip_file_size, segment_size)
|
81
|
+
::Zip::File.open(zip_file_name) {} # Check for correct zip structure.
|
82
|
+
partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
|
83
|
+
szip_file_index = 0
|
84
|
+
::File.open(zip_file_name, 'rb') do |zip_file|
|
85
|
+
until zip_file.eof?
|
86
|
+
szip_file_index += 1
|
87
|
+
save_splited_part(
|
88
|
+
zip_file, partial_zip_file_name, zip_file_size,
|
89
|
+
szip_file_index, segment_size, segment_count
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
::File.delete(zip_file_name) if delete_original
|
94
|
+
szip_file_index
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|