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.
Files changed (37) hide show
  1. data/Readme.txt +6 -0
  2. data/code/01rwdcore/01rwdcore.rb +4 -2
  3. data/code/01rwdcore/test_cases.rb +126 -0
  4. data/code/01rwdcore/test_harness.rb +15 -0
  5. data/code/01rwdcore/uploadreturns.rb +62 -0
  6. data/code/superant.com.rwdtinkerbackwindow/diagnostictab.rb +14 -10
  7. data/configuration/language.dist +1 -1
  8. data/configuration/rwdapplicationidentity.dist +2 -2
  9. data/configuration/rwdshell.dist +2 -2
  10. data/configuration/rwdtinker.dist +2 -2
  11. data/configuration/tinkerwin2variables.dist +1 -1
  12. data/extras/zip/ioextras.rb +114 -0
  13. data/extras/zip/stdrubyext.rb +111 -0
  14. data/extras/zip/tempfile_bugfixed.rb +195 -0
  15. data/extras/zip/zip.rb +1377 -0
  16. data/extras/zip/zipfilesystem.rb +558 -0
  17. data/extras/zip/ziprequire.rb +61 -0
  18. data/gui/helpaboutinstalled/superant.com.tinkerhelpabout/3copyright.rwd +1 -1
  19. data/gui/tinkerbackwindows/superant.com.rwdshellbackwindow/46editscriptrecord.rwd +5 -5
  20. data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/70rwddiagnostics.rwd +12 -16
  21. data/init.rb +3 -0
  22. data/rwd_files/HowTo_Shell.txt +4 -0
  23. data/rwd_files/HowTo_Tinker.txt +14 -0
  24. data/rwdconfig.dist +6 -2
  25. data/tests/makedist.rb +16 -1
  26. metadata +12 -17
  27. data/extras/cmdline_parse +0 -47
  28. data/extras/config_file +0 -69
  29. data/extras/errorMsg +0 -19
  30. data/extras/makePlaylist +0 -34
  31. data/extras/mp3controld +0 -289
  32. data/extras/playlist +0 -186
  33. data/extras/showHelp +0 -18
  34. data/gui/helpaboutinstalled/superant.com.rwdwin2helpabout/1appname.rwd +0 -4
  35. data/gui/helpaboutinstalled/superant.com.rwdwin2helpabout/3copyright.rwd +0 -3
  36. data/gui/helpaboutinstalled/superant.com.rwdwin2helpabout/5version.rwd +0 -10
  37. 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.