tinkerbell 0.03 → 0.04

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/code/01rwdcore/01rwdcore.rb +6 -6
  2. data/code/01rwdcore/02helptexthashbegin.rb +13 -11
  3. data/code/01rwdcore/jumplinkcommand.rb +1 -0
  4. data/code/01rwdcore/openhelpwindow.rb +7 -0
  5. data/code/01rwdcore/runhelpabout.rb +6 -1
  6. data/code/01rwdcore/selectiontab.rb +2 -0
  7. data/code/01rwdcore/setuphelpaboutoptions.rb +2 -0
  8. data/code/01rwdcore/setuptinkerdocuments.rb +1 -0
  9. data/code/01rwdcore/test_cases.rb +100 -51
  10. data/code/01rwdcore/test_harness.rb +8 -1
  11. data/code/01rwdcore/uploadreturns.rb +3 -0
  12. data/code/dd0viewphoto/dd0viewphoto.rb +2 -0
  13. data/code/superant.com.rwdtinkerbackwindow/changelocale.rb +84 -0
  14. data/code/superant.com.rwdtinkerbackwindow/initiateapplets.rb +0 -1
  15. data/code/superant.com.rwdtinkerbackwindow/installgemapplet.rb +2 -0
  16. data/code/superant.com.rwdtinkerbackwindow/listgemzips.rb +2 -0
  17. data/code/superant.com.rwdtinkerbackwindow/listinstalledfiles.rb +3 -1
  18. data/code/superant.com.rwdtinkerbackwindow/listzips.rb +2 -0
  19. data/code/superant.com.rwdtinkerbackwindow/loadconfigurationrecord.rb +3 -1
  20. data/code/superant.com.rwdtinkerbackwindow/openhelpwindowtinkerwin2.rb +3 -1
  21. data/code/superant.com.rwdtinkerbackwindow/showlocaleoptions.rb +9 -0
  22. data/code/superant.com.rwdtinkerbackwindow/viewappletcontents.rb +1 -0
  23. data/code/superant.com.rwdtinkerbackwindow/viewlogfile.rb +8 -5
  24. data/code/superant.com.sandbox/tabthree.rb +0 -4
  25. data/code/superant.com.tinkerbell/addzipfiles.rb +66 -0
  26. data/code/superant.com.tinkerbell/helptexthashload.rb +22 -0
  27. data/code/superant.com.tinkerbell/loadviewfiles.rb +19 -0
  28. data/code/superant.com.tinkerbell/openhelpwindow.rb +11 -17
  29. data/code/superant.com.tinkerbell/runrwdapplet.rb +1 -1
  30. data/code/zz0applicationend/zz0end.rb +2 -1
  31. data/configuration/rwdtinker.dist +3 -3
  32. data/configuration/sandbox.dist +7 -0
  33. data/configuration/tinkerbellw.dist +3 -3
  34. data/configuration/tinkerwin2variables.dist +1 -1
  35. data/gui/tinkerbackwindows/superant.com.sandbox/33tabthree.rwd +2 -2
  36. data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/80localechanger.rwd +17 -0
  37. data/gui/tinkerbackwindows/superant.com.tinkerbell/60zipfoldfiles.rwd +31 -0
  38. data/gui/tinkerbackwindows/superant.com.tinkerbellback/1rwdrubyeval.rwd +1 -1
  39. data/gui/tinkerbackwindows/superant.com.tinkerbellback/81jumplinkcommands.rwd +17 -0
  40. data/init.rb +7 -1
  41. data/installed/sandbox.inf +4 -0
  42. data/installed/tinkerbellw.inf +7 -4
  43. data/lang/en/rwdcore/en.po +28 -0
  44. data/lang/en/tinkerbell/en.po +17 -0
  45. data/lang/es/rwdcore/es.po +22 -13
  46. data/lang/es/tinkerbell/en.po +16 -0
  47. data/lang/fr/rwdcore/fr.po +4 -1
  48. data/lang/fr/tinkerbell/en.po +7 -0
  49. data/lang/hi/rwdcore/hi.po +39 -36
  50. data/lang/hi/tinkerbell/en.po +6 -0
  51. data/lang/ja/rwdcore/ja.po +3 -0
  52. data/lang/ja/tinkerbell/en.po +6 -0
  53. data/lang/nl/rwdcore/nl.po +4 -1
  54. data/lang/nl/tinkerbell/en.po +6 -0
  55. data/lib/zip/ioextras.rb +43 -2
  56. data/lib/zip/stdrubyext.rb +5 -5
  57. data/lib/zip/tempfile_bugfixed.rb +2 -2
  58. data/lib/zip/zip.rb +618 -149
  59. data/lib/zip/zipfilesystem.rb +59 -8
  60. data/lib/zip/ziprequire.rb +32 -3
  61. data/rwd_files/HowTo_Tinker.txt +9 -0
  62. data/rwd_files/HowTo_TinkerBell.txt +4 -1
  63. data/rwd_files/Tinkerhelptexthash.txt +5 -2
  64. data/rwd_files/log/rwdtinker.log +1338 -0
  65. data/rwd_files/rwdapplications.html +23 -1
  66. data/{code/superant.com.tinkerbell/helptexthashrwdhypernote.rb → rwd_files/tinkerbellhelpfiles.txt} +21 -5
  67. data/rwdconfig.dist +1 -1
  68. data/tests/makedist-tinkerbellw.rb +1 -1
  69. data/tests/makedist.rb +1 -1
  70. data/{rwdtinker.rb → tinkerbell.rb} +0 -0
  71. data/zips/rwdwfoldeditor-0.07.zip +0 -0
  72. data/zips/rwdwhypernote-0.16.zip +0 -0
  73. data/zips/rwdwschedule-1.07.zip +0 -0
  74. data/zips/tinkerbellw-0.04.zip +0 -0
  75. metadata +24 -8
  76. data/gui/tinkerbackwindows/superant.com.tinkerbellback/77jumplinkcommands.rwd +0 -17
  77. data/zips/rwdwfoldeditor-0.05.zip +0 -0
  78. data/zips/rwdwhypernote-0.15.zip +0 -0
  79. data/zips/tinkerbellw-0.03.zip +0 -0
@@ -1,13 +1,13 @@
1
1
  #
2
2
  # tempfile - manipulates temporary files
3
3
  #
4
- # $Id: tempfile_bugfixed.rb,v 1.2 2004/03/28 12:46:36 thomas Exp $
4
+ # $Id: tempfile_bugfixed.rb,v 1.2 2005/02/19 20:30:33 thomas Exp $
5
5
  #
6
6
 
7
7
  require 'delegate'
8
8
  require 'tmpdir'
9
9
 
10
- module BugFix
10
+ module BugFix #:nodoc:all
11
11
 
12
12
  # A class for managing temporary files. This library is written to be
13
13
  # thread safe.
data/lib/zip/zip.rb CHANGED
@@ -1,8 +1,8 @@
1
-
2
1
  require 'delegate'
3
2
  require 'singleton'
4
3
  require 'tempfile'
5
4
  require 'ftools'
5
+ require 'stringio'
6
6
  require 'zlib'
7
7
  require 'lib/zip/stdrubyext'
8
8
  require 'lib/zip/ioextras'
