rwdshell 0.97 → 0.98
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.
- data/Readme.txt +6 -0
- data/code/01rwdcore/01rwdcore.rb +4 -2
- data/code/01rwdcore/test_cases.rb +126 -0
- data/code/01rwdcore/test_harness.rb +15 -0
- data/code/01rwdcore/uploadreturns.rb +62 -0
- data/code/superant.com.rwdtinkerbackwindow/diagnostictab.rb +14 -10
- data/configuration/language.dist +1 -1
- data/configuration/rwdapplicationidentity.dist +2 -2
- data/configuration/rwdshell.dist +2 -2
- data/configuration/rwdtinker.dist +2 -2
- data/configuration/tinkerwin2variables.dist +1 -1
- data/extras/zip/ioextras.rb +114 -0
- data/extras/zip/stdrubyext.rb +111 -0
- data/extras/zip/tempfile_bugfixed.rb +195 -0
- data/extras/zip/zip.rb +1377 -0
- data/extras/zip/zipfilesystem.rb +558 -0
- data/extras/zip/ziprequire.rb +61 -0
- data/gui/helpaboutinstalled/superant.com.tinkerhelpabout/3copyright.rwd +1 -1
- data/gui/tinkerbackwindows/superant.com.rwdshellbackwindow/46editscriptrecord.rwd +5 -5
- data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/70rwddiagnostics.rwd +12 -16
- data/init.rb +3 -0
- data/rwd_files/HowTo_Shell.txt +4 -0
- data/rwd_files/HowTo_Tinker.txt +14 -0
- data/rwdconfig.dist +6 -2
- data/tests/makedist.rb +16 -1
- metadata +12 -17
- data/extras/cmdline_parse +0 -47
- data/extras/config_file +0 -69
- data/extras/errorMsg +0 -19
- data/extras/makePlaylist +0 -34
- data/extras/mp3controld +0 -289
- data/extras/playlist +0 -186
- data/extras/showHelp +0 -18
- data/gui/helpaboutinstalled/superant.com.rwdwin2helpabout/1appname.rwd +0 -4
- data/gui/helpaboutinstalled/superant.com.rwdwin2helpabout/3copyright.rwd +0 -3
- data/gui/helpaboutinstalled/superant.com.rwdwin2helpabout/5version.rwd +0 -10
- data/installed/rwdtinkerwin2-0.5.inf +0 -8
data/extras/zip/zip.rb
ADDED
@@ -0,0 +1,1377 @@
|
|
1
|
+
|
2
|
+
require 'delegate'
|
3
|
+
require 'singleton'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'ftools'
|
6
|
+
require 'zlib'
|
7
|
+
require 'extras/zip/stdrubyext'
|
8
|
+
require 'extras/zip/ioextras'
|
9
|
+
|
10
|
+
if Tempfile.superclass == SimpleDelegator
|
11
|
+
require 'zip/tempfile_bugfixed'
|
12
|
+
Tempfile = BugFix::Tempfile
|
13
|
+
end
|
14
|
+
|
15
|
+
module Zlib
|
16
|
+
if ! const_defined? :MAX_WBITS
|
17
|
+
MAX_WBITS = Zlib::Deflate.MAX_WBITS
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Zip
|
22
|
+
|
23
|
+
RUBY_MINOR_VERSION = RUBY_VERSION.split(".")[1].to_i
|
24
|
+
|
25
|
+
# Ruby 1.7.x compatibility
|
26
|
+
# In ruby 1.6.x and 1.8.0 reading from an empty stream returns
|
27
|
+
# an empty string the first time and then nil.
|
28
|
+
# not so in 1.7.x
|
29
|
+
EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST = RUBY_MINOR_VERSION != 7
|
30
|
+
|
31
|
+
class ZipInputStream
|
32
|
+
include IOExtras::AbstractInputStream
|
33
|
+
|
34
|
+
def initialize(filename, offset = 0)
|
35
|
+
super()
|
36
|
+
@archiveIO = File.open(filename, "rb")
|
37
|
+
@archiveIO.seek(offset, IO::SEEK_SET)
|
38
|
+
@decompressor = NullDecompressor.instance
|
39
|
+
@currentEntry = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def close
|
43
|
+
@archiveIO.close
|
44
|
+
end
|
45
|
+
|
46
|
+
def ZipInputStream.open(filename)
|
47
|
+
return new(filename) unless block_given?
|
48
|
+
|
49
|
+
zio = new(filename)
|
50
|
+
yield zio
|
51
|
+
ensure
|
52
|
+
zio.close if zio
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_next_entry
|
56
|
+
@archiveIO.seek(@currentEntry.next_header_offset,
|
57
|
+
IO::SEEK_SET) if @currentEntry
|
58
|
+
open_entry
|
59
|
+
end
|
60
|
+
|
61
|
+
def rewind
|
62
|
+
return if @currentEntry.nil?
|
63
|
+
@lineno = 0
|
64
|
+
@archiveIO.seek(@currentEntry.localHeaderOffset,
|
65
|
+
IO::SEEK_SET)
|
66
|
+
open_entry
|
67
|
+
end
|
68
|
+
|
69
|
+
def open_entry
|
70
|
+
@currentEntry = ZipEntry.read_local_entry(@archiveIO)
|
71
|
+
if (@currentEntry == nil)
|
72
|
+
@decompressor = NullDecompressor.instance
|
73
|
+
elsif @currentEntry.compression_method == ZipEntry::STORED
|
74
|
+
@decompressor = PassThruDecompressor.new(@archiveIO,
|
75
|
+
@currentEntry.size)
|
76
|
+
elsif @currentEntry.compression_method == ZipEntry::DEFLATED
|
77
|
+
@decompressor = Inflater.new(@archiveIO)
|
78
|
+
else
|
79
|
+
raise ZipCompressionMethodError,
|
80
|
+
"Unsupported compression method #{@currentEntry.compression_method}"
|
81
|
+
end
|
82
|
+
flush
|
83
|
+
return @currentEntry
|
84
|
+
end
|
85
|
+
|
86
|
+
def read(numberOfBytes = nil)
|
87
|
+
@decompressor.read(numberOfBytes)
|
88
|
+
end
|
89
|
+
protected
|
90
|
+
def produce_input
|
91
|
+
@decompressor.produce_input
|
92
|
+
end
|
93
|
+
|
94
|
+
def input_finished?
|
95
|
+
@decompressor.input_finished?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
class Decompressor #:nodoc:all
|
102
|
+
CHUNK_SIZE=32768
|
103
|
+
def initialize(inputStream)
|
104
|
+
super()
|
105
|
+
@inputStream=inputStream
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class Inflater < Decompressor #:nodoc:all
|
110
|
+
def initialize(inputStream)
|
111
|
+
super
|
112
|
+
@zlibInflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
113
|
+
@outputBuffer=""
|
114
|
+
@hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST
|
115
|
+
end
|
116
|
+
|
117
|
+
def read(numberOfBytes = nil)
|
118
|
+
readEverything = (numberOfBytes == nil)
|
119
|
+
while (readEverything || @outputBuffer.length < numberOfBytes)
|
120
|
+
break if internal_input_finished?
|
121
|
+
@outputBuffer << internal_produce_input
|
122
|
+
end
|
123
|
+
return value_when_finished if @outputBuffer.length==0 && input_finished?
|
124
|
+
endIndex= numberOfBytes==nil ? @outputBuffer.length : numberOfBytes
|
125
|
+
return @outputBuffer.slice!(0...endIndex)
|
126
|
+
end
|
127
|
+
|
128
|
+
def produce_input
|
129
|
+
if (@outputBuffer.empty?)
|
130
|
+
return internal_produce_input
|
131
|
+
else
|
132
|
+
return @outputBuffer.slice!(0...(@outputBuffer.length))
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# to be used with produce_input, not read (as read may still have more data cached)
|
137
|
+
def input_finished?
|
138
|
+
@outputBuffer.empty? && internal_input_finished?
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def internal_produce_input
|
144
|
+
@zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE))
|
145
|
+
end
|
146
|
+
|
147
|
+
def internal_input_finished?
|
148
|
+
@zlibInflater.finished?
|
149
|
+
end
|
150
|
+
|
151
|
+
# TODO: Specialize to handle different behaviour in ruby > 1.7.0 ?
|
152
|
+
def value_when_finished # mimic behaviour of ruby File object.
|
153
|
+
return nil if @hasReturnedEmptyString
|
154
|
+
@hasReturnedEmptyString=true
|
155
|
+
return ""
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class PassThruDecompressor < Decompressor #:nodoc:all
|
160
|
+
def initialize(inputStream, charsToRead)
|
161
|
+
super inputStream
|
162
|
+
@charsToRead = charsToRead
|
163
|
+
@readSoFar = 0
|
164
|
+
@hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST
|
165
|
+
end
|
166
|
+
|
167
|
+
# TODO: Specialize to handle different behaviour in ruby > 1.7.0 ?
|
168
|
+
def read(numberOfBytes = nil)
|
169
|
+
if input_finished?
|
170
|
+
hasReturnedEmptyStringVal=@hasReturnedEmptyString
|
171
|
+
@hasReturnedEmptyString=true
|
172
|
+
return "" unless hasReturnedEmptyStringVal
|
173
|
+
return nil
|
174
|
+
end
|
175
|
+
|
176
|
+
if (numberOfBytes == nil || @readSoFar+numberOfBytes > @charsToRead)
|
177
|
+
numberOfBytes = @charsToRead-@readSoFar
|
178
|
+
end
|
179
|
+
@readSoFar += numberOfBytes
|
180
|
+
@inputStream.read(numberOfBytes)
|
181
|
+
end
|
182
|
+
|
183
|
+
def produce_input
|
184
|
+
read(Decompressor::CHUNK_SIZE)
|
185
|
+
end
|
186
|
+
|
187
|
+
def input_finished?
|
188
|
+
(@readSoFar >= @charsToRead)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class NullDecompressor #:nodoc:all
|
193
|
+
include Singleton
|
194
|
+
def read(numberOfBytes = nil)
|
195
|
+
nil
|
196
|
+
end
|
197
|
+
|
198
|
+
def produce_input
|
199
|
+
nil
|
200
|
+
end
|
201
|
+
|
202
|
+
def input_finished?
|
203
|
+
true
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
class NullInputStream < NullDecompressor #:nodoc:all
|
208
|
+
include IOExtras::AbstractInputStream
|
209
|
+
end
|
210
|
+
|
211
|
+
class ZipEntry
|
212
|
+
STORED = 0
|
213
|
+
DEFLATED = 8
|
214
|
+
|
215
|
+
attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
|
216
|
+
:name, :size, :localHeaderOffset, :zipfile, :fstype, :externalFileAttributes
|
217
|
+
|
218
|
+
def initialize(zipfile = "", name = "", comment = "", extra = "",
|
219
|
+
compressed_size = 0, crc = 0,
|
220
|
+
compression_method = ZipEntry::DEFLATED, size = 0,
|
221
|
+
time = Time.now)
|
222
|
+
super()
|
223
|
+
if name.starts_with("/")
|
224
|
+
raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
|
225
|
+
end
|
226
|
+
@localHeaderOffset = 0
|
227
|
+
@internalFileAttributes = 1
|
228
|
+
@externalFileAttributes = 0
|
229
|
+
@version = 52 # this library's version
|
230
|
+
@fstype = 0 # default is fat
|
231
|
+
@zipfile, @comment, @compressed_size, @crc, @extra, @compression_method,
|
232
|
+
@name, @size = zipfile, comment, compressed_size, crc,
|
233
|
+
extra, compression_method, name, size
|
234
|
+
@time = time
|
235
|
+
unless ZipExtraField === @extra
|
236
|
+
@extra = ZipExtraField.new(@extra.to_s)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def time
|
241
|
+
if @extra["UniversalTime"]
|
242
|
+
@extra["UniversalTime"].mtime
|
243
|
+
else
|
244
|
+
# Atandard time field in central directory has local time
|
245
|
+
# under archive creator. Then, we can't get timezone.
|
246
|
+
@time
|
247
|
+
end
|
248
|
+
end
|
249
|
+
alias :mtime :time
|
250
|
+
|
251
|
+
def time=(aTime)
|
252
|
+
unless @extra.member?("UniversalTime")
|
253
|
+
@extra.create("UniversalTime")
|
254
|
+
end
|
255
|
+
@extra["UniversalTime"].mtime = aTime
|
256
|
+
@time = aTime
|
257
|
+
end
|
258
|
+
|
259
|
+
def directory?
|
260
|
+
return (%r{\/$} =~ @name) != nil
|
261
|
+
end
|
262
|
+
alias :is_directory :directory?
|
263
|
+
|
264
|
+
def file?
|
265
|
+
! directory?
|
266
|
+
end
|
267
|
+
|
268
|
+
def local_entry_offset #:nodoc:all
|
269
|
+
localHeaderOffset + local_header_size
|
270
|
+
end
|
271
|
+
|
272
|
+
def local_header_size #:nodoc:all
|
273
|
+
LOCAL_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) + (@extra ? @extra.local_size : 0)
|
274
|
+
end
|
275
|
+
|
276
|
+
def cdir_header_size #:nodoc:all
|
277
|
+
CDIR_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) +
|
278
|
+
(@extra ? @extra.c_dir_size : 0) + (@comment ? @comment.size : 0)
|
279
|
+
end
|
280
|
+
|
281
|
+
def next_header_offset #:nodoc:all
|
282
|
+
local_entry_offset + self.compressed_size
|
283
|
+
end
|
284
|
+
|
285
|
+
def to_s
|
286
|
+
@name
|
287
|
+
end
|
288
|
+
|
289
|
+
protected
|
290
|
+
|
291
|
+
def ZipEntry.read_zip_short(io)
|
292
|
+
io.read(2).unpack('v')[0]
|
293
|
+
end
|
294
|
+
|
295
|
+
def ZipEntry.read_zip_long(io)
|
296
|
+
io.read(4).unpack('V')[0]
|
297
|
+
end
|
298
|
+
public
|
299
|
+
|
300
|
+
LOCAL_ENTRY_SIGNATURE = 0x04034b50
|
301
|
+
LOCAL_ENTRY_STATIC_HEADER_LENGTH = 30
|
302
|
+
|
303
|
+
def read_local_entry(io) #:nodoc:all
|
304
|
+
@localHeaderOffset = io.tell
|
305
|
+
staticSizedFieldsBuf = io.read(LOCAL_ENTRY_STATIC_HEADER_LENGTH)
|
306
|
+
unless (staticSizedFieldsBuf.size==LOCAL_ENTRY_STATIC_HEADER_LENGTH)
|
307
|
+
raise ZipError, "Premature end of file. Not enough data for zip entry local header"
|
308
|
+
end
|
309
|
+
|
310
|
+
localHeader ,
|
311
|
+
@version ,
|
312
|
+
@fstype ,
|
313
|
+
@gpFlags ,
|
314
|
+
@compression_method,
|
315
|
+
lastModTime ,
|
316
|
+
lastModDate ,
|
317
|
+
@crc ,
|
318
|
+
@compressed_size ,
|
319
|
+
@size ,
|
320
|
+
nameLength ,
|
321
|
+
extraLength = staticSizedFieldsBuf.unpack('VCCvvvvVVVvv')
|
322
|
+
|
323
|
+
unless (localHeader == LOCAL_ENTRY_SIGNATURE)
|
324
|
+
raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
|
325
|
+
end
|
326
|
+
set_time(lastModDate, lastModTime)
|
327
|
+
|
328
|
+
@name = io.read(nameLength)
|
329
|
+
extra = io.read(extraLength)
|
330
|
+
|
331
|
+
if (extra && extra.length != extraLength)
|
332
|
+
raise ZipError, "Truncated local zip entry header"
|
333
|
+
else
|
334
|
+
if ZipExtraField === @extra
|
335
|
+
@extra.merge(extra)
|
336
|
+
else
|
337
|
+
@extra = ZipExtraField.new(extra)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def ZipEntry.read_local_entry(io)
|
343
|
+
entry = new(io.path)
|
344
|
+
entry.read_local_entry(io)
|
345
|
+
return entry
|
346
|
+
rescue ZipError
|
347
|
+
return nil
|
348
|
+
end
|
349
|
+
|
350
|
+
def write_local_entry(io) #:nodoc:all
|
351
|
+
@localHeaderOffset = io.tell
|
352
|
+
|
353
|
+
io <<
|
354
|
+
[LOCAL_ENTRY_SIGNATURE ,
|
355
|
+
0 ,
|
356
|
+
0 , # @gpFlags ,
|
357
|
+
@compression_method ,
|
358
|
+
@time.to_binary_dos_time , # @lastModTime ,
|
359
|
+
@time.to_binary_dos_date , # @lastModDate ,
|
360
|
+
@crc ,
|
361
|
+
@compressed_size ,
|
362
|
+
@size ,
|
363
|
+
@name ? @name.length : 0,
|
364
|
+
@extra? @extra.local_length : 0 ].pack('VvvvvvVVVvv')
|
365
|
+
io << @name
|
366
|
+
io << (@extra ? @extra.to_local_bin : "")
|
367
|
+
end
|
368
|
+
|
369
|
+
CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50
|
370
|
+
CDIR_ENTRY_STATIC_HEADER_LENGTH = 46
|
371
|
+
|
372
|
+
def read_c_dir_entry(io) #:nodoc:all
|
373
|
+
staticSizedFieldsBuf = io.read(CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
374
|
+
unless (staticSizedFieldsBuf.size == CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
375
|
+
raise ZipError, "Premature end of file. Not enough data for zip cdir entry header"
|
376
|
+
end
|
377
|
+
|
378
|
+
cdirSignature ,
|
379
|
+
@version , # version of encoding software
|
380
|
+
@fstype , # filesystem type
|
381
|
+
@versionNeededToExtract,
|
382
|
+
@gpFlags ,
|
383
|
+
@compression_method ,
|
384
|
+
lastModTime ,
|
385
|
+
lastModDate ,
|
386
|
+
@crc ,
|
387
|
+
@compressed_size ,
|
388
|
+
@size ,
|
389
|
+
nameLength ,
|
390
|
+
extraLength ,
|
391
|
+
commentLength ,
|
392
|
+
diskNumberStart ,
|
393
|
+
@internalFileAttributes,
|
394
|
+
@externalFileAttributes,
|
395
|
+
@localHeaderOffset ,
|
396
|
+
@name ,
|
397
|
+
@extra ,
|
398
|
+
@comment = staticSizedFieldsBuf.unpack('VCCvvvvvVVVvvvvvVV')
|
399
|
+
|
400
|
+
unless (cdirSignature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE)
|
401
|
+
raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
|
402
|
+
end
|
403
|
+
set_time(lastModDate, lastModTime)
|
404
|
+
|
405
|
+
@name = io.read(nameLength)
|
406
|
+
if ZipExtraField === @extra
|
407
|
+
@extra.merge(io.read(extraLength))
|
408
|
+
else
|
409
|
+
@extra = ZipExtraField.new(io.read(extraLength))
|
410
|
+
end
|
411
|
+
@comment = io.read(commentLength)
|
412
|
+
unless (@comment && @comment.length == commentLength)
|
413
|
+
raise ZipError, "Truncated cdir zip entry header"
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
def ZipEntry.read_c_dir_entry(io) #:nodoc:all
|
418
|
+
entry = new(io.path)
|
419
|
+
entry.read_c_dir_entry(io)
|
420
|
+
return entry
|
421
|
+
rescue ZipError
|
422
|
+
return nil
|
423
|
+
end
|
424
|
+
|
425
|
+
|
426
|
+
def write_c_dir_entry(io) #:nodoc:all
|
427
|
+
io <<
|
428
|
+
[CENTRAL_DIRECTORY_ENTRY_SIGNATURE,
|
429
|
+
@version , # version of encoding software
|
430
|
+
@fstype , # filesystem type
|
431
|
+
0 , # @versionNeededToExtract ,
|
432
|
+
0 , # @gpFlags ,
|
433
|
+
@compression_method ,
|
434
|
+
@time.to_binary_dos_time , # @lastModTime ,
|
435
|
+
@time.to_binary_dos_date , # @lastModDate ,
|
436
|
+
@crc ,
|
437
|
+
@compressed_size ,
|
438
|
+
@size ,
|
439
|
+
@name ? @name.length : 0 ,
|
440
|
+
@extra ? @extra.c_dir_length : 0 ,
|
441
|
+
@comment ? comment.length : 0 ,
|
442
|
+
0 , # disk number start
|
443
|
+
@internalFileAttributes , # file type (binary=0, text=1)
|
444
|
+
@externalFileAttributes , # native filesystem attributes
|
445
|
+
@localHeaderOffset ,
|
446
|
+
@name ,
|
447
|
+
@extra ,
|
448
|
+
@comment ].pack('VCCvvvvvVVVvvvvvVV')
|
449
|
+
|
450
|
+
io << @name
|
451
|
+
io << (@extra ? @extra.to_c_dir_bin : "")
|
452
|
+
io << @comment
|
453
|
+
end
|
454
|
+
|
455
|
+
def == (other)
|
456
|
+
return false unless other.class == ZipEntry
|
457
|
+
# Compares contents of local entry and exposed fields
|
458
|
+
(@compression_method == other.compression_method &&
|
459
|
+
@crc == other.crc &&
|
460
|
+
@compressed_size == other.compressed_size &&
|
461
|
+
@size == other.size &&
|
462
|
+
@name == other.name &&
|
463
|
+
@extra == other.extra &&
|
464
|
+
self.time.dos_equals(other.time))
|
465
|
+
end
|
466
|
+
|
467
|
+
def <=> (other)
|
468
|
+
return to_s <=> other.to_s
|
469
|
+
end
|
470
|
+
|
471
|
+
def get_input_stream
|
472
|
+
zis = ZipInputStream.new(@zipfile, localHeaderOffset)
|
473
|
+
zis.get_next_entry
|
474
|
+
if block_given?
|
475
|
+
begin
|
476
|
+
return yield(zis)
|
477
|
+
ensure
|
478
|
+
zis.close
|
479
|
+
end
|
480
|
+
else
|
481
|
+
return zis
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
|
486
|
+
def write_to_zip_output_stream(aZipOutputStream) #:nodoc:all
|
487
|
+
aZipOutputStream.copy_raw_entry(self)
|
488
|
+
end
|
489
|
+
|
490
|
+
def parent_as_string
|
491
|
+
entry_name = name.chomp("/")
|
492
|
+
slash_index = entry_name.rindex("/")
|
493
|
+
slash_index ? entry_name.slice(0, slash_index+1) : nil
|
494
|
+
end
|
495
|
+
|
496
|
+
def get_raw_input_stream(&aProc)
|
497
|
+
File.open(@zipfile, "rb", &aProc)
|
498
|
+
end
|
499
|
+
|
500
|
+
private
|
501
|
+
def set_time(binaryDosDate, binaryDosTime)
|
502
|
+
@time = Time.parse_binary_dos_format(binaryDosDate, binaryDosTime)
|
503
|
+
rescue ArgumentError
|
504
|
+
puts "Invalid date/time in zip entry"
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
|
509
|
+
class ZipOutputStream
|
510
|
+
include IOExtras::AbstractOutputStream
|
511
|
+
|
512
|
+
attr_accessor :comment
|
513
|
+
|
514
|
+
def initialize(fileName)
|
515
|
+
super()
|
516
|
+
@fileName = fileName
|
517
|
+
@outputStream = File.new(@fileName, "wb")
|
518
|
+
@entrySet = ZipEntrySet.new
|
519
|
+
@compressor = NullCompressor.instance
|
520
|
+
@closed = false
|
521
|
+
@currentEntry = nil
|
522
|
+
@comment = nil
|
523
|
+
end
|
524
|
+
|
525
|
+
def ZipOutputStream.open(fileName)
|
526
|
+
return new(fileName) unless block_given?
|
527
|
+
zos = new(fileName)
|
528
|
+
yield zos
|
529
|
+
ensure
|
530
|
+
zos.close if zos
|
531
|
+
end
|
532
|
+
|
533
|
+
def close
|
534
|
+
return if @closed
|
535
|
+
finalize_current_entry
|
536
|
+
update_local_headers
|
537
|
+
write_central_directory
|
538
|
+
@outputStream.close
|
539
|
+
@closed = true
|
540
|
+
end
|
541
|
+
|
542
|
+
def put_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION)
|
543
|
+
raise ZipError, "zip stream is closed" if @closed
|
544
|
+
newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@fileName, entry.to_s)
|
545
|
+
init_next_entry(newEntry)
|
546
|
+
@currentEntry=newEntry
|
547
|
+
end
|
548
|
+
|
549
|
+
def copy_raw_entry(entry)
|
550
|
+
entry = entry.dup
|
551
|
+
raise ZipError, "zip stream is closed" if @closed
|
552
|
+
raise ZipError, "entry is not a ZipEntry" if !entry.kind_of?(ZipEntry)
|
553
|
+
finalize_current_entry
|
554
|
+
@entrySet << entry
|
555
|
+
src_pos = entry.local_entry_offset
|
556
|
+
entry.write_local_entry(@outputStream)
|
557
|
+
@compressor = NullCompressor.instance
|
558
|
+
@outputStream << entry.get_raw_input_stream {
|
559
|
+
|is|
|
560
|
+
is.seek(src_pos, IO::SEEK_SET)
|
561
|
+
is.read(entry.compressed_size)
|
562
|
+
}
|
563
|
+
@compressor = NullCompressor.instance
|
564
|
+
@currentEntry = nil
|
565
|
+
end
|
566
|
+
|
567
|
+
private
|
568
|
+
def finalize_current_entry
|
569
|
+
return unless @currentEntry
|
570
|
+
finish
|
571
|
+
@currentEntry.compressed_size = @outputStream.tell - @currentEntry.localHeaderOffset -
|
572
|
+
@currentEntry.local_header_size
|
573
|
+
@currentEntry.size = @compressor.size
|
574
|
+
@currentEntry.crc = @compressor.crc
|
575
|
+
@currentEntry = nil
|
576
|
+
@compressor = NullCompressor.instance
|
577
|
+
end
|
578
|
+
|
579
|
+
def init_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION)
|
580
|
+
finalize_current_entry
|
581
|
+
@entrySet << entry
|
582
|
+
entry.write_local_entry(@outputStream)
|
583
|
+
@compressor = get_compressor(entry, level)
|
584
|
+
end
|
585
|
+
|
586
|
+
def get_compressor(entry, level)
|
587
|
+
case entry.compression_method
|
588
|
+
when ZipEntry::DEFLATED then Deflater.new(@outputStream, level)
|
589
|
+
when ZipEntry::STORED then PassThruCompressor.new(@outputStream)
|
590
|
+
else raise ZipCompressionMethodError,
|
591
|
+
"Invalid compression method: '#{entry.compression_method}'"
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
def update_local_headers
|
596
|
+
pos = @outputStream.tell
|
597
|
+
@entrySet.each {
|
598
|
+
|entry|
|
599
|
+
@outputStream.pos = entry.localHeaderOffset
|
600
|
+
entry.write_local_entry(@outputStream)
|
601
|
+
}
|
602
|
+
@outputStream.pos = pos
|
603
|
+
end
|
604
|
+
|
605
|
+
def write_central_directory
|
606
|
+
cdir = ZipCentralDirectory.new(@entrySet, @comment)
|
607
|
+
cdir.write_to_stream(@outputStream)
|
608
|
+
end
|
609
|
+
|
610
|
+
protected
|
611
|
+
|
612
|
+
def finish
|
613
|
+
@compressor.finish
|
614
|
+
end
|
615
|
+
|
616
|
+
public
|
617
|
+
def << (data)
|
618
|
+
@compressor << data
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
|
623
|
+
class Compressor #:nodoc:all
|
624
|
+
def finish
|
625
|
+
end
|
626
|
+
end
|
627
|
+
|
628
|
+
class PassThruCompressor < Compressor #:nodoc:all
|
629
|
+
def initialize(outputStream)
|
630
|
+
super()
|
631
|
+
@outputStream = outputStream
|
632
|
+
@crc = Zlib::crc32
|
633
|
+
@size = 0
|
634
|
+
end
|
635
|
+
|
636
|
+
def << (data)
|
637
|
+
val = data.to_s
|
638
|
+
@crc = Zlib::crc32(val, @crc)
|
639
|
+
@size += val.size
|
640
|
+
@outputStream << val
|
641
|
+
end
|
642
|
+
|
643
|
+
attr_reader :size, :crc
|
644
|
+
end
|
645
|
+
|
646
|
+
class NullCompressor < Compressor #:nodoc:all
|
647
|
+
include Singleton
|
648
|
+
|
649
|
+
def << (data)
|
650
|
+
raise IOError, "closed stream"
|
651
|
+
end
|
652
|
+
|
653
|
+
attr_reader :size, :compressed_size
|
654
|
+
end
|
655
|
+
|
656
|
+
class Deflater < Compressor #:nodoc:all
|
657
|
+
def initialize(outputStream, level = Zlib::DEFAULT_COMPRESSION)
|
658
|
+
super()
|
659
|
+
@outputStream = outputStream
|
660
|
+
@zlibDeflater = Zlib::Deflate.new(level, -Zlib::MAX_WBITS)
|
661
|
+
@size = 0
|
662
|
+
@crc = Zlib::crc32
|
663
|
+
end
|
664
|
+
|
665
|
+
def << (data)
|
666
|
+
val = data.to_s
|
667
|
+
@crc = Zlib::crc32(val, @crc)
|
668
|
+
@size += val.size
|
669
|
+
@outputStream << @zlibDeflater.deflate(data)
|
670
|
+
end
|
671
|
+
|
672
|
+
def finish
|
673
|
+
until @zlibDeflater.finished?
|
674
|
+
@outputStream << @zlibDeflater.finish
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
attr_reader :size, :crc
|
679
|
+
end
|
680
|
+
|
681
|
+
|
682
|
+
class ZipEntrySet
|
683
|
+
include Enumerable
|
684
|
+
|
685
|
+
def initialize(anEnumerable = [])
|
686
|
+
super()
|
687
|
+
@entrySet = {}
|
688
|
+
anEnumerable.each { |o| push(o) }
|
689
|
+
end
|
690
|
+
|
691
|
+
def include?(entry)
|
692
|
+
@entrySet.include?(entry.to_s)
|
693
|
+
end
|
694
|
+
|
695
|
+
def <<(entry)
|
696
|
+
@entrySet[entry.to_s] = entry
|
697
|
+
end
|
698
|
+
alias :push :<<
|
699
|
+
|
700
|
+
def size
|
701
|
+
@entrySet.size
|
702
|
+
end
|
703
|
+
alias :length :size
|
704
|
+
|
705
|
+
def delete(entry)
|
706
|
+
@entrySet.delete(entry.to_s) ? entry : nil
|
707
|
+
end
|
708
|
+
|
709
|
+
def each(&aProc)
|
710
|
+
@entrySet.values.each(&aProc)
|
711
|
+
end
|
712
|
+
|
713
|
+
def entries
|
714
|
+
@entrySet.values
|
715
|
+
end
|
716
|
+
|
717
|
+
# deep clone
|
718
|
+
def dup
|
719
|
+
newZipEntrySet = ZipEntrySet.new(@entrySet.values.map { |e| e.dup })
|
720
|
+
end
|
721
|
+
|
722
|
+
def == (other)
|
723
|
+
return false unless other.kind_of?(ZipEntrySet)
|
724
|
+
return @entrySet == other.entrySet
|
725
|
+
end
|
726
|
+
|
727
|
+
def parent(entry)
|
728
|
+
@entrySet[entry.parent_as_string]
|
729
|
+
end
|
730
|
+
|
731
|
+
#TODO attr_accessor :auto_create_directories
|
732
|
+
protected
|
733
|
+
attr_accessor :entrySet
|
734
|
+
end
|
735
|
+
|
736
|
+
|
737
|
+
class ZipCentralDirectory #:nodoc:all
|
738
|
+
include Enumerable
|
739
|
+
|
740
|
+
END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50
|
741
|
+
MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE = 65536 + 18
|
742
|
+
STATIC_EOCD_SIZE = 22
|
743
|
+
|
744
|
+
attr_reader :comment
|
745
|
+
|
746
|
+
def entries
|
747
|
+
@entrySet.entries
|
748
|
+
end
|
749
|
+
|
750
|
+
def initialize(entries = ZipEntrySet.new, comment = "")
|
751
|
+
super()
|
752
|
+
@entrySet = entries.kind_of?(ZipEntrySet) ? entries : ZipEntrySet.new(entries)
|
753
|
+
@comment = comment
|
754
|
+
end
|
755
|
+
|
756
|
+
def write_to_stream(io)
|
757
|
+
offset = io.tell
|
758
|
+
@entrySet.each { |entry| entry.write_c_dir_entry(io) }
|
759
|
+
write_e_o_c_d(io, offset)
|
760
|
+
end
|
761
|
+
|
762
|
+
def write_e_o_c_d(io, offset)
|
763
|
+
io <<
|
764
|
+
[END_OF_CENTRAL_DIRECTORY_SIGNATURE,
|
765
|
+
0 , # @numberOfThisDisk
|
766
|
+
0 , # @numberOfDiskWithStartOfCDir
|
767
|
+
@entrySet? @entrySet.size : 0 ,
|
768
|
+
@entrySet? @entrySet.size : 0 ,
|
769
|
+
cdir_size ,
|
770
|
+
offset ,
|
771
|
+
@comment ? @comment.length : 0 ].pack('VvvvvVVv')
|
772
|
+
io << @comment
|
773
|
+
end
|
774
|
+
private :write_e_o_c_d
|
775
|
+
|
776
|
+
def cdir_size
|
777
|
+
# does not include eocd
|
778
|
+
@entrySet.inject(0) { |value, entry| entry.cdir_header_size + value }
|
779
|
+
end
|
780
|
+
private :cdir_size
|
781
|
+
|
782
|
+
def read_e_o_c_d(io)
|
783
|
+
buf = get_e_o_c_d(io)
|
784
|
+
@numberOfThisDisk = ZipEntry::read_zip_short(buf)
|
785
|
+
@numberOfDiskWithStartOfCDir = ZipEntry::read_zip_short(buf)
|
786
|
+
@totalNumberOfEntriesInCDirOnThisDisk = ZipEntry::read_zip_short(buf)
|
787
|
+
@size = ZipEntry::read_zip_short(buf)
|
788
|
+
@sizeInBytes = ZipEntry::read_zip_long(buf)
|
789
|
+
@cdirOffset = ZipEntry::read_zip_long(buf)
|
790
|
+
commentLength = ZipEntry::read_zip_short(buf)
|
791
|
+
@comment = buf.read(commentLength)
|
792
|
+
raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0
|
793
|
+
end
|
794
|
+
|
795
|
+
def read_central_directory_entries(io)
|
796
|
+
begin
|
797
|
+
io.seek(@cdirOffset, IO::SEEK_SET)
|
798
|
+
rescue Errno::EINVAL
|
799
|
+
raise ZipError, "Zip consistency problem while reading central directory entry"
|
800
|
+
end
|
801
|
+
@entrySet = ZipEntrySet.new
|
802
|
+
@size.times {
|
803
|
+
@entrySet << ZipEntry.read_c_dir_entry(io)
|
804
|
+
}
|
805
|
+
end
|
806
|
+
|
807
|
+
def read_from_stream(io)
|
808
|
+
read_e_o_c_d(io)
|
809
|
+
read_central_directory_entries(io)
|
810
|
+
end
|
811
|
+
|
812
|
+
def get_e_o_c_d(io)
|
813
|
+
begin
|
814
|
+
io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END)
|
815
|
+
rescue Errno::EINVAL
|
816
|
+
io.seek(0, IO::SEEK_SET)
|
817
|
+
rescue Errno::EFBIG # FreeBSD 4.9 returns Errno::EFBIG instead of Errno::EINVAL
|
818
|
+
io.seek(0, IO::SEEK_SET)
|
819
|
+
end
|
820
|
+
buf = io.read
|
821
|
+
sigIndex = buf.rindex([END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack('V'))
|
822
|
+
raise ZipError, "Zip end of central directory signature not found" unless sigIndex
|
823
|
+
buf=buf.slice!((sigIndex+4)...(buf.size))
|
824
|
+
def buf.read(count)
|
825
|
+
slice!(0, count)
|
826
|
+
end
|
827
|
+
return buf
|
828
|
+
end
|
829
|
+
|
830
|
+
def each(&proc)
|
831
|
+
@entrySet.each(&proc)
|
832
|
+
end
|
833
|
+
|
834
|
+
def size
|
835
|
+
@entrySet.size
|
836
|
+
end
|
837
|
+
|
838
|
+
def ZipCentralDirectory.read_from_stream(io)
|
839
|
+
cdir = new
|
840
|
+
cdir.read_from_stream(io)
|
841
|
+
return cdir
|
842
|
+
rescue ZipError
|
843
|
+
return nil
|
844
|
+
end
|
845
|
+
|
846
|
+
def == (other)
|
847
|
+
return false unless other.kind_of?(ZipCentralDirectory)
|
848
|
+
@entrySet.entries.sort == other.entries.sort && comment == other.comment
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
|
853
|
+
class ZipError < StandardError ; end
|
854
|
+
|
855
|
+
class ZipEntryExistsError < ZipError; end
|
856
|
+
class ZipDestinationFileExistsError < ZipError; end
|
857
|
+
class ZipCompressionMethodError < ZipError; end
|
858
|
+
class ZipEntryNameError < ZipError; end
|
859
|
+
|
860
|
+
class ZipFile < ZipCentralDirectory
|
861
|
+
|
862
|
+
CREATE = 1
|
863
|
+
|
864
|
+
attr_reader :name
|
865
|
+
|
866
|
+
def initialize(fileName, create = nil)
|
867
|
+
super()
|
868
|
+
@name = fileName
|
869
|
+
@comment = ""
|
870
|
+
if (File.exists?(fileName))
|
871
|
+
File.open(name, "rb") { |f| read_from_stream(f) }
|
872
|
+
elsif (create == ZipFile::CREATE)
|
873
|
+
@entrySet = ZipEntrySet.new
|
874
|
+
else
|
875
|
+
raise ZipError, "File #{fileName} not found"
|
876
|
+
end
|
877
|
+
@create = create
|
878
|
+
@storedEntries = @entrySet.dup
|
879
|
+
end
|
880
|
+
|
881
|
+
def ZipFile.open(fileName, create = nil)
|
882
|
+
zf = ZipFile.new(fileName, create)
|
883
|
+
if block_given?
|
884
|
+
begin
|
885
|
+
yield zf
|
886
|
+
ensure
|
887
|
+
zf.close
|
888
|
+
end
|
889
|
+
else
|
890
|
+
zf
|
891
|
+
end
|
892
|
+
end
|
893
|
+
|
894
|
+
attr_accessor :comment
|
895
|
+
|
896
|
+
def ZipFile.foreach(aZipFileName, &block)
|
897
|
+
ZipFile.open(aZipFileName) {
|
898
|
+
|zipFile|
|
899
|
+
zipFile.each(&block)
|
900
|
+
}
|
901
|
+
end
|
902
|
+
|
903
|
+
def get_input_stream(entry, &aProc)
|
904
|
+
get_entry(entry).get_input_stream(&aProc)
|
905
|
+
end
|
906
|
+
|
907
|
+
def get_output_stream(entry, &aProc)
|
908
|
+
newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
|
909
|
+
if newEntry.directory?
|
910
|
+
raise ArgumentError,
|
911
|
+
"cannot open stream to directory entry - '#{newEntry}'"
|
912
|
+
end
|
913
|
+
zipStreamableEntry = ZipStreamableStream.new(newEntry)
|
914
|
+
@entrySet << zipStreamableEntry
|
915
|
+
zipStreamableEntry.get_output_stream(&aProc)
|
916
|
+
end
|
917
|
+
|
918
|
+
def to_s
|
919
|
+
@name
|
920
|
+
end
|
921
|
+
|
922
|
+
def read(entry)
|
923
|
+
get_input_stream(entry) { |is| is.read }
|
924
|
+
end
|
925
|
+
|
926
|
+
def add(entry, srcPath, &continueOnExistsProc)
|
927
|
+
continueOnExistsProc ||= proc { false }
|
928
|
+
check_entry_exists(entry, continueOnExistsProc, "add")
|
929
|
+
newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
|
930
|
+
if is_directory(newEntry, srcPath)
|
931
|
+
@entrySet << ZipStreamableDirectory.new(newEntry)
|
932
|
+
else
|
933
|
+
@entrySet << ZipStreamableFile.new(newEntry, srcPath)
|
934
|
+
end
|
935
|
+
end
|
936
|
+
|
937
|
+
def remove(entry)
|
938
|
+
@entrySet.delete(get_entry(entry))
|
939
|
+
end
|
940
|
+
|
941
|
+
def rename(entry, newName, &continueOnExistsProc)
|
942
|
+
foundEntry = get_entry(entry)
|
943
|
+
check_entry_exists(newName, continueOnExistsProc, "rename")
|
944
|
+
foundEntry.name=newName
|
945
|
+
end
|
946
|
+
|
947
|
+
def replace(entry, srcPath)
|
948
|
+
check_file(srcPath)
|
949
|
+
add(remove(entry), srcPath)
|
950
|
+
end
|
951
|
+
|
952
|
+
def extract(entry, destPath, &onExistsProc)
|
953
|
+
onExistsProc ||= proc { false }
|
954
|
+
foundEntry = get_entry(entry)
|
955
|
+
if foundEntry.is_directory
|
956
|
+
create_directory(foundEntry, destPath, &onExistsProc)
|
957
|
+
else
|
958
|
+
write_file(foundEntry, destPath, &onExistsProc)
|
959
|
+
end
|
960
|
+
end
|
961
|
+
|
962
|
+
def commit
|
963
|
+
return if ! commit_required?
|
964
|
+
on_success_replace(name) {
|
965
|
+
|tmpFile|
|
966
|
+
ZipOutputStream.open(tmpFile) {
|
967
|
+
|zos|
|
968
|
+
|
969
|
+
@entrySet.each { |e| e.write_to_zip_output_stream(zos) }
|
970
|
+
zos.comment = comment
|
971
|
+
}
|
972
|
+
true
|
973
|
+
}
|
974
|
+
initialize(name)
|
975
|
+
end
|
976
|
+
|
977
|
+
def close
|
978
|
+
commit
|
979
|
+
end
|
980
|
+
|
981
|
+
def commit_required?
|
982
|
+
return @entrySet != @storedEntries || @create == ZipFile::CREATE
|
983
|
+
end
|
984
|
+
|
985
|
+
def find_entry(entry)
|
986
|
+
@entrySet.detect {
|
987
|
+
|e|
|
988
|
+
e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "")
|
989
|
+
}
|
990
|
+
end
|
991
|
+
|
992
|
+
def get_entry(entry)
|
993
|
+
selectedEntry = find_entry(entry)
|
994
|
+
unless selectedEntry
|
995
|
+
raise Errno::ENOENT, entry
|
996
|
+
end
|
997
|
+
return selectedEntry
|
998
|
+
end
|
999
|
+
|
1000
|
+
def mkdir(entryName, permissionInt = 0) #permissionInt ignored
|
1001
|
+
if find_entry(entryName)
|
1002
|
+
raise Errno::EEXIST, "File exists - #{entryName}"
|
1003
|
+
end
|
1004
|
+
@entrySet << ZipStreamableDirectory.new(ZipEntry.new(name, entryName.to_s.ensure_end("/")))
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
private
|
1008
|
+
|
1009
|
+
def create_directory(entry, destPath)
|
1010
|
+
if File.directory? destPath
|
1011
|
+
return
|
1012
|
+
elsif File.exists? destPath
|
1013
|
+
if block_given? && yield(entry, destPath)
|
1014
|
+
File.rm_f destPath
|
1015
|
+
else
|
1016
|
+
raise ZipDestinationFileExistsError,
|
1017
|
+
"Cannot create directory '#{destPath}'. "+
|
1018
|
+
"A file already exists with that name"
|
1019
|
+
end
|
1020
|
+
end
|
1021
|
+
Dir.mkdir destPath
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
def is_directory(newEntry, srcPath)
|
1025
|
+
srcPathIsDirectory = File.directory?(srcPath)
|
1026
|
+
if newEntry.is_directory && ! srcPathIsDirectory
|
1027
|
+
raise ArgumentError,
|
1028
|
+
"entry name '#{newEntry}' indicates directory entry, but "+
|
1029
|
+
"'#{srcPath}' is not a directory"
|
1030
|
+
elsif ! newEntry.is_directory && srcPathIsDirectory
|
1031
|
+
newEntry.name += "/"
|
1032
|
+
end
|
1033
|
+
return newEntry.is_directory && srcPathIsDirectory
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
def check_entry_exists(entryName, continueOnExistsProc, procedureName)
|
1037
|
+
continueOnExistsProc ||= proc { false }
|
1038
|
+
if @entrySet.detect { |e| e.name == entryName }
|
1039
|
+
if continueOnExistsProc.call
|
1040
|
+
remove get_entry(entryName)
|
1041
|
+
else
|
1042
|
+
raise ZipEntryExistsError,
|
1043
|
+
procedureName+" failed. Entry #{entryName} already exists"
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
def write_file(entry, destPath, continueOnExistsProc = proc { false })
|
1049
|
+
if File.exists?(destPath) && ! yield(entry, destPath)
|
1050
|
+
raise ZipDestinationFileExistsError,
|
1051
|
+
"Destination '#{destPath}' already exists"
|
1052
|
+
end
|
1053
|
+
File.open(destPath, "wb") {
|
1054
|
+
|os|
|
1055
|
+
entry.get_input_stream { |is| os << is.read }
|
1056
|
+
}
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
def check_file(path)
|
1060
|
+
unless File.readable? path
|
1061
|
+
raise Errno::ENOENT, path
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
def on_success_replace(aFilename)
|
1066
|
+
tmpfile = get_tempfile
|
1067
|
+
tmpFilename = tmpfile.path
|
1068
|
+
tmpfile.close
|
1069
|
+
if yield tmpFilename
|
1070
|
+
File.move(tmpFilename, name)
|
1071
|
+
end
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
def get_tempfile
|
1075
|
+
tempFile = Tempfile.new(File.basename(name), File.dirname(name))
|
1076
|
+
tempFile.binmode
|
1077
|
+
tempFile
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
end
|
1081
|
+
|
1082
|
+
class ZipStreamableFile < DelegateClass(ZipEntry) #:nodoc:all
|
1083
|
+
def initialize(entry, filepath)
|
1084
|
+
super(entry)
|
1085
|
+
@delegate = entry
|
1086
|
+
@filepath = filepath
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
def get_input_stream(&aProc)
|
1090
|
+
File.open(@filepath, "rb", &aProc)
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
def write_to_zip_output_stream(aZipOutputStream)
|
1094
|
+
aZipOutputStream.put_next_entry(self)
|
1095
|
+
aZipOutputStream << get_input_stream { |is| is.read }
|
1096
|
+
end
|
1097
|
+
|
1098
|
+
def == (other)
|
1099
|
+
return false unless other.class == ZipStreamableFile
|
1100
|
+
@filepath == other.filepath && super(other.delegate)
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
protected
|
1104
|
+
attr_reader :filepath, :delegate
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
class ZipStreamableDirectory < DelegateClass(ZipEntry) #:nodoc:all
|
1108
|
+
def initialize(entry)
|
1109
|
+
super(entry)
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
def get_input_stream(&aProc)
|
1113
|
+
return yield(NullInputStream.instance) if block_given?
|
1114
|
+
NullInputStream.instance
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
def write_to_zip_output_stream(aZipOutputStream)
|
1118
|
+
aZipOutputStream.put_next_entry(self)
|
1119
|
+
end
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
class ZipStreamableStream < DelegateClass(ZipEntry) #nodoc:all
|
1123
|
+
def initialize(entry)
|
1124
|
+
super(entry)
|
1125
|
+
@tempFile = Tempfile.new(File.basename(name), File.dirname(zipfile))
|
1126
|
+
@tempFile.binmode
|
1127
|
+
end
|
1128
|
+
|
1129
|
+
def get_output_stream
|
1130
|
+
if block_given?
|
1131
|
+
begin
|
1132
|
+
yield(@tempFile)
|
1133
|
+
ensure
|
1134
|
+
@tempFile.close
|
1135
|
+
end
|
1136
|
+
else
|
1137
|
+
@tempFile
|
1138
|
+
end
|
1139
|
+
end
|
1140
|
+
|
1141
|
+
def get_input_stream
|
1142
|
+
if ! @tempFile.closed?
|
1143
|
+
raise StandardError, "cannot open entry for reading while its open for writing - #{name}"
|
1144
|
+
end
|
1145
|
+
@tempFile.open # reopens tempfile from top
|
1146
|
+
if block_given?
|
1147
|
+
begin
|
1148
|
+
yield(@tempFile)
|
1149
|
+
ensure
|
1150
|
+
@tempFile.close
|
1151
|
+
end
|
1152
|
+
else
|
1153
|
+
@tempFile
|
1154
|
+
end
|
1155
|
+
end
|
1156
|
+
|
1157
|
+
def write_to_zip_output_stream(aZipOutputStream)
|
1158
|
+
aZipOutputStream.put_next_entry(self)
|
1159
|
+
aZipOutputStream << get_input_stream { |is| is.read }
|
1160
|
+
end
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
class ZipExtraField < Hash
|
1164
|
+
ID_MAP = {}
|
1165
|
+
|
1166
|
+
# Meta class for extra fields
|
1167
|
+
class Generic
|
1168
|
+
def self.register_map
|
1169
|
+
if self.const_defined?(:HEADER_ID)
|
1170
|
+
ID_MAP[self.const_get(:HEADER_ID)] = self
|
1171
|
+
end
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
def self.name
|
1175
|
+
self.to_s.split("::")[-1]
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
# return field [size, content] or false
|
1179
|
+
def initial_parse(binstr)
|
1180
|
+
if ! binstr
|
1181
|
+
# If nil, start with empty.
|
1182
|
+
return false
|
1183
|
+
elsif binstr[0,2] != self.class.const_get(:HEADER_ID)
|
1184
|
+
$stderr.puts "Warning: weired extra feild header ID. skip parsing"
|
1185
|
+
return false
|
1186
|
+
end
|
1187
|
+
[binstr[2,2].unpack("v")[0], binstr[4..-1]]
|
1188
|
+
end
|
1189
|
+
|
1190
|
+
def ==(other)
|
1191
|
+
self.class != other.class and return false
|
1192
|
+
each { |k, v|
|
1193
|
+
v != other[k] and return false
|
1194
|
+
}
|
1195
|
+
true
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
def to_local_bin
|
1199
|
+
s = pack_for_local
|
1200
|
+
self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
def to_c_dir_bin
|
1204
|
+
s = pack_for_c_dir
|
1205
|
+
self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s
|
1206
|
+
end
|
1207
|
+
end
|
1208
|
+
|
1209
|
+
# Info-ZIP Additional timestamp field
|
1210
|
+
class UniversalTime < Generic
|
1211
|
+
HEADER_ID = "UT"
|
1212
|
+
register_map
|
1213
|
+
|
1214
|
+
def initialize(binstr = nil)
|
1215
|
+
@ctime = nil
|
1216
|
+
@mtime = nil
|
1217
|
+
@atime = nil
|
1218
|
+
@flag = nil
|
1219
|
+
binstr and merge(binstr)
|
1220
|
+
end
|
1221
|
+
attr_accessor :atime, :ctime, :mtime, :flag
|
1222
|
+
|
1223
|
+
def merge(binstr)
|
1224
|
+
binstr == "" and return
|
1225
|
+
size, content = initial_parse(binstr)
|
1226
|
+
size or return
|
1227
|
+
@flag, mtime, atime, ctime = content.unpack("CVVV")
|
1228
|
+
mtime and @mtime ||= Time.at(mtime)
|
1229
|
+
atime and @atime ||= Time.at(atime)
|
1230
|
+
ctime and @ctime ||= Time.at(ctime)
|
1231
|
+
end
|
1232
|
+
|
1233
|
+
def ==(other)
|
1234
|
+
@mtime == other.mtime &&
|
1235
|
+
@atime == other.atime &&
|
1236
|
+
@ctime == other.ctime
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
def pack_for_local
|
1240
|
+
s = [@flag].pack("C")
|
1241
|
+
@flag & 1 != 0 and s << [@mtime.to_i].pack("V")
|
1242
|
+
@flag & 2 != 0 and s << [@atime.to_i].pack("V")
|
1243
|
+
@flag & 4 != 0 and s << [@ctime.to_i].pack("V")
|
1244
|
+
s
|
1245
|
+
end
|
1246
|
+
|
1247
|
+
def pack_for_c_dir
|
1248
|
+
s = [@flag].pack("C")
|
1249
|
+
@flag & 1 == 1 and s << [@mtime.to_i].pack("V")
|
1250
|
+
s
|
1251
|
+
end
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
# Info-ZIP Extra for UNIX uid/gid
|
1255
|
+
class IUnix < Generic
|
1256
|
+
HEADER_ID = "Ux"
|
1257
|
+
register_map
|
1258
|
+
|
1259
|
+
def initialize(binstr = nil)
|
1260
|
+
@uid = nil
|
1261
|
+
@gid = nil
|
1262
|
+
binstr and merge(binstr)
|
1263
|
+
end
|
1264
|
+
attr_accessor :uid, :gid
|
1265
|
+
|
1266
|
+
def merge(binstr)
|
1267
|
+
binstr == "" and return
|
1268
|
+
size, content = initial_parse(binstr)
|
1269
|
+
# size: 0 for central direcotry. 4 for local header
|
1270
|
+
return if(! size || size == 0)
|
1271
|
+
uid, gid = content.unpack("vv")
|
1272
|
+
@uid ||= uid
|
1273
|
+
@gid ||= gid
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
def ==(other)
|
1277
|
+
@uid == other.uid &&
|
1278
|
+
@gid == other.gid
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
def pack_for_local
|
1282
|
+
[@uid, @gid].pack("vv")
|
1283
|
+
end
|
1284
|
+
|
1285
|
+
def pack_for_c_dir
|
1286
|
+
""
|
1287
|
+
end
|
1288
|
+
end
|
1289
|
+
|
1290
|
+
## start main of ZipExtraField < Hash
|
1291
|
+
def initialize(binstr = nil)
|
1292
|
+
binstr and merge(binstr)
|
1293
|
+
end
|
1294
|
+
|
1295
|
+
def merge(binstr)
|
1296
|
+
binstr == "" and return
|
1297
|
+
i = 0
|
1298
|
+
while i < binstr.length
|
1299
|
+
id = binstr[i,2]
|
1300
|
+
len = binstr[i+2,2].to_s.unpack("v")[0]
|
1301
|
+
if id && ID_MAP.member?(id)
|
1302
|
+
field_name = ID_MAP[id].name
|
1303
|
+
if self.member?(field_name)
|
1304
|
+
self[field_name].mergea(binstr[i, len+4])
|
1305
|
+
else
|
1306
|
+
field_obj = ID_MAP[id].new(binstr[i, len+4])
|
1307
|
+
self[field_name] = field_obj
|
1308
|
+
end
|
1309
|
+
elsif id
|
1310
|
+
unless self["Unknown"]
|
1311
|
+
s = ""
|
1312
|
+
class << s
|
1313
|
+
alias_method :to_c_dir_bin, :to_s
|
1314
|
+
alias_method :to_local_bin, :to_s
|
1315
|
+
end
|
1316
|
+
self["Unknown"] = s
|
1317
|
+
end
|
1318
|
+
if ! len || len+4 > binstr[i..-1].length
|
1319
|
+
self["Unknown"] << binstr[i..-1]
|
1320
|
+
break;
|
1321
|
+
end
|
1322
|
+
self["Unknown"] << binstr[i, len+4]
|
1323
|
+
end
|
1324
|
+
i += len+4
|
1325
|
+
end
|
1326
|
+
end
|
1327
|
+
|
1328
|
+
def create(name)
|
1329
|
+
field_class = nil
|
1330
|
+
ID_MAP.each { |id, klass|
|
1331
|
+
if klass.name == name
|
1332
|
+
field_class = klass
|
1333
|
+
break
|
1334
|
+
end
|
1335
|
+
}
|
1336
|
+
if ! field_class
|
1337
|
+
raise ZipError, "Unknown extra field '#{name}'"
|
1338
|
+
end
|
1339
|
+
self[name] = field_class.new()
|
1340
|
+
end
|
1341
|
+
|
1342
|
+
def to_local_bin
|
1343
|
+
s = ""
|
1344
|
+
each { |k, v|
|
1345
|
+
s << v.to_local_bin
|
1346
|
+
}
|
1347
|
+
s
|
1348
|
+
end
|
1349
|
+
alias :to_s :to_local_bin
|
1350
|
+
|
1351
|
+
def to_c_dir_bin
|
1352
|
+
s = ""
|
1353
|
+
each { |k, v|
|
1354
|
+
s << v.to_c_dir_bin
|
1355
|
+
}
|
1356
|
+
s
|
1357
|
+
end
|
1358
|
+
|
1359
|
+
def c_dir_length
|
1360
|
+
to_c_dir_bin.length
|
1361
|
+
end
|
1362
|
+
def local_length
|
1363
|
+
to_local_bin.length
|
1364
|
+
end
|
1365
|
+
alias :c_dir_size :c_dir_length
|
1366
|
+
alias :local_size :local_length
|
1367
|
+
alias :length :local_length
|
1368
|
+
alias :size :local_length
|
1369
|
+
end # end ZipExtraField
|
1370
|
+
|
1371
|
+
end # Zip namespace module
|
1372
|
+
|
1373
|
+
|
1374
|
+
|
1375
|
+
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
1376
|
+
# rubyzip is free software; you can redistribute it and/or
|
1377
|
+
# modify it under the terms of the ruby license.
|