github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/server/camlistored/ui/closure/closure.go (about) 1 /* 2 Copyright 2013 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package closure 18 19 import ( 20 "archive/zip" 21 "bytes" 22 "errors" 23 "fmt" 24 "io" 25 "io/ioutil" 26 "net/http" 27 "os" 28 "path" 29 "strings" 30 "sync" 31 "time" 32 ) 33 34 // ZipData is either the empty string (when compiling with "go get", 35 // or the devcam server), or is initialized to a base64-encoded zip file 36 // of the Closure library (when using make.go, which puts an extra 37 // file in this package containing an init function to set ZipData). 38 var ZipData string 39 var ZipModTime time.Time 40 41 func FileSystem() (http.FileSystem, error) { 42 if ZipData == "" { 43 return nil, os.ErrNotExist 44 } 45 zr, err := zip.NewReader(strings.NewReader(ZipData), int64(len(ZipData))) 46 if err != nil { 47 return nil, err 48 } 49 m := make(map[string]*fileInfo) 50 for _, zf := range zr.File { 51 if !strings.HasPrefix(zf.Name, "closure/") { 52 continue 53 } 54 fi, err := newFileInfo(zf) 55 if err != nil { 56 return nil, fmt.Errorf("Error reading zip file %q: %v", zf.Name, err) 57 } 58 m[strings.TrimPrefix(zf.Name, "closure")] = fi 59 } 60 return &fs{zr, m}, nil 61 62 } 63 64 type fs struct { 65 zr *zip.Reader 66 m map[string]*fileInfo // keyed by what Open gets. see Open's comment. 67 } 68 69 var nopCloser = ioutil.NopCloser(nil) 70 71 // Open is called with names like "/goog/base.js", but the zip contains Files named like "closure/goog/base.js". 72 func (s *fs) Open(name string) (http.File, error) { 73 fi, ok := s.m[name] 74 if !ok { 75 return nil, os.ErrNotExist 76 } 77 return &file{fileInfo: fi}, nil 78 } 79 80 // a file is an http.File, wrapping a *fileInfo with a lazily-constructed SectionReader. 81 type file struct { 82 *fileInfo 83 once sync.Once // for making the SectionReader 84 sr *io.SectionReader 85 } 86 87 func (f *file) Read(p []byte) (n int, err error) { 88 f.once.Do(f.initReader) 89 return f.sr.Read(p) 90 } 91 92 func (f *file) Seek(offset int64, whence int) (ret int64, err error) { 93 f.once.Do(f.initReader) 94 return f.sr.Seek(offset, whence) 95 } 96 97 func (f *file) initReader() { 98 f.sr = io.NewSectionReader(f.fileInfo.ra, 0, f.Size()) 99 } 100 101 func newFileInfo(zf *zip.File) (*fileInfo, error) { 102 rc, err := zf.Open() 103 if err != nil { 104 return nil, err 105 } 106 all, err := ioutil.ReadAll(rc) 107 if err != nil { 108 return nil, err 109 } 110 rc.Close() 111 return &fileInfo{ 112 fullName: zf.Name, 113 regdata: all, 114 Closer: nopCloser, 115 ra: bytes.NewReader(all), 116 }, nil 117 } 118 119 type fileInfo struct { 120 fullName string 121 regdata []byte // non-nil if regular file 122 ra io.ReaderAt // over regdata 123 io.Closer 124 } 125 126 func (f *fileInfo) IsDir() bool { return f.regdata == nil } 127 func (f *fileInfo) Size() int64 { return int64(len(f.regdata)) } 128 func (f *fileInfo) ModTime() time.Time { return ZipModTime } 129 func (f *fileInfo) Name() string { return path.Base(f.fullName) } 130 func (f *fileInfo) Stat() (os.FileInfo, error) { return f, nil } 131 func (f *fileInfo) Sys() interface{} { return nil } 132 133 func (f *fileInfo) Readdir(count int) ([]os.FileInfo, error) { 134 // TODO: implement. 135 return nil, errors.New("TODO") 136 } 137 138 func (f *fileInfo) Mode() os.FileMode { 139 if f.IsDir() { 140 return 0755 | os.ModeDir 141 } 142 return 0644 143 }