@@ -12,7 +12,7 @@ if Tempfile.superclass == SimpleDelegator
12
12
  Tempfile = BugFix::Tempfile
13
13
  end
14
14
 
15
- module Zlib
15
+ module Zlib #:nodoc:all
16
16
  if ! const_defined? :MAX_WBITS
17
17
  MAX_WBITS = Zlib::Deflate.MAX_WBITS
18
18
  end
@@ -20,17 +20,65 @@ end
20
20
 
21
21
  module Zip
22
22
 
23
+ VERSION = '0.9.1'
24
+
23
25
  RUBY_MINOR_VERSION = RUBY_VERSION.split(".")[1].to_i
24
26
 
27
+ RUNNING_ON_WINDOWS = /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
28
+
25
29
  # Ruby 1.7.x compatibility
26
30
  # In ruby 1.6.x and 1.8.0 reading from an empty stream returns
27
31
  # an empty string the first time and then nil.
28
32
  # not so in 1.7.x
29
33
  EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST = RUBY_MINOR_VERSION != 7
30
-
34
+
35
+ # ZipInputStream is the basic class for reading zip entries in a
36
+ # zip file. It is possible to create a ZipInputStream object directly,
37
+ # passing the zip file name to the constructor, but more often than not
38
+ # the ZipInputStream will be obtained from a ZipFile (perhaps using the
39
+ # ZipFileSystem interface) object for a particular entry in the zip
40
+ # archive.
41
+ #
42
+ # A ZipInputStream inherits IOExtras::AbstractInputStream in order
43
+ # to provide an IO-like interface for reading from a single zip
44
+ # entry. Beyond methods for mimicking an IO-object it contains
45
+ # the method get_next_entry for iterating through the entries of
46
+ # an archive. get_next_entry returns a ZipEntry object that describes
47
+ # the zip entry the ZipInputStream is currently reading from.
48
+ #
49
+ # Example that creates a zip archive with ZipOutputStream and reads it
50
+ # back again with a ZipInputStream.
51
+ #
52
+ # require 'zip/zip'
53
+ #
54
+ # Zip::ZipOutputStream::open("my.zip") {
55
+ # |io|
56
+ #
57
+ # io.put_next_entry("first_entry.txt")
58
+ # io.write "Hello world!"
59
+ #
60
+ # io.put_next_entry("adir/first_entry.txt")
61
+ # io.write "Hello again!"
62
+ # }
63
+ #
64
+ #
65
+ # Zip::ZipInputStream::open("my.zip") {
66
+ # |io|
67
+ #
68
+ # while (entry = io.get_next_entry)
69
+ # puts "Contents of #{entry.name}: '#{io.read}'"
70
+ # end
71
+ # }
72
+ #
73
+ # java.util.zip.ZipInputStream is the original inspiration for this
74
+ # class.
75
+
31
76
  class ZipInputStream
32
77
  include IOExtras::AbstractInputStream
33
78
 
79
+ # Opens the indicated zip file. An exception is thrown
80
+ # if the specified offset in the specified filename is
81
+ # not a local zip entry header.
34
82
  def initialize(filename, offset = 0)
35
83
  super()
36
84
  @archiveIO = File.open(filename, "rb")
@@ -42,7 +90,10 @@ module Zip
42
90
  def close
43
91
  @archiveIO.close
44
92
  end
45
-
93
+
94
+ # Same as #initialize but if a block is passed the opened
95
+ # stream is passed to the block and closed when the block
96
+ # returns.
46
97
  def ZipInputStream.open(filename)
47
98
  return new(filename) unless block_given?
48
99
 
@@ -52,12 +103,18 @@ module Zip
52
103
  zio.close if zio
53
104
  end
54
105
 
106
+ # Returns a ZipEntry object. It is necessary to call this
107
+ # method on a newly created ZipInputStream before reading from
108
+ # the first entry in the archive. Returns nil when there are
109
+ # no more entries.
110
+
55
111
  def get_next_entry
56
112
  @archiveIO.seek(@currentEntry.next_header_offset,
57
- IO::SEEK_SET) if @currentEntry
113
+ IO::SEEK_SET) if @currentEntry
58
114
  open_entry
59
115
  end
60
116
 
117
+ # Rewinds the stream to the beginning of the current entry
61
118
  def rewind
62
119
  return if @currentEntry.nil?
63
120
  @lineno = 0
@@ -66,6 +123,18 @@ module Zip
66
123
  open_entry
67
124
  end
68
125
 
126
+ # Modeled after IO.sysread
127
+ def sysread(numberOfBytes = nil, buf = nil)
128
+ @decompressor.sysread(numberOfBytes, buf)
129
+ end
130
+
131
+ def eof
132
+ @outputBuffer.empty? && @decompressor.eof
133
+ end
134
+ alias :eof? :eof
135
+
136
+ protected
137
+
69
138
  def open_entry
70
139
  @currentEntry = ZipEntry.read_local_entry(@archiveIO)
71
140
  if (@currentEntry == nil)
@@ -83,14 +152,10 @@ module Zip
83
152
  return @currentEntry
84
153
  end
85
154
 
86
- def read(numberOfBytes = nil)
87
- @decompressor.read(numberOfBytes)
88
- end
89
- protected
90
155
  def produce_input
91
156
  @decompressor.produce_input
92
157
  end
93
-
158
+
94
159
  def input_finished?
95
160
  @decompressor.input_finished?
96
161
  end
@@ -114,11 +179,11 @@ module Zip
114
179
  @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST
115
180
  end
116
181
 
117
- def read(numberOfBytes = nil)
182
+ def sysread(numberOfBytes = nil, buf = nil)
118
183
  readEverything = (numberOfBytes == nil)
119
184
  while (readEverything || @outputBuffer.length < numberOfBytes)
120
185
  break if internal_input_finished?
121
- @outputBuffer << internal_produce_input
186
+ @outputBuffer << internal_produce_input(buf)
122
187
  end
123
188
  return value_when_finished if @outputBuffer.length==0 && input_finished?
124
189
  endIndex= numberOfBytes==nil ? @outputBuffer.length : numberOfBytes
@@ -134,14 +199,24 @@ module Zip
134
199
  end
135
200
 
136
201
  # to be used with produce_input, not read (as read may still have more data cached)
202
+ # is data cached anywhere other than @outputBuffer? the comment above may be wrong
137
203
  def input_finished?
138
204
  @outputBuffer.empty? && internal_input_finished?
139
205
  end
206
+ alias :eof :input_finished?
207
+ alias :eof? :input_finished?
140
208
 
141
209
  private
142
210
 
143
- def internal_produce_input
144
- @zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE))
211
+ def internal_produce_input(buf = nil)
212
+ retried = 0
213
+ begin
214
+ @zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE, buf))
215
+ rescue Zlib::BufError
216
+ raise if (retried >= 5) # how many times should we retry?
217
+ retried += 1
218
+ retry
219
+ end
145
220
  end
146
221
 
147
222
  def internal_input_finished?
@@ -165,7 +240,7 @@ module Zip
165
240
  end
166
241
 
167
242
  # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ?
168
- def read(numberOfBytes = nil)
243
+ def sysread(numberOfBytes = nil, buf = nil)
169
244
  if input_finished?
170
245
  hasReturnedEmptyStringVal=@hasReturnedEmptyString
