fs.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. // Copyright 2014 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Package contains an HTTP file system that works with zip contents.
  15. package fs
  16. import (
  17. "archive/zip"
  18. "bytes"
  19. "errors"
  20. "io"
  21. "io/ioutil"
  22. "net/http"
  23. "os"
  24. "strings"
  25. "sync"
  26. )
  27. var zipData string
  28. type statikFS struct {
  29. files map[string]*zip.File
  30. }
  31. // Registers zip contents data, later used to initialize
  32. // the statik file system.
  33. func Register(data string) {
  34. zipData = data
  35. }
  36. // Creates a new file system with the registered zip contents data.
  37. func New() (http.FileSystem, error) {
  38. if zipData == "" {
  39. return nil, errors.New("statik/fs: No zip data registered.")
  40. }
  41. zipReader, err := zip.NewReader(strings.NewReader(zipData), int64(len(zipData)))
  42. if err != nil {
  43. return nil, err
  44. }
  45. files := make(map[string]*zip.File)
  46. for _, file := range zipReader.File {
  47. files["/"+file.Name] = file
  48. }
  49. return &statikFS{files: files}, nil
  50. }
  51. // Opens a file, unzip the contents and initializes
  52. // readers. Returns os.ErrNotExists if file is not
  53. // found in the archive.
  54. func (fs *statikFS) Open(name string) (http.File, error) {
  55. name = strings.Replace(name, "//", "/", -1)
  56. f, ok := fs.files[name]
  57. // The file doesn't match, but maybe it's a directory,
  58. // thus we should look for index.html
  59. if !ok {
  60. indexName := strings.Replace(name+"/index.html", "//", "/", -1)
  61. f, ok = fs.files[indexName]
  62. if !ok {
  63. return nil, os.ErrNotExist
  64. }
  65. return newFile(f, true)
  66. }
  67. return newFile(f, false)
  68. }
  69. var nopCloser = ioutil.NopCloser(nil)
  70. func newFile(zf *zip.File, isDir bool) (*file, error) {
  71. rc, err := zf.Open()
  72. if err != nil {
  73. return nil, err
  74. }
  75. defer rc.Close()
  76. all, err := ioutil.ReadAll(rc)
  77. if err != nil {
  78. return nil, err
  79. }
  80. return &file{
  81. FileInfo: zf.FileInfo(),
  82. data: all,
  83. readerAt: bytes.NewReader(all),
  84. Closer: nopCloser,
  85. isDir: isDir,
  86. }, nil
  87. }
  88. // Represents an HTTP file, acts as a bridge between
  89. // zip.File and http.File.
  90. type file struct {
  91. os.FileInfo
  92. io.Closer
  93. data []byte // non-nil if regular file
  94. reader *io.SectionReader
  95. readerAt io.ReaderAt // over data
  96. isDir bool
  97. once sync.Once
  98. }
  99. func (f *file) newReader() {
  100. f.reader = io.NewSectionReader(f.readerAt, 0, f.FileInfo.Size())
  101. }
  102. // Reads bytes into p, returns the number of read bytes.
  103. func (f *file) Read(p []byte) (n int, err error) {
  104. f.once.Do(f.newReader)
  105. return f.reader.Read(p)
  106. }
  107. // Seeks to the offset.
  108. func (f *file) Seek(offset int64, whence int) (ret int64, err error) {
  109. f.once.Do(f.newReader)
  110. return f.reader.Seek(offset, whence)
  111. }
  112. // Stats the file.
  113. func (f *file) Stat() (os.FileInfo, error) {
  114. return f, nil
  115. }
  116. // IsDir returns true if the file location represents a directory.
  117. func (f *file) IsDir() bool {
  118. return f.isDir
  119. }
  120. // Returns an empty slice of files, directory
  121. // listing is disabled.
  122. func (f *file) Readdir(count int) ([]os.FileInfo, error) {
  123. // directory listing is disabled.
  124. return make([]os.FileInfo, 0), nil
  125. }