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