171
246
  @hasReturnedEmptyString=true
@@ -177,21 +252,23 @@ module Zip
177
252
  numberOfBytes = @charsToRead-@readSoFar
178
253
  end
179
254
  @readSoFar += numberOfBytes
180
- @inputStream.read(numberOfBytes)
255
+ @inputStream.read(numberOfBytes, buf)
181
256
  end
182
257
 
183
258
  def produce_input
184
- read(Decompressor::CHUNK_SIZE)
259
+ sysread(Decompressor::CHUNK_SIZE)
185
260
  end
186
261
 
187
262
  def input_finished?
188
263
  (@readSoFar >= @charsToRead)
189
264
  end
265
+ alias :eof :input_finished?
266
+ alias :eof? :input_finished?
190
267
  end
191
268
 
192
269
  class NullDecompressor #:nodoc:all
193
270
  include Singleton
194
- def read(numberOfBytes = nil)
271
+ def sysread(numberOfBytes = nil, buf = nil)
195
272
  nil
196
273
  end
197
274
 
@@ -202,6 +279,11 @@ module Zip
202
279
  def input_finished?
203
280
  true
204
281
  end
282
+
283
+ def eof
284
+ true
285
+ end
286
+ alias :eof? :eof
205
287
  end
206
288
 
207
289
  class NullInputStream < NullDecompressor #:nodoc:all
@@ -211,9 +293,61 @@ module Zip
211
293
  class ZipEntry
212
294
  STORED = 0
213
295
  DEFLATED = 8
296
+
297
+ FSTYPE_FAT = 0
298
+ FSTYPE_AMIGA = 1
299
+ FSTYPE_VMS = 2
300
+ FSTYPE_UNIX = 3
301
+ FSTYPE_VM_CMS = 4
302
+ FSTYPE_ATARI = 5
303
+ FSTYPE_HPFS = 6
304
+ FSTYPE_MAC = 7
305
+ FSTYPE_Z_SYSTEM = 8
306
+ FSTYPE_CPM = 9
307
+ FSTYPE_TOPS20 = 10
308
+ FSTYPE_NTFS = 11
309
+ FSTYPE_QDOS = 12
310
+ FSTYPE_ACORN = 13
311
+ FSTYPE_VFAT = 14
312
+ FSTYPE_MVS = 15
313
+ FSTYPE_BEOS = 16
314
+ FSTYPE_TANDEM = 17
315
+ FSTYPE_THEOS = 18
316
+ FSTYPE_MAC_OSX = 19
317
+ FSTYPE_ATHEOS = 30
318
+
319
+ FSTYPES = {
320
+ FSTYPE_FAT => 'FAT'.freeze,
321
+ FSTYPE_AMIGA => 'Amiga'.freeze,
322
+ FSTYPE_VMS => 'VMS (Vax or Alpha AXP)'.freeze,
323
+ FSTYPE_UNIX => 'Unix'.freeze,
324
+ FSTYPE_VM_CMS => 'VM/CMS'.freeze,
325
+ FSTYPE_ATARI => 'Atari ST'.freeze,
326
+ FSTYPE_HPFS => 'OS/2 or NT HPFS'.freeze,
327
+ FSTYPE_MAC => 'Macintosh'.freeze,
328
+ FSTYPE_Z_SYSTEM => 'Z-System'.freeze,
329
+ FSTYPE_CPM => 'CP/M'.freeze,
330
+ FSTYPE_TOPS20 => 'TOPS-20'.freeze,
331
+ FSTYPE_NTFS => 'NTFS'.freeze,
332
+ FSTYPE_QDOS => 'SMS/QDOS'.freeze,
333
+ FSTYPE_ACORN => 'Acorn RISC OS'.freeze,
334
+ FSTYPE_VFAT => 'Win32 VFAT'.freeze,
335
+ FSTYPE_MVS => 'MVS'.freeze,
336
+ FSTYPE_BEOS => 'BeOS'.freeze,
337
+ FSTYPE_TANDEM => 'Tandem NSK'.freeze,
338
+ FSTYPE_THEOS => 'Theos'.freeze,
339
+ FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)'.freeze,
340
+ FSTYPE_ATHEOS => 'AtheOS'.freeze,
341
+ }.freeze
214
342
 
215
343
  attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
216
- :name, :size, :localHeaderOffset, :zipfile, :fstype, :externalFileAttributes
344
+ :name, :size, :localHeaderOffset, :zipfile, :fstype, :externalFileAttributes, :gp_flags, :header_signature
345
+
346
+ attr_accessor :follow_symlinks
347
+ attr_accessor :restore_times, :restore_permissions, :restore_ownership
348
+ attr_accessor :unix_uid, :unix_gid, :unix_perms
349
+
350
+ attr_reader :ftype, :filepath # :nodoc:
217
351
 
