OLD | NEW |
(Empty) | |
| 1 // Copyright 2011 The Go Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style |
| 3 // license that can be found in the LICENSE file. |
| 4 |
| 5 // This file provides an implementation of the http.FileSystem |
| 6 // interface based on the contents of a .zip file. |
| 7 // |
| 8 // Assumptions: |
| 9 // |
| 10 // - The file paths stored in the zip file must use a slash ('/') as path |
| 11 // separator; and they must be relative (i.e., they must not start with |
| 12 // a '/' - this is usually the case if the file was created w/o special |
| 13 // options). |
| 14 // - The zip file system treats the file paths found in the zip internally |
| 15 // like absolute paths w/o a leading '/'; i.e., the paths are considered |
| 16 // relative to the root of the file system. |
| 17 // - All path arguments to file system methods must be absolute paths. |
| 18 |
| 19 // TODO(gri) Should define a commonly used FileSystem API that is the same |
| 20 // for http and godoc. Then we only need one zip-file based file |
| 21 // system implementation. |
| 22 |
| 23 package main |
| 24 |
| 25 import ( |
| 26 "archive/zip" |
| 27 "fmt" |
| 28 "http" |
| 29 "io" |
| 30 "os" |
| 31 "path" |
| 32 "sort" |
| 33 "strings" |
| 34 ) |
| 35 |
| 36 // We cannot import syscall on app engine. |
| 37 // TODO(gri) Once we have a truly abstract FileInfo implementation |
| 38 // this won't be needed anymore. |
| 39 const ( |
| 40 S_IFDIR = 0x4000 // == syscall.S_IFDIR |
| 41 S_IFREG = 0x8000 // == syscall.S_IFREG |
| 42 ) |
| 43 |
| 44 // httpZipFile is the zip-file based implementation of http.File |
| 45 type httpZipFile struct { |
| 46 info os.FileInfo |
| 47 io.ReadCloser // nil for directory |
| 48 list zipList |
| 49 } |
| 50 |
| 51 func (f *httpZipFile) Close() os.Error { |
| 52 if f.info.IsRegular() { |
| 53 return f.ReadCloser.Close() |
| 54 } |
| 55 f.list = nil |
| 56 return nil |
| 57 } |
| 58 |
| 59 func (f *httpZipFile) Stat() (*os.FileInfo, os.Error) { |
| 60 return &f.info, nil |
| 61 } |
| 62 |
| 63 func (f *httpZipFile) Readdir(count int) ([]os.FileInfo, os.Error) { |
| 64 println("Readdir", f.info.Name) |
| 65 if f.info.IsRegular() { |
| 66 return nil, fmt.Errorf("Readdir called for regular file: %s", f.
info.Name) |
| 67 } |
| 68 |
| 69 var list []os.FileInfo |
| 70 dirname := zipPath(f.info.Name) + "/" |
| 71 prevname := "" |
| 72 for i, e := range f.list { |
| 73 if count == 0 { |
| 74 f.list = f.list[i:] |
| 75 break |
| 76 } |
| 77 if !strings.HasPrefix(e.Name, dirname) { |
| 78 f.list = nil |
| 79 break // not in the same directory anymore |
| 80 } |
| 81 name := e.Name[len(dirname):] // local name |
| 82 var mode uint32 |
| 83 var size, mtime_ns int64 |
| 84 if i := strings.IndexRune(name, '/'); i >= 0 { |
| 85 // We infer directories from files in subdirectories. |
| 86 // If we have x/y, return a directory entry for x. |
| 87 name = name[0:i] // keep local directory name only |
| 88 mode = S_IFDIR |
| 89 // no size or mtime_ns for directories |
| 90 } else { |
| 91 mode = S_IFREG |
| 92 size = int64(e.UncompressedSize) |
| 93 mtime_ns = e.Mtime_ns() |
| 94 } |
| 95 // If we have x/y and x/z, don't return two directory entries fo
r x. |
| 96 // TODO(gri): It should be possible to do this more efficiently |
| 97 // by determining the (fs.list) range of local directory entries |
| 98 // (via two binary searches). |
| 99 if name != prevname { |
| 100 list = append(list, os.FileInfo{ |
| 101 Name: name, |
| 102 Mode: mode, |
| 103 Size: size, |
| 104 Mtime_ns: mtime_ns, |
| 105 }) |
| 106 prevname = name |
| 107 count-- |
| 108 } |
| 109 } |
| 110 |
| 111 if count >= 0 && len(list) == 0 { |
| 112 return nil, os.EOF |
| 113 } |
| 114 |
| 115 return list, nil |
| 116 } |
| 117 |
| 118 func (f *httpZipFile) Read(buf []byte) (int, os.Error) { |
| 119 if f.info.IsRegular() { |
| 120 return f.ReadCloser.Read(buf) |
| 121 } |
| 122 return 0, fmt.Errorf("Read called for directory: %s", f.info.Name) |
| 123 } |
| 124 |
| 125 func (f *httpZipFile) Seek(offset int64, whence int) (int64, os.Error) { |
| 126 return 0, fmt.Errorf("Seek not implemented for zip file entry: %s", f.in
fo.Name) |
| 127 } |
| 128 |
| 129 // httpZipFS is the zip-file based implementation of http.FileSystem |
| 130 type httpZipFS struct { |
| 131 *zip.ReadCloser |
| 132 list zipList |
| 133 root string |
| 134 } |
| 135 |
| 136 func (fs *httpZipFS) Open(abspath string) (http.File, os.Error) { |
| 137 name := path.Join(fs.root, abspath) |
| 138 index := fs.list.lookup(name) |
| 139 if index < 0 { |
| 140 return nil, fmt.Errorf("file not found: %s", abspath) |
| 141 } |
| 142 |
| 143 if f := fs.list[index]; f.Name == name { |
| 144 // exact match found - must be a file |
| 145 rc, err := f.Open() |
| 146 if err != nil { |
| 147 return nil, err |
| 148 } |
| 149 return &httpZipFile{ |
| 150 os.FileInfo{ |
| 151 Name: abspath, |
| 152 Mode: S_IFREG, |
| 153 Size: int64(f.UncompressedSize), |
| 154 Mtime_ns: f.Mtime_ns(), |
| 155 }, |
| 156 rc, |
| 157 nil, |
| 158 }, nil |
| 159 } |
| 160 |
| 161 // not an exact match - must be a directory |
| 162 println("opened directory", abspath, len(fs.list[index:])) |
| 163 return &httpZipFile{ |
| 164 os.FileInfo{ |
| 165 Name: abspath, |
| 166 Mode: S_IFDIR, |
| 167 // no size or mtime_ns for directories |
| 168 }, |
| 169 nil, |
| 170 fs.list[index:], |
| 171 }, nil |
| 172 } |
| 173 |
| 174 func (fs *httpZipFS) Close() os.Error { |
| 175 fs.list = nil |
| 176 return fs.ReadCloser.Close() |
| 177 } |
| 178 |
| 179 func NewHttpZipFS(rc *zip.ReadCloser, root string) http.FileSystem { |
| 180 list := make(zipList, len(rc.File)) |
| 181 copy(list, rc.File) // sort a copy of rc.File |
| 182 sort.Sort(list) |
| 183 return &httpZipFS{rc, list, zipPath(root)} |
| 184 } |
OLD | NEW |