123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- // Copyright 2014 Google Inc. All Rights Reserved.
- //
- // 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 contains an HTTP file system that works with zip contents.
- package fs
- import (
- "archive/zip"
- "bytes"
- "errors"
- "io"
- "io/ioutil"
- "net/http"
- "os"
- "strings"
- "sync"
- )
- var zipData string
- type statikFS struct {
- files map[string]*zip.File
- }
- // Registers zip contents data, later used to initialize
- // the statik file system.
- func Register(data string) {
- zipData = data
- }
- // Creates a new file system with the registered zip contents data.
- func New() (http.FileSystem, error) {
- if zipData == "" {
- return nil, errors.New("statik/fs: No zip data registered.")
- }
- zipReader, err := zip.NewReader(strings.NewReader(zipData), int64(len(zipData)))
- if err != nil {
- return nil, err
- }
- files := make(map[string]*zip.File)
- for _, file := range zipReader.File {
- files["/"+file.Name] = file
- }
- return &statikFS{files: files}, nil
- }
- // Opens a file, unzip the contents and initializes
- // readers. Returns os.ErrNotExists if file is not
- // found in the archive.
- func (fs *statikFS) Open(name string) (http.File, error) {
- name = strings.Replace(name, "//", "/", -1)
- f, ok := fs.files[name]
- // The file doesn't match, but maybe it's a directory,
- // thus we should look for index.html
- if !ok {
- indexName := strings.Replace(name+"/index.html", "//", "/", -1)
- f, ok = fs.files[indexName]
- if !ok {
- return nil, os.ErrNotExist
- }
- return newFile(f, true)
- }
- return newFile(f, false)
- }
- var nopCloser = ioutil.NopCloser(nil)
- func newFile(zf *zip.File, isDir bool) (*file, error) {
- rc, err := zf.Open()
- if err != nil {
- return nil, err
- }
- defer rc.Close()
- all, err := ioutil.ReadAll(rc)
- if err != nil {
- return nil, err
- }
- return &file{
- FileInfo: zf.FileInfo(),
- data: all,
- readerAt: bytes.NewReader(all),
- Closer: nopCloser,
- isDir: isDir,
- }, nil
- }
- // Represents an HTTP file, acts as a bridge between
- // zip.File and http.File.
- type file struct {
- os.FileInfo
- io.Closer
- data []byte // non-nil if regular file
- reader *io.SectionReader
- readerAt io.ReaderAt // over data
- isDir bool
- once sync.Once
- }
- func (f *file) newReader() {
- f.reader = io.NewSectionReader(f.readerAt, 0, f.FileInfo.Size())
- }
- // Reads bytes into p, returns the number of read bytes.
- func (f *file) Read(p []byte) (n int, err error) {
- f.once.Do(f.newReader)
- return f.reader.Read(p)
- }
- // Seeks to the offset.
- func (f *file) Seek(offset int64, whence int) (ret int64, err error) {
- f.once.Do(f.newReader)
- return f.reader.Seek(offset, whence)
- }
- // Stats the file.
- func (f *file) Stat() (os.FileInfo, error) {
- return f, nil
- }
- // IsDir returns true if the file location represents a directory.
- func (f *file) IsDir() bool {
- return f.isDir
- }
- // Returns an empty slice of files, directory
- // listing is disabled.
- func (f *file) Readdir(count int) ([]os.FileInfo, error) {
- // directory listing is disabled.
- return make([]os.FileInfo, 0), nil
- }
|