perkeep/server/camlistored/ui/closure/closure.go

144 lines
3.5 KiB
Go

/*
Copyright 2013 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package closure
import (
"archive/zip"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"strings"
"sync"
"time"
)
// ZipData is either the empty string (when compiling with "go get",
// or the devcam server), or is initialized to a base64-encoded zip file
// of the Closure library (when using make.go, which puts an extra
// file in this package containing an init function to set ZipData).
var ZipData string
var ZipModTime time.Time
func FileSystem() (http.FileSystem, error) {
if ZipData == "" {
return nil, os.ErrNotExist
}
zr, err := zip.NewReader(strings.NewReader(ZipData), int64(len(ZipData)))
if err != nil {
return nil, err
}
m := make(map[string]*fileInfo)
for _, zf := range zr.File {
if !strings.HasPrefix(zf.Name, "closure/") {
continue
}
fi, err := newFileInfo(zf)
if err != nil {
return nil, fmt.Errorf("Error reading zip file %q: %v", zf.Name, err)
}
m[strings.TrimPrefix(zf.Name, "closure")] = fi
}
return &fs{zr, m}, nil
}
type fs struct {
zr *zip.Reader
m map[string]*fileInfo // keyed by what Open gets. see Open's comment.
}
var nopCloser = ioutil.NopCloser(nil)
// Open is called with names like "/goog/base.js", but the zip contains Files named like "closure/goog/base.js".
func (s *fs) Open(name string) (http.File, error) {
fi, ok := s.m[name]
if !ok {
return nil, os.ErrNotExist
}
return &file{fileInfo: fi}, nil
}
// a file is an http.File, wrapping a *fileInfo with a lazily-constructed SectionReader.
type file struct {
*fileInfo
once sync.Once // for making the SectionReader
sr *io.SectionReader
}
func (f *file) Read(p []byte) (n int, err error) {
f.once.Do(f.initReader)
return f.sr.Read(p)
}
func (f *file) Seek(offset int64, whence int) (ret int64, err error) {
f.once.Do(f.initReader)
return f.sr.Seek(offset, whence)
}
func (f *file) initReader() {
f.sr = io.NewSectionReader(f.fileInfo.ra, 0, f.Size())
}
func newFileInfo(zf *zip.File) (*fileInfo, error) {
rc, err := zf.Open()
if err != nil {
return nil, err
}
all, err := ioutil.ReadAll(rc)
if err != nil {
return nil, err
}
rc.Close()
return &fileInfo{
fullName: zf.Name,
regdata: all,
Closer: nopCloser,
ra: bytes.NewReader(all),
}, nil
}
type fileInfo struct {
fullName string
regdata []byte // non-nil if regular file
ra io.ReaderAt // over regdata
io.Closer
}
func (f *fileInfo) IsDir() bool { return f.regdata == nil }
func (f *fileInfo) Size() int64 { return int64(len(f.regdata)) }
func (f *fileInfo) ModTime() time.Time { return ZipModTime }
func (f *fileInfo) Name() string { return path.Base(f.fullName) }
func (f *fileInfo) Stat() (os.FileInfo, error) { return f, nil }
func (f *fileInfo) Sys() interface{} { return nil }
func (f *fileInfo) Readdir(count int) ([]os.FileInfo, error) {
// TODO: implement.
return nil, errors.New("TODO")
}
func (f *fileInfo) Mode() os.FileMode {
if f.IsDir() {
return 0755 | os.ModeDir
}
return 0644
}