rwdaddresses 0.95 → 0.97

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