| package assetfs |
| |
| import ( |
| "bytes" |
| "errors" |
| "io" |
| "io/ioutil" |
| "net/http" |
| "os" |
| "path" |
| "path/filepath" |
| "strings" |
| "time" |
| ) |
| |
| var ( |
| defaultFileTimestamp = time.Now() |
| ) |
| |
| // FakeFile implements os.FileInfo interface for a given path and size |
| type FakeFile struct { |
| // Path is the path of this file |
| Path string |
| // Dir marks of the path is a directory |
| Dir bool |
| // Len is the length of the fake file, zero if it is a directory |
| Len int64 |
| // Timestamp is the ModTime of this file |
| Timestamp time.Time |
| } |
| |
| func (f *FakeFile) Name() string { |
| _, name := filepath.Split(f.Path) |
| return name |
| } |
| |
| func (f *FakeFile) Mode() os.FileMode { |
| mode := os.FileMode(0644) |
| if f.Dir { |
| return mode | os.ModeDir |
| } |
| return mode |
| } |
| |
| func (f *FakeFile) ModTime() time.Time { |
| return f.Timestamp |
| } |
| |
| func (f *FakeFile) Size() int64 { |
| return f.Len |
| } |
| |
| func (f *FakeFile) IsDir() bool { |
| return f.Mode().IsDir() |
| } |
| |
| func (f *FakeFile) Sys() interface{} { |
| return nil |
| } |
| |
| // AssetFile implements http.File interface for a no-directory file with content |
| type AssetFile struct { |
| *bytes.Reader |
| io.Closer |
| FakeFile |
| } |
| |
| func NewAssetFile(name string, content []byte, timestamp time.Time) *AssetFile { |
| if timestamp.IsZero() { |
| timestamp = defaultFileTimestamp |
| } |
| return &AssetFile{ |
| bytes.NewReader(content), |
| ioutil.NopCloser(nil), |
| FakeFile{name, false, int64(len(content)), timestamp}} |
| } |
| |
| func (f *AssetFile) Readdir(count int) ([]os.FileInfo, error) { |
| return nil, errors.New("not a directory") |
| } |
| |
| func (f *AssetFile) Size() int64 { |
| return f.FakeFile.Size() |
| } |
| |
| func (f *AssetFile) Stat() (os.FileInfo, error) { |
| return f, nil |
| } |
| |
| // AssetDirectory implements http.File interface for a directory |
| type AssetDirectory struct { |
| AssetFile |
| ChildrenRead int |
| Children []os.FileInfo |
| } |
| |
| func NewAssetDirectory(name string, children []string, fs *AssetFS) *AssetDirectory { |
| fileinfos := make([]os.FileInfo, 0, len(children)) |
| for _, child := range children { |
| _, err := fs.AssetDir(filepath.Join(name, child)) |
| fileinfos = append(fileinfos, &FakeFile{child, err == nil, 0, time.Time{}}) |
| } |
| return &AssetDirectory{ |
| AssetFile{ |
| bytes.NewReader(nil), |
| ioutil.NopCloser(nil), |
| FakeFile{name, true, 0, time.Time{}}, |
| }, |
| 0, |
| fileinfos} |
| } |
| |
| func (f *AssetDirectory) Readdir(count int) ([]os.FileInfo, error) { |
| if count <= 0 { |
| return f.Children, nil |
| } |
| if f.ChildrenRead+count > len(f.Children) { |
| count = len(f.Children) - f.ChildrenRead |
| } |
| rv := f.Children[f.ChildrenRead : f.ChildrenRead+count] |
| f.ChildrenRead += count |
| return rv, nil |
| } |
| |
| func (f *AssetDirectory) Stat() (os.FileInfo, error) { |
| return f, nil |
| } |
| |
| // AssetFS implements http.FileSystem, allowing |
| // embedded files to be served from net/http package. |
| type AssetFS struct { |
| // Asset should return content of file in path if exists |
| Asset func(path string) ([]byte, error) |
| // AssetDir should return list of files in the path |
| AssetDir func(path string) ([]string, error) |
| // AssetInfo should return the info of file in path if exists |
| AssetInfo func(path string) (os.FileInfo, error) |
| // Prefix would be prepended to http requests |
| Prefix string |
| } |
| |
| func (fs *AssetFS) Open(name string) (http.File, error) { |
| name = path.Join(fs.Prefix, name) |
| if len(name) > 0 && name[0] == '/' { |
| name = name[1:] |
| } |
| if b, err := fs.Asset(name); err == nil { |
| timestamp := defaultFileTimestamp |
| if fs.AssetInfo != nil { |
| if info, err := fs.AssetInfo(name); err == nil { |
| timestamp = info.ModTime() |
| } |
| } |
| return NewAssetFile(name, b, timestamp), nil |
| } |
| if children, err := fs.AssetDir(name); err == nil { |
| return NewAssetDirectory(name, children, fs), nil |
| } else { |
| // If the error is not found, return an error that will |
| // result in a 404 error. Otherwise the server returns |
| // a 500 error for files not found. |
| if strings.Contains(err.Error(), "not found") { |
| return nil, os.ErrNotExist |
| } |
| return nil, err |
| } |
| } |