github.com/simpleiot/simpleiot@v0.18.3/server/fs-decomp.go (about) 1 package server 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "io/fs" 7 "strings" 8 "time" 9 ) 10 11 // fsDecomp can be used to wrap a fs.FS. If a file is requested and not found, 12 // we look for a .gz version. If the .gz version is found, we decompress it 13 // and return the contents. This allows us to ship .gz compressed embedded files 14 // but still serve uncompressed files. fsDecomp will also translate root paths 15 // ("/" and "") to index.html, so we don't need to do that translation in the 16 // http handler 17 type fsDecomp struct { 18 fs fs.FS 19 rootFile string 20 } 21 22 func newFsDecomp(fs fs.FS, rootFile string) *fsDecomp { 23 return &fsDecomp{fs: fs, rootFile: rootFile} 24 } 25 26 func (fsd *fsDecomp) Open(name string) (fs.File, error) { 27 if name == "" || name == "/" { 28 name = fsd.rootFile 29 } 30 31 // look for file, if it does not exist, look for gz version 32 f, err := fsd.fs.Open(name) 33 if err != nil { 34 f, gzerr := fsd.fs.Open(name + ".gz") 35 if gzerr != nil { 36 // return original error 37 return f, err 38 } 39 40 // return fileGz version 41 return newFileGz(f) 42 } 43 44 return f, nil 45 } 46 47 // fileGz implements both fs.File and fs.FileInfo interfaces 48 type fileGz struct { 49 file fs.File 50 fileInfo fs.FileInfo 51 data bytes.Buffer 52 size int64 53 } 54 55 func newFileGz(file fs.File) (*fileGz, error) { 56 fileInfo, err := file.Stat() 57 if err != nil { 58 return nil, err 59 } 60 61 r, err := gzip.NewReader(file) 62 63 if err != nil { 64 return nil, err 65 } 66 67 var data bytes.Buffer 68 69 size, err := data.ReadFrom(r) 70 if err != nil { 71 return nil, err 72 } 73 74 return &fileGz{file: file, fileInfo: fileInfo, data: data, size: size}, nil 75 } 76 77 func (fgz *fileGz) Stat() (fs.FileInfo, error) { 78 return fgz, nil 79 } 80 81 func (fgz *fileGz) Read(data []byte) (int, error) { 82 return fgz.data.Read(data) 83 } 84 85 func (fgz *fileGz) Close() error { 86 return nil 87 } 88 89 func (fgz *fileGz) Name() string { 90 return strings.TrimSuffix(fgz.fileInfo.Name(), ".gz") 91 } 92 93 func (fgz *fileGz) Size() int64 { 94 return fgz.size 95 } 96 97 func (fgz *fileGz) Mode() fs.FileMode { 98 return fgz.fileInfo.Mode() 99 } 100 101 func (fgz *fileGz) ModTime() time.Time { 102 return fgz.fileInfo.ModTime() 103 } 104 105 func (fgz *fileGz) IsDir() bool { 106 return fgz.fileInfo.IsDir() 107 } 108 109 func (fgz *fileGz) Sys() any { 110 return fgz.fileInfo.Sys() 111 }