218
352
  def initialize(zipfile = "", name = "", comment = "", extra = "",
219
353
  compressed_size = 0, crc = 0,
@@ -227,11 +361,37 @@ module Zip
227
361
  @internalFileAttributes = 1
228
362
  @externalFileAttributes = 0
229
363
  @version = 52 # this library's version
230
- @fstype = 0 # default is fat
364
+ @ftype = nil # unspecified or unknown
365
+ @filepath = nil
366
+ if Zip::RUNNING_ON_WINDOWS
367
+ @fstype = FSTYPE_FAT
368
+ else
369
+ @fstype = FSTYPE_UNIX
370
+ end
231
371
  @zipfile, @comment, @compressed_size, @crc, @extra, @compression_method,
232
372
  @name, @size = zipfile, comment, compressed_size, crc,
233
373
  extra, compression_method, name, size
234
374
  @time = time
375
+
376
+ @follow_symlinks = false
377
+
378
+ @restore_times = true
379
+ @restore_permissions = false
380
+ @restore_ownership = false
381
+
382
+ # BUG: need an extra field to support uid/gid's
383
+ @unix_uid = nil
384
+ @unix_gid = nil
385
+ @unix_perms = nil
386
+ # @posix_acl = nil
387
+ # @ntfs_acl = nil
388
+
389
+ if name_is_directory?
390
+ @ftype = :directory
391
+ else
392
+ @ftype = :file
393
+ end
394
+
235
395
  unless ZipExtraField === @extra
236
396
  @extra = ZipExtraField.new(@extra.to_s)
237
397
  end
@@ -256,13 +416,27 @@ module Zip
256
416
  @time = aTime
257
417
  end
258
418
 
419
+ # Returns +true+ if the entry is a directory.
259
420
  def directory?
260
- return (%r{\/$} =~ @name) != nil
421
+ raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
422
+ @ftype == :directory
261
423
  end
262
424
  alias :is_directory :directory?
263
425
 
426
+ # Returns +true+ if the entry is a file.
264
427
  def file?
265
- ! directory?
428
+ raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
429
+ @ftype == :file
430
+ end
431
+
432
+ # Returns +true+ if the entry is a symlink.
433
+ def symlink?
434
+ raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
435
+ @ftype == :link
436
+ end
437
+
438
+ def name_is_directory? #:nodoc:all
439
+ (%r{\/$} =~ @name) != nil
266
440
  end
267
441
 
268
442
  def local_entry_offset #:nodoc:all
@@ -281,24 +455,42 @@ module Zip
281
455
  def next_header_offset #:nodoc:all
282
456
  local_entry_offset + self.compressed_size
283
457
  end
284
-
458
+
459
+ # Extracts entry to file destPath (defaults to @name).
460
+ def extract(destPath = @name, &onExistsProc)
461
+ onExistsProc ||= proc { false }
462
+
463
+ if directory?
464
+ create_directory(destPath, &onExistsProc)
465
+ elsif file?
466
+ write_file(destPath, &onExistsProc)
467
+ elsif symlink?
468
+ create_symlink(destPath, &onExistsProc)
469
+ else
470
+ raise RuntimeError, "unknown file type #{self.inspect}"
471
+ end
472
+
473
+ self
474
+ end
475
+
285
476
  def to_s
286
477
  @name
287
478
  end
288
479
 
289
480
  protected
290
481
 
291
- def ZipEntry.read_zip_short(io)
482
+ def ZipEntry.read_zip_short(io) # :nodoc:
292
483
  io.read(2).unpack('v')[0]
293
484
  end
294
485
 
295
- def ZipEntry.read_zip_long(io)
486
+ def ZipEntry.read_zip_long(io) # :nodoc:
296
487
  io.read(4).unpack('V')[0]
297
488
  end
298
489
  public
299
490
 
300
491
  LOCAL_ENTRY_SIGNATURE = 0x04034b50
301
492
  LOCAL_ENTRY_STATIC_HEADER_LENGTH = 30
493
+ LOCAL_ENTRY_TRAILING_DESCRIPTOR_LENGTH = 4+4+4
302
494
 
303
495
  def read_local_entry(io) #:nodoc:all
304
496
  @localHeaderOffset = io.tell
@@ -307,10 +499,10 @@ module Zip
307
499
  raise ZipError, "Premature end of file. Not enough data for zip entry local header"
308
500
  end
309
501
 
310
- localHeader ,
502
+ @header_signature ,
311
503
  @version ,
312
504
  @fstype ,
313
- @gpFlags ,
505
+ @gp_flags ,
314
506
  @compression_method,
315
507
  lastModTime ,
316
508
  lastModDate ,
@@ -320,7 +512,7 @@ module Zip
320
512
  nameLength ,
321
513
  extraLength = staticSizedFieldsBuf.unpack('VCCvvvvVVVvv')
322
514
 
323
- unless (localHeader == LOCAL_ENTRY_SIGNATURE)
515
+ unless (@header_signature == LOCAL_ENTRY_SIGNATURE)
324
516
  raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
325
517
  end
326
518
  set_time(lastModDate, lastModTime)
@@ -353,7 +545,7 @@ module Zip
353
545
  io <<
354
546
  [LOCAL_ENTRY_SIGNATURE ,
355
547
  0 ,
356
- 0 , # @gpFlags ,
548
+ 0 , # @gp_flags ,
357
549
  @compression_method ,
358
550
  @time.to_binary_dos_time , # @lastModTime ,
359
551
  @time.to_binary_dos_date , # @lastModDate ,
@@ -374,12 +566,12 @@ module Zip
374
566
  unless (staticSizedFieldsBuf.size == CDIR_ENTRY_STATIC_HEADER_LENGTH)
375
567
  raise ZipError, "Premature end of file. Not enough data for zip cdir entry header"
376
568
  end
377
-
378
- cdirSignature ,
569
+
570
+ @header_signature ,
379
571
  @version , # version of encoding software
380
572
  @fstype , # filesystem type
381
573
  @versionNeededToExtract,
382
- @gpFlags ,
574
+ @gp_flags ,
383
575
  @compression_method ,
384
576
  lastModTime ,
385
577
  lastModDate ,
@@ -397,7 +589,7 @@ module Zip
397
589
  @extra ,
398
590
  @comment = staticSizedFieldsBuf.unpack('VCCvvvvvVVVvvvvvVV')
399
591
 
400
- unless (cdirSignature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE)
592
+ unless (@header_signature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE)
401
593
  raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
402
594
  end
403
595
  set_time(lastModDate, lastModTime)
@@ -412,6 +604,28 @@ module Zip
412
604
  unless (@comment && @comment.length == commentLength)
413
605
  raise ZipError, "Truncated cdir zip entry header"
414
606
  end
607
+
608
+ case @fstype
609
+ when FSTYPE_UNIX
610
+ @unix_perms = (@externalFileAttributes >> 16) & 07777
611
+
612
+ case (@externalFileAttributes >> 28)
613
+ when 04
614
+ @ftype = :directory
615
+ when 010
616
+ @ftype = :file
617
+ when 012
618
+ @ftype = :link
619
+ else
620
+ raise ZipInternalError, "unknown file type #{'0%o' % (@externalFileAttributes >> 28)}"
621
+ end
622
+ else
623
+ if name_is_directory?
624
+ @ftype = :directory
625
+ else
626
+ @ftype = :file
627
+ end
628
+ end
415
629
  end
416
630
 
417
631
  def ZipEntry.read_c_dir_entry(io) #:nodoc:all
@@ -422,14 +636,65 @@ module Zip
422
636
  return nil
423
637
  end
424
638
 
639
+ def file_stat(path) # :nodoc:
640
+ if @follow_symlinks
641
+ return File::stat(path)
642
+ else
643
+ return File::lstat(path)
644
+ end
645
+ end
646
+
647
+ def get_extra_attributes_from_path(path) # :nodoc:
648
+ unless Zip::RUNNING_ON_WINDOWS
649
+ stat = file_stat(path)
650
+ @unix_uid = stat.uid
651
+ @unix_gid = stat.gid
652
+ @unix_perms = stat.mode & 07777
653
+ end
654
+ end
655
+
656
+ def set_extra_attributes_on_path(destPath) # :nodoc:
657
+ return unless (file? or directory?)
658
+
659
+ case @fstype
660
+ when FSTYPE_UNIX
661
+ # BUG: does not update timestamps into account
662
+ # ignore setuid/setgid bits by default. honor if @restore_ownership
663
+ unix_perms_mask = 01777
664
+ unix_perms_mask = 07777 if (@restore_ownership)
665
+ File::chmod(@unix_perms & unix_perms_mask, destPath) if (@restore_permissions && @unix_perms)
666
+ File::chown(@unix_uid, @unix_gid, destPath) if (@restore_ownership && @unix_uid && @unix_gid && Process::egid == 0)
667
+ # File::utimes()
668
+ end
669
+ end
425
670
 
426
671
  def write_c_dir_entry(io) #:nodoc:all
672
+ case @fstype
673
+ when FSTYPE_UNIX
674
+ ft = nil
675
+ case @ftype
676
+ when :file
677
+ ft = 010
678
+ @unix_perms ||= 0644
679
+ when :directory
680
+ ft = 004
681
+ @unix_perms ||= 0755
682
+ when :symlink
683
+ ft = 012
684
+ @unix_perms ||= 0755
685
+ else
686
+ raise ZipInternalError, "unknown file type #{self.inspect}"
687
+ end
688
+
689
+ @externalFileAttributes = (ft << 12 | (@unix_perms & 07777)) << 16
690
+ end
691
+
427
692
  io <<
428
693
  [CENTRAL_DIRECTORY_ENTRY_SIGNATURE,
429
694
  @version , # version of encoding software
430
695
  @fstype , # filesystem type
431
696
  0 , # @versionNeededToExtract ,
432
- 0 , # @gpFlags ,
697
+ 0 , # @gp_flags ,
433
698
  @compression_method ,
434
699
  @time.to_binary_dos_time , # @lastModTime ,
435
700
  @time.to_binary_dos_date , # @lastModDate ,
@@ -453,14 +718,15 @@ module Zip
453
718
  end
454
719
 
455
720
  def == (other)
456
- return false unless other.class == ZipEntry
721
+ return false unless other.class == self.class
457
722
  # Compares contents of local entry and exposed fields
458
723
  (@compression_method == other.compression_method &&
459
724
  @crc == other.crc &&
460
- @compressed_size == other.compressed_size &&
725
+ @compressed_size == other.compressed_size &&
461
726
  @size == other.size &&
462
727
  @name == other.name &&
463
728
  @extra == other.extra &&
729
+ @filepath == other.filepath &&
464
730
  self.time.dos_equals(other.time))
465
731
  end
466
732
 
@@ -468,23 +734,79 @@ module Zip
468
734
  return to_s <=> other.to_s
469
735
  end
470
736
 
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
737
+ # Returns an IO like object for the given ZipEntry.
738
+ # Warning: may behave weird with symlinks.
739
+ def get_input_stream(&aProc)
740
+ if @ftype == :directory
741
+ return yield(NullInputStream.instance) if block_given?
742
+ return NullInputStream.instance
743
+ elsif @filepath
744
+ case @ftype
745
+ when :file
746
+ return File.open(@filepath, "rb", &aProc)
747
+
748
+ when :symlink
749
+ linkpath = File::readlink(@filepath)
750
+ stringio = StringIO.new(linkpath)
751
+ return yield(stringio) if block_given?
752
+ return stringio
753
+ else
754
+ raise "unknown @ftype #{@ftype}"
755
+ end
480
756
  else
481
- return zis
757
+ zis = ZipInputStream.new(@zipfile, localHeaderOffset)
758
+ zis.get_next_entry
759
+ if block_given?
760
+ begin
761
+ return yield(zis)
762
+ ensure
763
+ zis.close
764
+ end
765
+ else
766
+ return zis
767
+ end
482
768
  end
483
769
  end
484
770
 
771
+ def gather_fileinfo_from_srcpath(srcPath) # :nodoc:
772
+ stat = file_stat(srcPath)
773
+ case stat.ftype
774
+ when 'file'
775
+ if name_is_directory?
776
+ raise ArgumentError,
777
+ "entry name '#{newEntry}' indicates directory entry, but "+
778
+ "'#{srcPath}' is not a directory"
779
+ end
780
+ @ftype = :file
781
+ when 'directory'
782
+ if ! name_is_directory?
783
+ @name += "/"
784
+ end
785
+ @ftype = :directory
786
+ when 'link'
787
+ if name_is_directory?
788
+ raise ArgumentError,
789
+ "entry name '#{newEntry}' indicates directory entry, but "+
790
+ "'#{srcPath}' is not a directory"
791
+ end
792
+ @ftype = :symlink
793
+ else
794
+ raise RuntimeError, "unknown file type: #{srcPath.inspect} #{stat.inspect}"
795
+ end
796
+
797
+ @filepath = srcPath
798
+ get_extra_attributes_from_path(@filepath)
799
+ end
485
800
 
486
801
  def write_to_zip_output_stream(aZipOutputStream) #:nodoc:all
487
- aZipOutputStream.copy_raw_entry(self)
802
+ if @ftype == :directory
803
+ aZipOutputStream.put_next_entry(self)
804
+ elsif @filepath
805
+ aZipOutputStream.put_next_entry(self)
806
+ get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) }
807
+ else
808
+ aZipOutputStream.copy_raw_entry(self)
809
+ end
488
810
  end
489
811
 
490
812
  def parent_as_string
@@ -498,19 +820,103 @@ module Zip
498
820
  end
499
821
 
500
822
  private
823
+
501
824
  def set_time(binaryDosDate, binaryDosTime)
502
825
  @time = Time.parse_binary_dos_format(binaryDosDate, binaryDosTime)
503
826
  rescue ArgumentError
504
827
  puts "Invalid date/time in zip entry"
505
828
  end
829
+
830
+ def write_file(destPath, continueOnExistsProc = proc { false })
831
+ if File.exists?(destPath) && ! yield(self, destPath)
832
+ raise ZipDestinationFileExistsError,
833
+ "Destination '#{destPath}' already exists"
834
+ end
835
+ File.open(destPath, "wb") do |os|
836
+ get_input_stream do |is|
837
+ set_extra_attributes_on_path(destPath)
838
+
839
+ buf = ''
840
+ while buf = is.sysread(Decompressor::CHUNK_SIZE, buf)
841
+ os << buf
842
+ end
843
+ end
844
+ end
845
+ end
846
+
847
+ def create_directory(destPath)
848
+ if File.directory? destPath
849
+ return
850
+ elsif File.exists? destPath
851
+ if block_given? && yield(self, destPath)
852
+ File.rm_f destPath
853
+ else
854
+ raise ZipDestinationFileExistsError,
855
+ "Cannot create directory '#{destPath}'. "+
856
+ "A file already exists with that name"
857
+ end
858
+ end
859
+ Dir.mkdir destPath
860
+ set_extra_attributes_on_path(destPath)
861
+ end
862
+
863
+ # BUG: create_symlink() does not use &onExistsProc
864
+ def create_symlink(destPath)
865
+ stat = nil
866
+ begin
867
+ stat = File::lstat(destPath)
868
+ rescue Errno::ENOENT
869
+ end
870
+
871
+ io = get_input_stream
872
+ linkto = io.read
873
+
874
+ if stat
875
+ if stat.symlink?
876
+ if File::readlink(destPath) == linkto
877
+ return
878
+ else
879
+ raise ZipDestinationFileExistsError,
880
+ "Cannot create symlink '#{destPath}'. "+
881
+ "A symlink already exists with that name"
882
+ end
883
+ else
884
+ raise ZipDestinationFileExistsError,
885
+ "Cannot create symlink '#{destPath}'. "+
886
+ "A file already exists with that name"
887
+ end
888
+ end
889
+
890
+ File::symlink(linkto, destPath)
891
+ end
506
892
  end
507
893
 
508
894
 
895
+ # ZipOutputStream is the basic class for writing zip files. It is
896
+ # possible to create a ZipOutputStream object directly, passing
897
+ # the zip file name to the constructor, but more often than not
898
+ # the ZipOutputStream will be obtained from a ZipFile (perhaps using the
899
+ # ZipFileSystem interface) object for a particular entry in the zip
900
+ # archive.
901
+ #
902
+ # A ZipOutputStream inherits IOExtras::AbstractOutputStream in order
903
+ # to provide an IO-like interface for writing to a single zip
904
+ # entry. Beyond methods for mimicking an IO-object it contains
905
+ # the method put_next_entry that closes the current entry
906
+ # and creates a new.
907
+ #
908
+ # Please refer to ZipInputStream for example code.
909
+ #
910
+ # java.util.zip.ZipOutputStream is the original inspiration for this
911
+ # class.
912
+
509
913
  class ZipOutputStream
510
914
  include IOExtras::AbstractOutputStream
511
915
 
512
916
  attr_accessor :comment
513
917
 
918
+ # Opens the indicated zip file. If a file with that name already
919
+ # exists it will be overwritten.
514
920
  def initialize(fileName)
515
921
  super()
516
922
  @fileName = fileName
@@ -522,6 +928,9 @@ module Zip
522
928
  @comment = nil
523
929
  end
524
930
 
931
+ # Same as #initialize but if a block is passed the opened
932
+ # stream is passed to the block and closed when the block
933
+ # returns.
525
934
  def ZipOutputStream.open(fileName)
526
935
  return new(fileName) unless block_given?
527
936
  zos = new(fileName)
@@ -530,6 +939,7 @@ module Zip
530
939
  zos.close if zos
531
940
  end
532
941
 
942
+ # Closes the stream and writes the central directory to the zip file
533
943
  def close
534
944
  return if @closed
535
945
  finalize_current_entry
@@ -539,10 +949,12 @@ module Zip
539
949
  @closed = true
540
950
  end
541
951
 
952
+ # Closes the current entry and opens a new for writing.
953
+ # +entry+ can be a ZipEntry object or a string.
542
954
  def put_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION)
