1package assetfs 2 3import ( 4 "bytes" 5 "errors" 6 "io" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "path" 11 "path/filepath" 12 "strings" 13 "time" 14) 15 16var ( 17 defaultFileTimestamp = time.Now() 18) 19 20// FakeFile implements os.FileInfo interface for a given path and size 21type FakeFile struct { 22 // Path is the path of this file 23 Path string 24 // Dir marks of the path is a directory 25 Dir bool 26 // Len is the length of the fake file, zero if it is a directory 27 Len int64 28 // Timestamp is the ModTime of this file 29 Timestamp time.Time 30} 31 32func (f *FakeFile) Name() string { 33 _, name := filepath.Split(f.Path) 34 return name 35} 36 37func (f *FakeFile) Mode() os.FileMode { 38 mode := os.FileMode(0644) 39 if f.Dir { 40 return mode | os.ModeDir 41 } 42 return mode 43} 44 45func (f *FakeFile) ModTime() time.Time { 46 return f.Timestamp 47} 48 49func (f *FakeFile) Size() int64 { 50 return f.Len 51} 52 53func (f *FakeFile) IsDir() bool { 54 return f.Mode().IsDir() 55} 56 57func (f *FakeFile) Sys() interface{} { 58 return nil 59} 60 61// AssetFile implements http.File interface for a no-directory file with content 62type AssetFile struct { 63 *bytes.Reader 64 io.Closer 65 FakeFile 66} 67 68func NewAssetFile(name string, content []byte, timestamp time.Time) *AssetFile { 69 if timestamp.IsZero() { 70 timestamp = defaultFileTimestamp 71 } 72 return &AssetFile{ 73 bytes.NewReader(content), 74 ioutil.NopCloser(nil), 75 FakeFile{name, false, int64(len(content)), timestamp}} 76} 77 78func (f *AssetFile) Readdir(count int) ([]os.FileInfo, error) { 79 return nil, errors.New("not a directory") 80} 81 82func (f *AssetFile) Size() int64 { 83 return f.FakeFile.Size() 84} 85 86func (f *AssetFile) Stat() (os.FileInfo, error) { 87 return f, nil 88} 89 90// AssetDirectory implements http.File interface for a directory 91type AssetDirectory struct { 92 AssetFile 93 ChildrenRead int 94 Children []os.FileInfo 95} 96 97func NewAssetDirectory(name string, children []string, fs *AssetFS) *AssetDirectory { 98 fileinfos := make([]os.FileInfo, 0, len(children)) 99 for _, child := range children { 100 _, err := fs.AssetDir(filepath.Join(name, child)) 101 fileinfos = append(fileinfos, &FakeFile{child, err == nil, 0, time.Time{}}) 102 } 103 return &AssetDirectory{ 104 AssetFile{ 105 bytes.NewReader(nil), 106 ioutil.NopCloser(nil), 107 FakeFile{name, true, 0, time.Time{}}, 108 }, 109 0, 110 fileinfos} 111} 112 113func (f *AssetDirectory) Readdir(count int) ([]os.FileInfo, error) { 114 if count <= 0 { 115 return f.Children, nil 116 } 117 if f.ChildrenRead+count > len(f.Children) { 118 count = len(f.Children) - f.ChildrenRead 119 } 120 rv := f.Children[f.ChildrenRead : f.ChildrenRead+count] 121 f.ChildrenRead += count 122 return rv, nil 123} 124 125func (f *AssetDirectory) Stat() (os.FileInfo, error) { 126 return f, nil 127} 128 129// AssetFS implements http.FileSystem, allowing 130// embedded files to be served from net/http package. 131type AssetFS struct { 132 // Asset should return content of file in path if exists 133 Asset func(path string) ([]byte, error) 134 // AssetDir should return list of files in the path 135 AssetDir func(path string) ([]string, error) 136 // AssetInfo should return the info of file in path if exists 137 AssetInfo func(path string) (os.FileInfo, error) 138 // Prefix would be prepended to http requests 139 Prefix string 140} 141 142func (fs *AssetFS) Open(name string) (http.File, error) { 143 name = path.Join(fs.Prefix, name) 144 if len(name) > 0 && name[0] == '/' { 145 name = name[1:] 146 } 147 if b, err := fs.Asset(name); err == nil { 148 timestamp := defaultFileTimestamp 149 if fs.AssetInfo != nil { 150 if info, err := fs.AssetInfo(name); err == nil { 151 timestamp = info.ModTime() 152 } 153 } 154 return NewAssetFile(name, b, timestamp), nil 155 } 156 if children, err := fs.AssetDir(name); err == nil { 157 return NewAssetDirectory(name, children, fs), nil 158 } else { 159 // If the error is not found, return an error that will 160 // result in a 404 error. Otherwise the server returns 161 // a 500 error for files not found. 162 if strings.Contains(err.Error(), "not found") { 163 return nil, os.ErrNotExist 164 } 165 return nil, err 166 } 167} 168