mezza-rubyzip 0.9.4.1

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.
@@ -0,0 +1,134 @@
1
+ module Zip
2
+ # ZipInputStream is the basic class for reading zip entries in a
3
+ # zip file. It is possible to create a ZipInputStream object directly,
4
+ # passing the zip file name to the constructor, but more often than not
5
+ # the ZipInputStream will be obtained from a ZipFile (perhaps using the
6
+ # ZipFileSystem interface) object for a particular entry in the zip
7
+ # archive.
8
+ #
9
+ # A ZipInputStream inherits IOExtras::AbstractInputStream in order
10
+ # to provide an IO-like interface for reading from a single zip
11
+ # entry. Beyond methods for mimicking an IO-object it contains
12
+ # the method get_next_entry for iterating through the entries of
13
+ # an archive. get_next_entry returns a ZipEntry object that describes
14
+ # the zip entry the ZipInputStream is currently reading from.
15
+ #
16
+ # Example that creates a zip archive with ZipOutputStream and reads it
17
+ # back again with a ZipInputStream.
18
+ #
19
+ # require 'zip/zip'
20
+ #
21
+ # Zip::ZipOutputStream::open("my.zip") {
22
+ # |io|
23
+ #
24
+ # io.put_next_entry("first_entry.txt")
25
+ # io.write "Hello world!"
26
+ #
27
+ # io.put_next_entry("adir/first_entry.txt")
28
+ # io.write "Hello again!"
29
+ # }
30
+ #
31
+ #
32
+ # Zip::ZipInputStream::open("my.zip") {
33
+ # |io|
34
+ #
35
+ # while (entry = io.get_next_entry)
36
+ # puts "Contents of #{entry.name}: '#{io.read}'"
37
+ # end
38
+ # }
39
+ #
40
+ # java.util.zip.ZipInputStream is the original inspiration for this
41
+ # class.
42
+
43
+ class ZipInputStream
44
+ include IOExtras::AbstractInputStream
45
+
46
+ # Opens the indicated zip file. An exception is thrown
47
+ # if the specified offset in the specified filename is
48
+ # not a local zip entry header.
49
+ def initialize(filename, offset = 0)
50
+ super()
51
+ @archiveIO = ::File.open(filename, "rb")
52
+ @archiveIO.seek(offset, IO::SEEK_SET)
53
+ @decompressor = NullDecompressor.instance
54
+ @currentEntry = nil
55
+ end
56
+
57
+ def close
58
+ @archiveIO.close
59
+ end
60
+
61
+ # Same as #initialize but if a block is passed the opened
62
+ # stream is passed to the block and closed when the block
63
+ # returns.
64
+ def ZipInputStream.open(filename)
65
+ return new(filename) unless block_given?
66
+
67
+ zio = new(filename)
68
+ yield zio
69
+ ensure
70
+ zio.close if zio
71
+ end
72
+
73
+ # Returns a ZipEntry object. It is necessary to call this
74
+ # method on a newly created ZipInputStream before reading from
75
+ # the first entry in the archive. Returns nil when there are
76
+ # no more entries.
77
+
78
+ def get_next_entry
79
+ @archiveIO.seek(@currentEntry.next_header_offset,
80
+ IO::SEEK_SET) if @currentEntry
81
+ open_entry
82
+ end
83
+
84
+ # Rewinds the stream to the beginning of the current entry
85
+ def rewind
86
+ return if @currentEntry.nil?
87
+ @lineno = 0
88
+ @archiveIO.seek(@currentEntry.localHeaderOffset,
89
+ IO::SEEK_SET)
90
+ open_entry
91
+ end
92
+
93
+ # Modeled after IO.sysread
94
+ def sysread(numberOfBytes = nil, buf = nil)
95
+ @decompressor.sysread(numberOfBytes, buf)
96
+ end
97
+
98
+ def eof
99
+ @outputBuffer.empty? && @decompressor.eof
100
+ end
101
+ alias :eof? :eof
102
+
103
+ protected
104
+
105
+ def open_entry
106
+ @currentEntry = ZipEntry.read_local_entry(@archiveIO)
107
+ if (@currentEntry == nil)
108
+ @decompressor = NullDecompressor.instance
109
+ elsif @currentEntry.compression_method == ZipEntry::STORED
110
+ @decompressor = PassThruDecompressor.new(@archiveIO,
111
+ @currentEntry.size)
112
+ elsif @currentEntry.compression_method == ZipEntry::DEFLATED
113
+ @decompressor = Inflater.new(@archiveIO)
114
+ else
115
+ raise ZipCompressionMethodError,
116
+ "Unsupported compression method #{@currentEntry.compression_method}"
117
+ end
118
+ flush
119
+ return @currentEntry
120
+ end
121
+
122
+ def produce_input
123
+ @decompressor.produce_input
124
+ end
125
+
126
+ def input_finished?
127
+ @decompressor.input_finished?
128
+ end
129
+ end
130
+ end
131
+
132
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
133
+ # rubyzip is free software; you can redistribute it and/or
134
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,171 @@
1
+ module Zip
2
+ # ZipOutputStream is the basic class for writing zip files. It is
3
+ # possible to create a ZipOutputStream object directly, passing
4
+ # the zip file name to the constructor, but more often than not
5
+ # the ZipOutputStream will be obtained from a ZipFile (perhaps using the
6
+ # ZipFileSystem interface) object for a particular entry in the zip
7
+ # archive.
8
+ #
9
+ # A ZipOutputStream inherits IOExtras::AbstractOutputStream in order
10
+ # to provide an IO-like interface for writing to a single zip
11
+ # entry. Beyond methods for mimicking an IO-object it contains
12
+ # the method put_next_entry that closes the current entry
13
+ # and creates a new.
14
+ #
15
+ # Please refer to ZipInputStream for example code.
16
+ #
17
+ # java.util.zip.ZipOutputStream is the original inspiration for this
18
+ # class.
19
+
20
+ class ZipOutputStream
21
+ include IOExtras::AbstractOutputStream
22
+
23
+ attr_accessor :comment
24
+
25
+ # Opens the indicated zip file. If a file with that name already
26
+ # exists it will be overwritten.
27
+ def initialize(fileName, stream=false)
28
+ super()
29
+ @fileName = fileName
30
+ if stream
31
+ @outputStream = StringIO.new
32
+ else
33
+ @outputStream = ::File.new(@fileName, "wb")
34
+ end
35
+ @entrySet = ZipEntrySet.new
36
+ @compressor = NullCompressor.instance
37
+ @closed = false
38
+ @currentEntry = nil
39
+ @comment = nil
40
+ end
41
+
42
+ # Same as #initialize but if a block is passed the opened
43
+ # stream is passed to the block and closed when the block
44
+ # returns.
45
+ def ZipOutputStream.open(fileName)
46
+ return new(fileName) unless block_given?
47
+ zos = new(fileName)
48
+ yield zos
49
+ ensure
50
+ zos.close if zos
51
+ end
52
+
53
+ # Same as #open but writes to a filestream instead
54
+ def ZipOutputStream.write_buffer
55
+ zos = new('', true)
56
+ yield zos
57
+ return zos.close_buffer
58
+ end
59
+
60
+ # Closes the stream and writes the central directory to the zip file
61
+ def close
62
+ return if @closed
63
+ finalize_current_entry
64
+ update_local_headers
65
+ write_central_directory
66
+ @outputStream.close
67
+ @closed = true
68
+ end
69
+
70
+ # Closes the stream and writes the central directory to the zip file
71
+ def close_buffer
72
+ return @outputStream if @closed
73
+ finalize_current_entry
74
+ update_local_headers
75
+ write_central_directory
76
+ @closed = true
77
+ return @outputStream
78
+ end
79
+
80
+ # Closes the current entry and opens a new for writing.
81
+ # +entry+ can be a ZipEntry object or a string.
82
+ def put_next_entry(entryname, comment = nil, extra = nil, compression_method = ZipEntry::DEFLATED, level = Zlib::DEFAULT_COMPRESSION)
83
+ raise ZipError, "zip stream is closed" if @closed
84
+ new_entry = ZipEntry.new(@fileName, entryname.to_s)
85
+ new_entry.comment = comment if !comment.nil?
86
+ if (!extra.nil?)
87
+ new_entry.extra = ZipExtraField === extra ? extra : ZipExtraField.new(extra.to_s)
88
+ end
89
+ new_entry.compression_method = compression_method
90
+ init_next_entry(new_entry, level)
91
+ @currentEntry = new_entry
92
+ end
93
+
94
+ def copy_raw_entry(entry)
95
+ entry = entry.dup
96
+ raise ZipError, "zip stream is closed" if @closed
97
+ raise ZipError, "entry is not a ZipEntry" if !entry.kind_of?(ZipEntry)
98
+ finalize_current_entry
99
+ @entrySet << entry
100
+ src_pos = entry.local_entry_offset
101
+ entry.write_local_entry(@outputStream)
102
+ @compressor = NullCompressor.instance
103
+ entry.get_raw_input_stream {
104
+ |is|
105
+ is.seek(src_pos, IO::SEEK_SET)
106
+ IOExtras.copy_stream_n(@outputStream, is, entry.compressed_size)
107
+ }
108
+ @compressor = NullCompressor.instance
109
+ @currentEntry = nil
110
+ end
111
+
112
+ private
113
+ def finalize_current_entry
114
+ return unless @currentEntry
115
+ finish
116
+ @currentEntry.compressed_size = @outputStream.tell - @currentEntry.localHeaderOffset -
117
+ @currentEntry.calculate_local_header_size
118
+ @currentEntry.size = @compressor.size
119
+ @currentEntry.crc = @compressor.crc
120
+ @currentEntry = nil
121
+ @compressor = NullCompressor.instance
122
+ end
123
+
124
+ def init_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION)
125
+ finalize_current_entry
126
+ @entrySet << entry
127
+ entry.write_local_entry(@outputStream)
128
+ @compressor = get_compressor(entry, level)
129
+ end
130
+
131
+ def get_compressor(entry, level)
132
+ case entry.compression_method
133
+ when ZipEntry::DEFLATED then Deflater.new(@outputStream, level)
134
+ when ZipEntry::STORED then PassThruCompressor.new(@outputStream)
135
+ else raise ZipCompressionMethodError,
136
+ "Invalid compression method: '#{entry.compression_method}'"
137
+ end
138
+ end
139
+
140
+ def update_local_headers
141
+ pos = @outputStream.tell
142
+ @entrySet.each {
143
+ |entry|
144
+ @outputStream.pos = entry.localHeaderOffset
145
+ entry.write_local_entry(@outputStream)
146
+ }
147
+ @outputStream.pos = pos
148
+ end
149
+
150
+ def write_central_directory
151
+ cdir = ZipCentralDirectory.new(@entrySet, @comment)
152
+ cdir.write_to_stream(@outputStream)
153
+ end
154
+
155
+ protected
156
+
157
+ def finish
158
+ @compressor.finish
159
+ end
160
+
161
+ public
162
+ # Modeled after IO.<<
163
+ def << (data)
164
+ @compressor << data
165
+ end
166
+ end
167
+ end
168
+
169
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
170
+ # rubyzip is free software; you can redistribute it and/or
171
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,15 @@
1
+ module Zip
2
+ class ZipStreamableDirectory < ZipEntry
3
+ def initialize(zipfile, entry, srcPath = nil, permissionInt = nil)
4
+ super(zipfile, entry)
5
+
6
+ @ftype = :directory
7
+ entry.get_extra_attributes_from_path(srcPath) if (srcPath)
8
+ @unix_perms = permissionInt if (permissionInt)
9
+ end
10
+ end
11
+ end
12
+
13
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
14
+ # rubyzip is free software; you can redistribute it and/or
15
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,47 @@
1
+ module Zip
2
+ class ZipStreamableStream < DelegateClass(ZipEntry) #nodoc:all
3
+ def initialize(entry)
4
+ super(entry)
5
+ @tempFile = Tempfile.new(::File.basename(name), ::File.dirname(zipfile))
6
+ @tempFile.binmode
7
+ end
8
+
9
+ def get_output_stream
10
+ if block_given?
11
+ begin
12
+ yield(@tempFile)
13
+ ensure
14
+ @tempFile.close
15
+ end
16
+ else
17
+ @tempFile
18
+ end
19
+ end
20
+
21
+ def get_input_stream
22
+ if ! @tempFile.closed?
23
+ raise StandardError, "cannot open entry for reading while its open for writing - #{name}"
24
+ end
25
+ @tempFile.open # reopens tempfile from top
26
+ @tempFile.binmode
27
+ if block_given?
28
+ begin
29
+ yield(@tempFile)
30
+ ensure
31
+ @tempFile.close
32
+ end
33
+ else
34
+ @tempFile
35
+ end
36
+ end
37
+
38
+ def write_to_zip_output_stream(aZipOutputStream)
39
+ aZipOutputStream.put_next_entry(self)
40
+ get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) }
41
+ end
42
+ end
43
+ end
44
+
45
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
46
+ # rubyzip is free software; you can redistribute it and/or
47
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,610 @@
1
+ require 'zip/zip'
2
+
3
+ module Zip
4
+
5
+ # The ZipFileSystem API provides an API for accessing entries in
6
+ # a zip archive that is similar to ruby's builtin File and Dir
7
+ # classes.
8
+ #
9
+ # Requiring 'zip/zipfilesystem' includes this module in ZipFile
10
+ # making the methods in this module available on ZipFile objects.
11
+ #
12
+ # Using this API the following example creates a new zip file
13
+ # <code>my.zip</code> containing a normal entry with the name
14
+ # <code>first.txt</code>, a directory entry named <code>mydir</code>
15
+ # and finally another normal entry named <code>second.txt</code>
16
+ #
17
+ # require 'zip/zipfilesystem'
18
+ #
19
+ # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
20
+ # |zipfile|
21
+ # zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" }
22
+ # zipfile.dir.mkdir("mydir")
23
+ # zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" }
24
+ # }
25
+ #
26
+ # Reading is as easy as writing, as the following example shows. The
27
+ # example writes the contents of <code>first.txt</code> from zip archive
28
+ # <code>my.zip</code> to standard out.
29
+ #
30
+ # require 'zip/zipfilesystem'
31
+ #
32
+ # Zip::ZipFile.open("my.zip") {
33
+ # |zipfile|
34
+ # puts zipfile.file.read("first.txt")
35
+ # }
36
+
37
+ module ZipFileSystem
38
+
39
+ def initialize # :nodoc:
40
+ mappedZip = ZipFileNameMapper.new(self)
41
+ @zipFsDir = ZipFsDir.new(mappedZip)
42
+ @zipFsFile = ZipFsFile.new(mappedZip)
43
+ @zipFsDir.file = @zipFsFile
44
+ @zipFsFile.dir = @zipFsDir
45
+ end
46
+
47
+ # Returns a ZipFsDir which is much like ruby's builtin Dir (class)
48
+ # object, except it works on the ZipFile on which this method is
49
+ # invoked
50
+ def dir
51
+ @zipFsDir
52
+ end
53
+
54
+ # Returns a ZipFsFile which is much like ruby's builtin File (class)
55
+ # object, except it works on the ZipFile on which this method is
56
+ # invoked
57
+ def file
58
+ @zipFsFile
59
+ end
60
+
61
+ # Instances of this class are normally accessed via the accessor
62
+ # ZipFile::file. An instance of ZipFsFile behaves like ruby's
63
+ # builtin File (class) object, except it works on ZipFile entries.
64
+ #
65
+ # The individual methods are not documented due to their
66
+ # similarity with the methods in File
67
+ class ZipFsFile
68
+
69
+ attr_writer :dir
70
+ # protected :dir
71
+
72
+ class ZipFsStat
73
+ def initialize(zipFsFile, entryName)
74
+ @zipFsFile = zipFsFile
75
+ @entryName = entryName
76
+ end
77
+
78
+ def forward_invoke(msg)
79
+ @zipFsFile.send(msg, @entryName)
80
+ end
81
+
82
+ def kind_of?(t)
83
+ super || t == ::File::Stat
84
+ end
85
+
86
+ forward_message :forward_invoke, :file?, :directory?, :pipe?, :chardev?
87
+ forward_message :forward_invoke, :symlink?, :socket?, :blockdev?
88
+ forward_message :forward_invoke, :readable?, :readable_real?
89
+ forward_message :forward_invoke, :writable?, :writable_real?
90
+ forward_message :forward_invoke, :executable?, :executable_real?
91
+ forward_message :forward_invoke, :sticky?, :owned?, :grpowned?
92
+ forward_message :forward_invoke, :setuid?, :setgid?
93
+ forward_message :forward_invoke, :zero?
94
+ forward_message :forward_invoke, :size, :size?
95
+ forward_message :forward_invoke, :mtime, :atime, :ctime
96
+
97
+ def blocks; nil; end
98
+
99
+ def get_entry
100
+ @zipFsFile.__send__(:get_entry, @entryName)
101
+ end
102
+ private :get_entry
103
+
104
+ def gid
105
+ e = get_entry
106
+ if e.extra.member? "IUnix"
107
+ e.extra["IUnix"].gid || 0
108
+ else
109
+ 0
110
+ end
111
+ end
112
+
113
+ def uid
114
+ e = get_entry
115
+ if e.extra.member? "IUnix"
116
+ e.extra["IUnix"].uid || 0
117
+ else
118
+ 0
119
+ end
120
+ end
121
+
122
+ def ino; 0; end
123
+
124
+ def dev; 0; end
125
+
126
+ def rdev; 0; end
127
+
128
+ def rdev_major; 0; end
129
+
130
+ def rdev_minor; 0; end
131
+
132
+ def ftype
133
+ if file?
134
+ return "file"
135
+ elsif directory?
136
+ return "directory"
137
+ else
138
+ raise StandardError, "Unknown file type"
139
+ end
140
+ end
141
+
142
+ def nlink; 1; end
143
+
144
+ def blksize; nil; end
145
+
146
+ def mode
147
+ e = get_entry
148
+ if e.fstype == 3
149
+ e.externalFileAttributes >> 16
150
+ else
151
+ 33206 # 33206 is equivalent to -rw-rw-rw-
152
+ end
153
+ end
154
+ end
155
+
156
+ def initialize(mappedZip)
157
+ @mappedZip = mappedZip
158
+ end
159
+
160
+ def get_entry(fileName)
161
+ if ! exists?(fileName)
162
+ raise Errno::ENOENT, "No such file or directory - #{fileName}"
163
+ end
164
+ @mappedZip.find_entry(fileName)
165
+ end
166
+ private :get_entry
167
+
168
+ def unix_mode_cmp(fileName, mode)
169
+ begin
170
+ e = get_entry(fileName)
171
+ e.fstype == 3 && ((e.externalFileAttributes >> 16) & mode ) != 0
172
+ rescue Errno::ENOENT
173
+ false
174
+ end
175
+ end
176
+ private :unix_mode_cmp
177
+
178
+ def exists?(fileName)
179
+ expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil
180
+ end
181
+ alias :exist? :exists?
182
+
183
+ # Permissions not implemented, so if the file exists it is accessible
184
+ alias owned? exists?
185
+ alias grpowned? exists?
186
+
187
+ def readable?(fileName)
188
+ unix_mode_cmp(fileName, 0444)
189
+ end
190
+ alias readable_real? readable?
191
+
192
+ def writable?(fileName)
193
+ unix_mode_cmp(fileName, 0222)
194
+ end
195
+ alias writable_real? writable?
196
+
197
+ def executable?(fileName)
198
+ unix_mode_cmp(fileName, 0111)
199
+ end
200
+ alias executable_real? executable?
201
+
202
+ def setuid?(fileName)
203
+ unix_mode_cmp(fileName, 04000)
204
+ end
205
+
206
+ def setgid?(fileName)
207
+ unix_mode_cmp(fileName, 02000)
208
+ end
209
+
210
+ def sticky?(fileName)
211
+ unix_mode_cmp(fileName, 01000)
212
+ end
213
+
214
+ def umask(*args)
215
+ ::File.umask(*args)
216
+ end
217
+
218
+ def truncate(fileName, len)
219
+ raise StandardError, "truncate not supported"
220
+ end
221
+
222
+ def directory?(fileName)
223
+ entry = @mappedZip.find_entry(fileName)
224
+ expand_path(fileName) == "/" || (entry != nil && entry.directory?)
225
+ end
226
+
227
+ def open(fileName, openMode = "r", &block)
228
+ openMode.gsub!("b", "") # ignore b option
229
+ case openMode
230
+ when "r"
231
+ @mappedZip.get_input_stream(fileName, &block)
232
+ when "w"
233
+ @mappedZip.get_output_stream(fileName, &block)
234
+ else
235
+ raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r"
236
+ end
237
+ end
238
+
239
+ def new(fileName, openMode = "r")
240
+ open(fileName, openMode)
241
+ end
242
+
243
+ def size(fileName)
244
+ @mappedZip.get_entry(fileName).size
245
+ end
246
+
247
+ # Returns nil for not found and nil for directories
248
+ def size?(fileName)
249
+ entry = @mappedZip.find_entry(fileName)
250
+ return (entry == nil || entry.directory?) ? nil : entry.size
251
+ end
252
+
253
+ def chown(ownerInt, groupInt, *filenames)
254
+ filenames.each { |fileName|
255
+ e = get_entry(fileName)
256
+ unless e.extra.member?("IUnix")
257
+ e.extra.create("IUnix")
258
+ end
259
+ e.extra["IUnix"].uid = ownerInt
260
+ e.extra["IUnix"].gid = groupInt
261
+ }
262
+ filenames.size
263
+ end
264
+
265
+ def chmod (modeInt, *filenames)
266
+ filenames.each { |fileName|
267
+ e = get_entry(fileName)
268
+ e.fstype = 3 # force convertion filesystem type to unix
269
+ e.externalFileAttributes = modeInt << 16
270
+ }
271
+ filenames.size
272
+ end
273
+
274
+ def zero?(fileName)
275
+ sz = size(fileName)
276
+ sz == nil || sz == 0
277
+ rescue Errno::ENOENT
278
+ false
279
+ end
280
+
281
+ def file?(fileName)
282
+ entry = @mappedZip.find_entry(fileName)
283
+ entry != nil && entry.file?
284
+ end
285
+
286
+ def dirname(fileName)
287
+ ::File.dirname(fileName)
288
+ end
289
+
290
+ def basename(fileName)
291
+ ::File.basename(fileName)
292
+ end
293
+
294
+ def split(fileName)
295
+ ::File.split(fileName)
296
+ end
297
+
298
+ def join(*fragments)
299
+ ::File.join(*fragments)
300
+ end
301
+
302
+ def utime(modifiedTime, *fileNames)
303
+ fileNames.each { |fileName|
304
+ get_entry(fileName).time = modifiedTime
305
+ }
306
+ end
307
+
308
+ def mtime(fileName)
309
+ @mappedZip.get_entry(fileName).mtime
310
+ end
311
+
312
+ def atime(fileName)
313
+ e = get_entry(fileName)
314
+ if e.extra.member? "UniversalTime"
315
+ e.extra["UniversalTime"].atime
316
+ else
317
+ nil
318
+ end
319
+ end
320
+
321
+ def ctime(fileName)
322
+ e = get_entry(fileName)
323
+ if e.extra.member? "UniversalTime"
324
+ e.extra["UniversalTime"].ctime
325
+ else
326
+ nil
327
+ end
328
+ end
329
+
330
+ def pipe?(filename)
331
+ false
332
+ end
333
+
334
+ def blockdev?(filename)
335
+ false
336
+ end
337
+
338
+ def chardev?(filename)
339
+ false
340
+ end
341
+
342
+ def symlink?(fileName)
343
+ false
344
+ end
345
+
346
+ def socket?(fileName)
347
+ false
348
+ end
349
+
350
+ def ftype(fileName)
351
+ @mappedZip.get_entry(fileName).directory? ? "directory" : "file"
352
+ end
353
+
354
+ def readlink(fileName)
355
+ raise NotImplementedError, "The readlink() function is not implemented"
356
+ end
357
+
358
+ def symlink(fileName, symlinkName)
359
+ raise NotImplementedError, "The symlink() function is not implemented"
360
+ end
361
+
362
+ def link(fileName, symlinkName)
363
+ raise NotImplementedError, "The link() function is not implemented"
364
+ end
365
+
366
+ def pipe
367
+ raise NotImplementedError, "The pipe() function is not implemented"
368
+ end
369
+
370
+ def stat(fileName)
371
+ if ! exists?(fileName)
372
+ raise Errno::ENOENT, fileName
373
+ end
374
+ ZipFsStat.new(self, fileName)
375
+ end
376
+
377
+ alias lstat stat
378
+
379
+ def readlines(fileName)
380
+ open(fileName) { |is| is.readlines }
381
+ end
382
+
383
+ def read(fileName)
384
+ @mappedZip.read(fileName)
385
+ end
386
+
387
+ def popen(*args, &aProc)
388
+ File.popen(*args, &aProc)
389
+ end
390
+
391
+ def foreach(fileName, aSep = $/, &aProc)
392
+ open(fileName) { |is| is.each_line(aSep, &aProc) }
393
+ end
394
+
395
+ def delete(*args)
396
+ args.each {
397
+ |fileName|
398
+ if directory?(fileName)
399
+ raise Errno::EISDIR, "Is a directory - \"#{fileName}\""
400
+ end
401
+ @mappedZip.remove(fileName)
402
+ }
403
+ end
404
+
405
+ def rename(fileToRename, newName)
406
+ @mappedZip.rename(fileToRename, newName) { true }
407
+ end
408
+
409
+ alias :unlink :delete
410
+
411
+ def expand_path(aPath)
412
+ @mappedZip.expand_path(aPath)
413
+ end
414
+ end
415
+
416
+ # Instances of this class are normally accessed via the accessor
417
+ # ZipFile::dir. An instance of ZipFsDir behaves like ruby's
418
+ # builtin Dir (class) object, except it works on ZipFile entries.
419
+ #
420
+ # The individual methods are not documented due to their
421
+ # similarity with the methods in Dir
422
+ class ZipFsDir
423
+
424
+ def initialize(mappedZip)
425
+ @mappedZip = mappedZip
426
+ end
427
+
428
+ attr_writer :file
429
+
430
+ def new(aDirectoryName)
431
+ ZipFsDirIterator.new(entries(aDirectoryName))
432
+ end
433
+
434
+ def open(aDirectoryName)
435
+ dirIt = new(aDirectoryName)
436
+ if block_given?
437
+ begin
438
+ yield(dirIt)
439
+ return nil
440
+ ensure
441
+ dirIt.close
442
+ end
443
+ end
444
+ dirIt
445
+ end
446
+
447
+ def pwd; @mappedZip.pwd; end
448
+ alias getwd pwd
449
+
450
+ def chdir(aDirectoryName)
451
+ unless @file.stat(aDirectoryName).directory?
452
+ raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}"
453
+ end
454
+ @mappedZip.pwd = @file.expand_path(aDirectoryName)
455
+ end
456
+
457
+ def entries(aDirectoryName)
458
+ entries = []
459
+ foreach(aDirectoryName) { |e| entries << e }
460
+ entries
461
+ end
462
+
463
+ def foreach(aDirectoryName)
464
+ unless @file.stat(aDirectoryName).directory?
465
+ raise Errno::ENOTDIR, aDirectoryName
466
+ end
467
+ path = @file.expand_path(aDirectoryName).ensure_end("/")
468
+
469
+ subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$")
470
+ @mappedZip.each {
471
+ |fileName|
472
+ match = subDirEntriesRegex.match(fileName)
473
+ yield(match[1]) unless match == nil
474
+ }
475
+ end
476
+
477
+ def delete(entryName)
478
+ unless @file.stat(entryName).directory?
479
+ raise Errno::EINVAL, "Invalid argument - #{entryName}"
480
+ end
481
+ @mappedZip.remove(entryName)
482
+ end
483
+ alias rmdir delete
484
+ alias unlink delete
485
+
486
+ def mkdir(entryName, permissionInt = 0755)
487
+ @mappedZip.mkdir(entryName, permissionInt)
488
+ end
489
+
490
+ def chroot(*args)
491
+ raise NotImplementedError, "The chroot() function is not implemented"
492
+ end
493
+
494
+ end
495
+
496
+ class ZipFsDirIterator # :nodoc:all
497
+ include Enumerable
498
+
499
+ def initialize(arrayOfFileNames)
500
+ @fileNames = arrayOfFileNames
501
+ @index = 0
502
+ end
503
+
504
+ def close
505
+ @fileNames = nil
506
+ end
507
+
508
+ def each(&aProc)
509
+ raise IOError, "closed directory" if @fileNames == nil
510
+ @fileNames.each(&aProc)
511
+ end
512
+
513
+ def read
514
+ raise IOError, "closed directory" if @fileNames == nil
515
+ @fileNames[(@index+=1)-1]
516
+ end
517
+
518
+ def rewind
519
+ raise IOError, "closed directory" if @fileNames == nil
520
+ @index = 0
521
+ end
522
+
523
+ def seek(anIntegerPosition)
524
+ raise IOError, "closed directory" if @fileNames == nil
525
+ @index = anIntegerPosition
526
+ end
527
+
528
+ def tell
529
+ raise IOError, "closed directory" if @fileNames == nil
530
+ @index
531
+ end
532
+ end
533
+
534
+ # All access to ZipFile from ZipFsFile and ZipFsDir goes through a
535
+ # ZipFileNameMapper, which has one responsibility: ensure
536
+ class ZipFileNameMapper # :nodoc:all
537
+ include Enumerable
538
+
539
+ def initialize(zipFile)
540
+ @zipFile = zipFile
541
+ @pwd = "/"
542
+ end
543
+
544
+ attr_accessor :pwd
545
+
546
+ def find_entry(fileName)
547
+ @zipFile.find_entry(expand_to_entry(fileName))
548
+ end
549
+
550
+ def get_entry(fileName)
551
+ @zipFile.get_entry(expand_to_entry(fileName))
552
+ end
553
+
554
+ def get_input_stream(fileName, &aProc)
555
+ @zipFile.get_input_stream(expand_to_entry(fileName), &aProc)
556
+ end
557
+
558
+ def get_output_stream(fileName, &aProc)
559
+ @zipFile.get_output_stream(expand_to_entry(fileName), &aProc)
560
+ end
561
+
562
+ def read(fileName)
563
+ @zipFile.read(expand_to_entry(fileName))
564
+ end
565
+
566
+ def remove(fileName)
567
+ @zipFile.remove(expand_to_entry(fileName))
568
+ end
569
+
570
+ def rename(fileName, newName, &continueOnExistsProc)
571
+ @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName),
572
+ &continueOnExistsProc)
573
+ end
574
+
575
+ def mkdir(fileName, permissionInt = 0755)
576
+ @zipFile.mkdir(expand_to_entry(fileName), permissionInt)
577
+ end
578
+
579
+ # Turns entries into strings and adds leading /
580
+ # and removes trailing slash on directories
581
+ def each
582
+ @zipFile.each {
583
+ |e|
584
+ yield("/"+e.to_s.chomp("/"))
585
+ }
586
+ end
587
+
588
+ def expand_path(aPath)
589
+ expanded = aPath.starts_with("/") ? aPath : @pwd.ensure_end("/") + aPath
590
+ expanded.gsub!(/\/\.(\/|$)/, "")
591
+ expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "")
592
+ expanded.empty? ? "/" : expanded
593
+ end
594
+
595
+ private
596
+
597
+ def expand_to_entry(aPath)
598
+ expand_path(aPath).lchop
599
+ end
600
+ end
601
+ end
602
+
603
+ class ZipFile
604
+ include ZipFileSystem
605
+ end
606
+ end
607
+
608
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
609
+ # rubyzip is free software; you can redistribute it and/or
610
+ # modify it under the terms of the ruby license.