543
955
  raise ZipError, "zip stream is closed" if @closed
544
956
  newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@fileName, entry.to_s)
545
- init_next_entry(newEntry)
957
+ init_next_entry(newEntry, level)
546
958
  @currentEntry=newEntry
547
959
  end
548
960
 
@@ -614,6 +1026,7 @@ module Zip
614
1026
  end
615
1027
 
616
1028
  public
1029
+ # Modeled after IO.<<
617
1030
  def << (data)
618
1031
  @compressor << data
619
1032
  end
@@ -679,7 +1092,7 @@ module Zip
679
1092
  end
680
1093
 
681
1094
 
682
- class ZipEntrySet
1095
+ class ZipEntrySet #:nodoc:all
683
1096
  include Enumerable
684
1097
 
685
1098
  def initialize(anEnumerable = [])
@@ -728,13 +1141,20 @@ module Zip
728
1141
  @entrySet[entry.parent_as_string]
729
1142
  end
730
1143
 
1144
+ def glob(pattern, flags = File::FNM_PATHNAME|File::FNM_DOTMATCH)
1145
+ entries.select {
1146
+ |entry|
1147
+ File.fnmatch(pattern, entry.name.chomp('/'), flags)
1148
+ }
1149
+ end
1150
+
731
1151
  #TODO attr_accessor :auto_create_directories
