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  }