initial import boxroom 0.6.2
[boxroom-stian.git] / vendor / plugins / rubyzip-0.9.1 / lib / zip / zipfilesystem.rb
blob3fa3748c70e5fd8c2f33b39d1089b2d921a7b36f
1 require 'zip/zip'
3 module Zip
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   #   }
37   module ZipFileSystem
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
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
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
69       attr_writer :dir
70 #      protected :dir
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
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
99         def get_entry
100           @zipFsFile.__send__(:get_entry, @entryName)
101         end
102         private :get_entry
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
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
122         def ino; 0; end
124         def dev; 0; end
126         def rdev; 0; end
128         def rdev_major; 0; end
130         def rdev_minor; 0; end
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
142         def nlink; 1; end
143         
144         def blksize; nil; end
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
156       def initialize(mappedZip)
157         @mappedZip = mappedZip
158       end
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
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?
187       def readable?(fileName)
188         unix_mode_cmp(fileName, 0444)
189       end
190       alias readable_real?   readable?
192       def writable?(fileName)
193         unix_mode_cmp(fileName, 0222)
194       end
195       alias writable_real?   writable?
197       def executable?(fileName)
198         unix_mode_cmp(fileName, 0111)
199       end
200       alias executable_real? executable?
202       def setuid?(fileName)
203         unix_mode_cmp(fileName, 04000)
204       end
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
214       def umask(*args)
215         ::File.umask(*args)
216       end
218       def truncate(fileName, len)
219         raise StandardError, "truncate not supported"
220       end
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         case openMode
229         when "r" 
230           @mappedZip.get_input_stream(fileName, &block)
231         when "w"
232           @mappedZip.get_output_stream(fileName, &block)
233         else
234           raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r"
235         end
236       end
238       def new(fileName, openMode = "r")
239         open(fileName, openMode)
240       end
241       
242       def size(fileName)
243         @mappedZip.get_entry(fileName).size
244       end
245       
246       # Returns nil for not found and nil for directories
247       def size?(fileName)
248         entry = @mappedZip.find_entry(fileName)
249         return (entry == nil || entry.directory?) ? nil : entry.size
250       end
251       
252       def chown(ownerInt, groupInt, *filenames)
253         filenames.each { |fileName|
254           e = get_entry(fileName)
255           unless e.extra.member?("IUnix")
256             e.extra.create("IUnix")
257           end
258           e.extra["IUnix"].uid = ownerInt
259           e.extra["IUnix"].gid = groupInt
260         }
261         filenames.size
262       end
264       def chmod (modeInt, *filenames)
265         filenames.each { |fileName|
266           e = get_entry(fileName)
267           e.fstype = 3 # force convertion filesystem type to unix
268           e.externalFileAttributes = modeInt << 16
269         }
270         filenames.size
271       end
273       def zero?(fileName)
274         sz = size(fileName)
275         sz == nil || sz == 0
276       rescue Errno::ENOENT
277         false
278       end
279       
280       def file?(fileName)
281         entry = @mappedZip.find_entry(fileName)
282         entry != nil && entry.file?
283       end      
284       
285       def dirname(fileName)
286         ::File.dirname(fileName)
287       end
288       
289       def basename(fileName)
290         ::File.basename(fileName)
291       end
292       
293       def split(fileName)
294         ::File.split(fileName)
295       end
296       
297       def join(*fragments)
298         ::File.join(*fragments)
299       end
300       
301       def utime(modifiedTime, *fileNames)
302         fileNames.each { |fileName|
303           get_entry(fileName).time = modifiedTime
304         }
305       end
307       def mtime(fileName)
308         @mappedZip.get_entry(fileName).mtime
309       end
310       
311       def atime(fileName)
312         e = get_entry(fileName)
313         if e.extra.member? "UniversalTime"
314           e.extra["UniversalTime"].atime
315         else
316           nil
317         end
318       end
319       
320       def ctime(fileName)
321         e = get_entry(fileName)
322         if e.extra.member? "UniversalTime"
323           e.extra["UniversalTime"].ctime
324         else
325           nil
326         end
327       end
329       def pipe?(filename)
330         false
331       end
332       
333       def blockdev?(filename)
334         false
335       end
336       
337       def chardev?(filename)
338         false
339       end
340       
341       def symlink?(fileName)
342         false
343       end
344       
345       def socket?(fileName)
346         false
347       end
348       
349       def ftype(fileName)
350         @mappedZip.get_entry(fileName).directory? ? "directory" : "file"
351       end
352       
353       def readlink(fileName)
354         raise NotImplementedError, "The readlink() function is not implemented"
355       end
356       
357       def symlink(fileName, symlinkName)
358         raise NotImplementedError, "The symlink() function is not implemented"
359       end
361       def link(fileName, symlinkName)
362         raise NotImplementedError, "The link() function is not implemented"
363       end
365       def pipe
366         raise NotImplementedError, "The pipe() function is not implemented"
367       end
369       def stat(fileName)
370         if ! exists?(fileName)
371           raise Errno::ENOENT, fileName
372         end
373         ZipFsStat.new(self, fileName)
374       end
376       alias lstat stat
378       def readlines(fileName)
379         open(fileName) { |is| is.readlines }
380       end
382       def read(fileName)
383         @mappedZip.read(fileName)
384       end
386       def popen(*args, &aProc)
387         File.popen(*args, &aProc)
388       end
390       def foreach(fileName, aSep = $/, &aProc)
391         open(fileName) { |is| is.each_line(aSep, &aProc) }
392       end
394       def delete(*args)
395         args.each { 
396           |fileName|
397           if directory?(fileName)
398             raise Errno::EISDIR, "Is a directory - \"#{fileName}\""
399           end
400           @mappedZip.remove(fileName) 
401         }
402       end
404       def rename(fileToRename, newName)
405         @mappedZip.rename(fileToRename, newName) { true }
406       end
408       alias :unlink :delete
410       def expand_path(aPath)
411         @mappedZip.expand_path(aPath)
412       end
413     end
415     # Instances of this class are normally accessed via the accessor
416     # ZipFile::dir. An instance of ZipFsDir behaves like ruby's
417     # builtin Dir (class) object, except it works on ZipFile entries.
418     #
419     # The individual methods are not documented due to their
420     # similarity with the methods in Dir
421     class ZipFsDir
422       
423       def initialize(mappedZip)
424         @mappedZip = mappedZip
425       end
426       
427       attr_writer :file
429       def new(aDirectoryName)
430         ZipFsDirIterator.new(entries(aDirectoryName))
431       end
433       def open(aDirectoryName)
434         dirIt = new(aDirectoryName)
435         if block_given?
436           begin
437             yield(dirIt)
438             return nil
439           ensure
440             dirIt.close
441           end
442         end
443         dirIt
444       end
446       def pwd; @mappedZip.pwd; end
447       alias getwd pwd
448       
449       def chdir(aDirectoryName)
450         unless @file.stat(aDirectoryName).directory?
451           raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}"
452         end
453         @mappedZip.pwd = @file.expand_path(aDirectoryName)
454       end
455       
456       def entries(aDirectoryName)
457         entries = []
458         foreach(aDirectoryName) { |e| entries << e }
459         entries
460       end
462       def foreach(aDirectoryName)
463         unless @file.stat(aDirectoryName).directory?
464           raise Errno::ENOTDIR, aDirectoryName
465         end
466         path = @file.expand_path(aDirectoryName).ensure_end("/")
468         subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$")
469         @mappedZip.each { 
470           |fileName|
471           match = subDirEntriesRegex.match(fileName)
472           yield(match[1]) unless match == nil
473         }
474       end
476       def delete(entryName)
477         unless @file.stat(entryName).directory?
478           raise Errno::EINVAL, "Invalid argument - #{entryName}"
479         end
480         @mappedZip.remove(entryName)
481       end
482       alias rmdir  delete
483       alias unlink delete
484       
485       def mkdir(entryName, permissionInt = 0755)
486         @mappedZip.mkdir(entryName, permissionInt)
487       end
488       
489       def chroot(*args)
490         raise NotImplementedError, "The chroot() function is not implemented"
491       end
493     end
495     class ZipFsDirIterator # :nodoc:all
496       include Enumerable
498       def initialize(arrayOfFileNames)
499         @fileNames = arrayOfFileNames
500         @index = 0
501       end
503       def close
504         @fileNames = nil
505       end
507       def each(&aProc)
508         raise IOError, "closed directory" if @fileNames == nil
509         @fileNames.each(&aProc)
510       end
512       def read
513         raise IOError, "closed directory" if @fileNames == nil
514         @fileNames[(@index+=1)-1]
515       end
517       def rewind
518         raise IOError, "closed directory" if @fileNames == nil
519         @index = 0
520       end
522       def seek(anIntegerPosition)
523         raise IOError, "closed directory" if @fileNames == nil
524         @index = anIntegerPosition
525       end
527       def tell
528         raise IOError, "closed directory" if @fileNames == nil
529         @index
530       end
531     end
533     # All access to ZipFile from ZipFsFile and ZipFsDir goes through a
534     # ZipFileNameMapper, which has one responsibility: ensure
535     class ZipFileNameMapper # :nodoc:all
536       include Enumerable
538       def initialize(zipFile)
539         @zipFile = zipFile
540         @pwd = "/"
541       end
542       
543       attr_accessor :pwd
544       
545       def find_entry(fileName)
546         @zipFile.find_entry(expand_to_entry(fileName))
547       end
548       
549       def get_entry(fileName)
550         @zipFile.get_entry(expand_to_entry(fileName))
551       end
553       def get_input_stream(fileName, &aProc)
554         @zipFile.get_input_stream(expand_to_entry(fileName), &aProc)
555       end
556       
557       def get_output_stream(fileName, &aProc)
558         @zipFile.get_output_stream(expand_to_entry(fileName), &aProc)
559       end
561       def read(fileName)
562         @zipFile.read(expand_to_entry(fileName))
563       end
564       
565       def remove(fileName)
566         @zipFile.remove(expand_to_entry(fileName))
567       end
569       def rename(fileName, newName, &continueOnExistsProc)
570         @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName), 
571                         &continueOnExistsProc)
572       end
574       def mkdir(fileName, permissionInt = 0755)
575         @zipFile.mkdir(expand_to_entry(fileName), permissionInt)
576       end
578       # Turns entries into strings and adds leading /
579       # and removes trailing slash on directories
580       def each
581         @zipFile.each {
582           |e|
583           yield("/"+e.to_s.chomp("/"))
584         }
585       end
586       
587       def expand_path(aPath)
588         expanded = aPath.starts_with("/") ? aPath : @pwd.ensure_end("/") + aPath
589         expanded.gsub!(/\/\.(\/|$)/, "")
590         expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "")
591         expanded.empty? ? "/" : expanded
592       end
594       private
596       def expand_to_entry(aPath)
597         expand_path(aPath).lchop
598       end
599     end
600   end
602   class ZipFile
603     include ZipFileSystem
604   end
607 # Copyright (C) 2002, 2003 Thomas Sondergaard
608 # rubyzip is free software; you can redistribute it and/or
609 # modify it under the terms of the ruby license.