732
1152
  protected
733
1153
  attr_accessor :entrySet
734
1154
  end
735
1155
 
736
1156
 
737
- class ZipCentralDirectory #:nodoc:all
1157
+ class ZipCentralDirectory
738
1158
  include Enumerable
739
1159
 
740
1160
  END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50
@@ -742,24 +1162,25 @@ module Zip
742
1162
  STATIC_EOCD_SIZE = 22
743
1163
 
744
1164
  attr_reader :comment
745
-
1165
+
1166
+ # Returns an Enumerable containing the entries.
746
1167
  def entries
747
1168
  @entrySet.entries
748
1169
  end
749
1170
 
750
- def initialize(entries = ZipEntrySet.new, comment = "")
1171
+ def initialize(entries = ZipEntrySet.new, comment = "") #:nodoc:
751
1172
  super()
752
1173
  @entrySet = entries.kind_of?(ZipEntrySet) ? entries : ZipEntrySet.new(entries)
753
1174
  @comment = comment
754
1175
  end
755
1176
 
756
- def write_to_stream(io)
1177
+ def write_to_stream(io) #:nodoc:
757
1178
  offset = io.tell
758
1179
  @entrySet.each { |entry| entry.write_c_dir_entry(io) }
759
1180
  write_e_o_c_d(io, offset)
760
1181
  end
761
1182
 
762
- def write_e_o_c_d(io, offset)
1183
+ def write_e_o_c_d(io, offset) #:nodoc:
763
1184
  io <<
764
1185
  [END_OF_CENTRAL_DIRECTORY_SIGNATURE,
765
1186
  0 , # @numberOfThisDisk
@@ -773,13 +1194,13 @@ module Zip
773
1194
  end
774
1195
  private :write_e_o_c_d
775
1196
 
776
- def cdir_size
1197
+ def cdir_size #:nodoc:
777
1198
  # does not include eocd
778
1199
  @entrySet.inject(0) { |value, entry| entry.cdir_header_size + value }
779
1200
  end
780
1201
  private :cdir_size
781
1202
 
782
- def read_e_o_c_d(io)
1203
+ def read_e_o_c_d(io) #:nodoc:
783
1204
  buf = get_e_o_c_d(io)
784
1205
  @numberOfThisDisk = ZipEntry::read_zip_short(buf)
785
1206
  @numberOfDiskWithStartOfCDir = ZipEntry::read_zip_short(buf)
@@ -792,7 +1213,7 @@ module Zip
792
1213
  raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0
793
1214
  end
794
1215
 
795
- def read_central_directory_entries(io)
1216
+ def read_central_directory_entries(io) #:nodoc:
796
1217
  begin
797
1218
  io.seek(@cdirOffset, IO::SEEK_SET)
798
1219
  rescue Errno::EINVAL
@@ -804,20 +1225,33 @@ module Zip
804
1225
  }
805
1226
  end
806
1227
 
807
- def read_from_stream(io)
1228
+ def read_from_stream(io) #:nodoc:
808
1229
  read_e_o_c_d(io)
809
1230
  read_central_directory_entries(io)
810
1231
  end
811
1232
 
812
- def get_e_o_c_d(io)
1233
+ def get_e_o_c_d(io) #:nodoc:
813
1234
  begin
814
1235
  io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END)
815
1236
  rescue Errno::EINVAL
816
1237
  io.seek(0, IO::SEEK_SET)
817
- rescue Errno::EFBIG # FreeBSD 4.9 returns Errno::EFBIG instead of Errno::EINVAL
1238
+ rescue Errno::EFBIG # FreeBSD 4.9 raise Errno::EFBIG instead of Errno::EINVAL
818
1239
  io.seek(0, IO::SEEK_SET)
819
1240
  end
