You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

168 lines
3.8 KiB

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