820
- buf = io.read
1241
+
1242
+ # 'buf = io.read' substituted with lump of code to work around FreeBSD 4.5 issue
1243
+ retried = false
1244
+ buf = nil
1245
+ begin
1246
+ buf = io.read
1247
+ rescue Errno::EFBIG # FreeBSD 4.5 may raise Errno::EFBIG
1248
+ raise if (retried)
1249
+ retried = true
1250
+
1251
+ io.seek(0, IO::SEEK_SET)
1252
+ retry
1253
+ end
1254
+
821
1255
  sigIndex = buf.rindex([END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack('V'))
822
1256
  raise ZipError, "Zip end of central directory signature not found" unless sigIndex
823
1257
  buf=buf.slice!((sigIndex+4)...(buf.size))
@@ -826,16 +1260,19 @@ module Zip
826
1260
  end
827
1261
  return buf
828
1262
  end
829
-
1263
+
1264
+ # For iterating over the entries.
830
1265
  def each(&proc)
831
1266
  @entrySet.each(&proc)
832
1267
  end
833
1268
 
1269
+ # Returns the number of entries in the central directory (and
1270
+ # consequently in the zip archive).
834
1271
  def size
835
1272
  @entrySet.size
836
1273
  end
837
1274
 
838
- def ZipCentralDirectory.read_from_stream(io)
1275
+ def ZipCentralDirectory.read_from_stream(io) #:nodoc:
839
1276
  cdir = new
840
1277
  cdir.read_from_stream(io)
841
1278
  return cdir
@@ -843,7 +1280,7 @@ module Zip
843
1280
  return nil
844
1281
  end
845
1282
 
846
- def == (other)
1283
+ def == (other) #:nodoc:
847
1284
  return false unless other.kind_of?(ZipCentralDirectory)
848
1285
  @entrySet.entries.sort == other.entries.sort && comment == other.comment
849
1286
  end
@@ -856,28 +1293,88 @@ module Zip
856
1293
  class ZipDestinationFileExistsError < ZipError; end
857
1294
  class ZipCompressionMethodError < ZipError; end
858
1295
  class ZipEntryNameError < ZipError; end
859
-
1296
+ class ZipInternalError < ZipError; end
1297
+
1298
+ # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK.
1299
+ # The most important methods are those inherited from
1300
+ # ZipCentralDirectory for accessing information about the entries in
1301
+ # the archive and methods such as get_input_stream and
1302
+ # get_output_stream for reading from and writing entries to the
1303
+ # archive. The class includes a few convenience methods such as
1304
+ # #extract for extracting entries to the filesystem, and #remove,
1305
+ # #replace, #rename and #mkdir for making simple modifications to
1306
+ # the archive.
1307
+ #
1308
+ # Modifications to a zip archive are not committed until #commit or
1309
+ # #close is called. The method #open accepts a block following
1310
+ # the pattern from File.open offering a simple way to
1311
+ # automatically close the archive when the block returns.
1312
+ #
1313
+ # The following example opens zip archive <code>my.zip</code>
1314
+ # (creating it if it doesn't exist) and adds an entry
1315
+ # <code>first.txt</code> and a directory entry <code>a_dir</code>
1316
+ # to it.
1317
+ #
1318
+ # require 'zip/zip'
1319
+ #
1320
+ # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
1321
+ # |zipfile|
1322
+ # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" }
1323
+ # zipfile.mkdir("a_dir")
1324
+ # }
1325
+ #
1326
+ # The next example reopens <code>my.zip</code> writes the contents of
1327
+ # <code>first.txt</code> to standard out and deletes the entry from
1328
+ # the archive.
1329
+ #
1330
+ # require 'zip/zip'
1331
+ #
1332
+ # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
1333
+ # |zipfile|
1334
+ # puts zipfile.read("first.txt")
1335
+ # zipfile.remove("first.txt")
1336
+ # }
1337
+ #
1338
+ # ZipFileSystem offers an alternative API that emulates ruby's
1339
+ # interface for accessing the filesystem, ie. the File and Dir classes.
1340
+
860
1341
  class ZipFile < ZipCentralDirectory
861
1342
 
862
1343
  CREATE = 1
863
1344
 
864
1345
  attr_reader :name
865
1346
 
1347
+ # default -> false
1348
+ attr_accessor :restore_ownership
1349
+ # default -> false
1350
+ attr_accessor :restore_permissions
1351
+ # default -> true
1352
+ attr_accessor :restore_times
1353
+
1354
+ # Opens a zip archive. Pass true as the second parameter to create
1355
+ # a new archive if it doesn't exist already.
866
1356
  def initialize(fileName, create = nil)
867
1357
  super()
868
1358
  @name = fileName
869
1359
  @comment = ""
870
1360
  if (File.exists?(fileName))
871
1361
  File.open(name, "rb") { |f| read_from_stream(f) }
872
- elsif (create == ZipFile::CREATE)
1362
+ elsif (create)
873
1363
  @entrySet = ZipEntrySet.new
874
1364
  else
875
1365
  raise ZipError, "File #{fileName} not found"
876
1366
  end
877
1367
  @create = create
878
1368
  @storedEntries = @entrySet.dup
1369
+
1370
+ @restore_ownership = false
1371
+ @restore_permissions = false
1372
+ @restore_times = true
879
1373
  end
880
-
1374
+
1375
+ # Same as #new. If a block is passed the ZipFile object is passed
1376
+ # to the block and is automatically closed afterwards just as with
1377
+ # ruby's builtin File.open method.
881
1378
  def ZipFile.open(fileName, create = nil)
882
1379
  zf = ZipFile.new(fileName, create)
883
1380
  if block_given?
@@ -891,8 +1388,15 @@ module Zip
891
1388
  end
892
1389
  end
893
1390
 
1391
+ # Returns the zip files comment, if it has one
894
1392
  attr_accessor :comment
895
1393
 
1394
+ # Iterates over the contents of the ZipFile. This is more efficient
1395
+ # than using a ZipInputStream since this methods simply iterates
1396
+ # through the entries in the central directory structure in the archive
1397
+ # whereas ZipInputStream jumps through the entire archive accessing the
1398
+ # local entry headers (which contain the same information as the
1399
+ # central directory).
896
1400
  def ZipFile.foreach(aZipFileName, &block)
897
1401
  ZipFile.open(aZipFileName) {
898
1402
  |zipFile|
@@ -900,10 +1404,16 @@ module Zip
900
1404
  }
901
1405
  end
902
1406
 
1407
+ # Returns an input stream to the specified entry. If a block is passed
1408
+ # the stream object is passed to the block and the stream is automatically
1409
+ # closed afterwards just as with ruby's builtin File.open method.
903
1410
  def get_input_stream(entry, &aProc)
904
1411
  get_entry(entry).get_input_stream(&aProc)
905
1412
  end
906
1413
 
1414
+ # Returns an output stream to the specified entry. If a block is passed
1415
+ # the stream object is passed to the block and the stream is automatically
1416
+ # closed afterwards just as with ruby's builtin File.open method.
907
1417
  def get_output_stream(entry, &aProc)
908
1418
  newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
909
1419
  if newEntry.directory?
@@ -915,50 +1425,53 @@ module Zip
915
1425
  zipStreamableEntry.get_output_stream(&aProc)
916
1426
  end
917
1427
 
1428
+ # Returns the name of the zip archive
918
1429
  def to_s
919
1430
  @name
920
1431
  end
921
1432
 
1433
+ # Returns a string containing the contents of the specified entry
922
1434
  def read(entry)
923
1435
  get_input_stream(entry) { |is| is.read }
924
1436
  end
925
1437
 
1438
+ # Convenience method for adding the contents of a file to the archive
926
1439
  def add(entry, srcPath, &continueOnExistsProc)
927
1440
  continueOnExistsProc ||= proc { false }
928
1441
  check_entry_exists(entry, continueOnExistsProc, "add")
929
1442
  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
1443
+ newEntry.gather_fileinfo_from_srcpath(srcPath)
1444
+ @entrySet << newEntry
935
1445
  end
936
1446
 
1447
+ # Removes the specified entry.
937
1448
  def remove(entry)
938
1449
  @entrySet.delete(get_entry(entry))
939
1450
  end
940
1451
 
1452
+ # Renames the specified entry.
941
1453
  def rename(entry, newName, &continueOnExistsProc)
942
1454
  foundEntry = get_entry(entry)
943
1455
  check_entry_exists(newName, continueOnExistsProc, "rename")
944
1456
  foundEntry.name=newName
945
1457
  end
946
1458
 
1459
+ # Replaces the specified entry with the contents of srcPath (from
1460
+ # the file system).
947
1461
  def replace(entry, srcPath)
948
1462
  check_file(srcPath)
949
1463
  add(remove(entry), srcPath)
950
1464
  end
951
-
1465
+
1466
+ # Extracts entry to file destPath.
952
1467
  def extract(entry, destPath, &onExistsProc)
953
1468
  onExistsProc ||= proc { false }
954
1469
  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
1470
+ foundEntry.extract(destPath, &onExistsProc)
960
1471
  end
961
-
1472
+
1473
+ # Commits changes that has been made since the previous commit to
1474
+ # the zip archive.
962
1475
  def commit
963
1476
  return if ! commit_required?
964
1477
  on_success_replace(name) {
@@ -973,54 +1486,51 @@ module Zip
973
1486
  }
974
1487
  initialize(name)
975
1488
  end
976
-
1489
+
1490
+ # Closes the zip file committing any changes that has been made.
977
1491
  def close
978
1492
  commit
979
1493
  end
980
1494
 
1495
+ # Returns true if any changes has been made to this archive since
1496
+ # the previous commit
981
1497
  def commit_required?
982
1498
  return @entrySet != @storedEntries || @create == ZipFile::CREATE
983
1499
  end
984
1500
 
1501
+ # Searches for entry with the specified name. Returns nil if
1502
+ # no entry is found. See also get_entry
985
1503
  def find_entry(entry)
986
1504
  @entrySet.detect {
987
1505
  |e|
988
1506
  e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "")
989
1507
  }
990
1508
  end
991
-
1509
+
1510
+ # Searches for an entry just as find_entry, but throws Errno::ENOENT
1511
+ # if no entry is found.
992
1512
  def get_entry(entry)
993
1513
  selectedEntry = find_entry(entry)
994
1514
  unless selectedEntry
995
1515
  raise Errno::ENOENT, entry
996
1516
  end
1517
+ selectedEntry.restore_ownership = @restore_ownership
1518
+ selectedEntry.restore_permissions = @restore_permissions
1519
+ selectedEntry.restore_times = @restore_times
1520
+
997
1521
  return selectedEntry
998
1522
  end
999
1523
 
1000
- def mkdir(entryName, permissionInt = 0) #permissionInt ignored
1524
+ # Creates a directory
1525
+ def mkdir(entryName, permissionInt = 0755)
1001
1526
  if find_entry(entryName)
1002
1527
  raise Errno::EEXIST, "File exists - #{entryName}"
1003
1528
  end
1004
- @entrySet << ZipStreamableDirectory.new(ZipEntry.new(name, entryName.to_s.ensure_end("/")))
1529
+ @entrySet << ZipStreamableDirectory.new(@name, entryName.to_s.ensure_end("/"), nil, permissionInt)
1005
1530
  end
1006
1531
 
1007
1532
  private
1008
1533
 
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
1534
  def is_directory(newEntry, srcPath)
1025
1535
  srcPathIsDirectory = File.directory?(srcPath)
1026
1536
  if newEntry.is_directory && ! srcPathIsDirectory
@@ -1045,18 +1555,6 @@ module Zip
1045
1555
  end
1046
1556
  end
1047
1557
 
1048
- def write_file(entry, destPath, continueOnExistsProc = proc { false })
1049
- if File.exists?(destPath) && ! yield(entry, destPath)
1050
-
1051
- # raise ZipDestinationFileExistsError,
1052
- $stderr.puts "Destination '#{destPath}' already exists"
1053
- end
1054
- File.open(destPath, "wb") {
1055
- |os|
1056
- entry.get_input_stream { |is| os << is.read }
1057
- }
1058
- end
1059
-
1060
1558
  def check_file(path)
1061
1559
  unless File.readable? path
1062
1560
  raise Errno::ENOENT, path
@@ -1080,43 +1578,13 @@ module Zip
1080
1578
 
1081
1579
  end
1082
1580
 
1083
- class ZipStreamableFile < DelegateClass(ZipEntry) #:nodoc:all
1084
- def initialize(entry, filepath)
1085
- super(entry)
1086
- @delegate = entry
1087
- @filepath = filepath
1088
- end
1581
+ class ZipStreamableDirectory < ZipEntry
1582
+ def initialize(zipfile, entry, srcPath = nil, permissionInt = nil)
1583
+ super(zipfile, entry)
1089
1584
 
1090
- def get_input_stream(&aProc)
1091
- File.open(@filepath, "rb", &aProc)
1092
- end
1093
-
1094
- def write_to_zip_output_stream(aZipOutputStream)
1095
- aZipOutputStream.put_next_entry(self)
1096
- aZipOutputStream << get_input_stream { |is| is.read }
1097
- end
1098
-
1099
- def == (other)
1100
- return false unless other.class == ZipStreamableFile
1101
- @filepath == other.filepath && super(other.delegate)
1102
- end
1103
-
1104
- protected
1105
- attr_reader :filepath, :delegate
1106
- end
1107
-
1108
- class ZipStreamableDirectory < DelegateClass(ZipEntry) #:nodoc:all
1109
- def initialize(entry)
1110
- super(entry)
1111
- end
1112
-
1113
- def get_input_stream(&aProc)
1114
- return yield(NullInputStream.instance) if block_given?
1115
- NullInputStream.instance
1116
- end
1117
-
1118
- def write_to_zip_output_stream(aZipOutputStream)
1119
- aZipOutputStream.put_next_entry(self)
1585
+ @ftype = :directory
1586
+ entry.get_extra_attributes_from_path(srcPath) if (srcPath)
1587
+ @unix_perms = permissionInt if (permissionInt)
1120
1588
  end
1121
1589
  end
1122
1590
 
@@ -1144,6 +1612,7 @@ module Zip
1144
1612
  raise StandardError, "cannot open entry for reading while its open for writing - #{name}"
1145
1613
  end
1146
1614
  @tempFile.open # reopens tempfile from top
1615
+ @tempFile.binmode
1147
1616
  if block_given?
1148
1617
  begin
1149
1618
  yield(@tempFile)
@@ -1157,7 +1626,7 @@ module Zip
1157
1626
 
1158
1627
  def write_to_zip_output_stream(aZipOutputStream)
1159
1628
  aZipOutputStream.put_next_entry(self)
1160
- aZipOutputStream << get_input_stream { |is| is.read }
1629
+ get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) }
1161
1630
  end
1162
1631
  end
1163
1632
 
@@ -1258,8 +1727,8 @@ module Zip
1258
1727
  register_map
1259
1728
 
1260
1729
  def initialize(binstr = nil)
1261
- @uid = nil
1262
- @gid = nil
1730
+ @uid = 0
1731
+ @gid = 0
1263
1732
  binstr and merge(binstr)
1264
1733
  end
1265
1734
  attr_accessor :uid, :gid