diff --git a/.gitignore b/.gitignore index e3a0811..891c371 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ .vscode +/_examples/test-* +/_examples/issue-* +/_examples/feature-* diff --git a/README.md b/README.md index 6387010..b9bc2a2 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,9 @@ Like [http.FileServer](https://pkg.go.dev/net/http?tab=doc#FileServer), plus the following features: - Embedded files through [go-bindata](https://github.com/go-bindata/go-bindata) +- In-memory file system with pre-compressed files **NEW** - HTTP/2 Push Targets on index requests -- On fly [fast](https://github.com/kataras/compress) [gzip](https://en.wikipedia.org/wiki/Gzip), [deflate](https://en.wikipedia.org/wiki/DEFLATE), [brotli](https://en.wikipedia.org/wiki/Brotli) and [snappy](https://en.wikipedia.org/wiki/Snappy_(compression)) compression based on the client's needs +- [Fast](https://github.com/kataras/compress) [gzip](https://en.wikipedia.org/wiki/Gzip), [deflate](https://en.wikipedia.org/wiki/DEFLATE), [brotli](https://en.wikipedia.org/wiki/Brotli) and [snappy](https://en.wikipedia.org/wiki/Snappy_(compression)) compression based on the client's needs - Content [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) and download speed limits - Customize directory listing by a [template](https://pkg.go.dev/html/template?tab=doc#Template) file or an `index.html` - Validator for each file per request, e.g. check permissions before serve a file @@ -32,7 +33,7 @@ The `httpfs` package is fully compatible with the standard library. Use `FileSer For system files you can use the [http.Dir](https://golang.org/pkg/net/http/#Dir): ```go - fileServer := httpfs.FileServer(http.Dir("./assets"), httpfs.DefaultOptions) +fileServer := httpfs.FileServer(http.Dir("./assets"), httpfs.DefaultOptions) ``` Where `httpfs.DefaultOptions` looks like this: @@ -56,10 +57,22 @@ Register the `FileServer` handler: http.Handle("/public/", fileServer) ``` -To serve files that are translated as Go code, located inside the executable program itself, use the `EmbeddedDir` instead of `http.Dir`. Based on the [generated](https://github.com/go-bindata/go-bindata) `Asset`, `AssetInfo` and `AssetNames` functions: +To serve files that are translated as Go code, inside the executable program itself, use the [generated](https://github.com/go-bindata/go-bindata) `AssetFile()` instead of `http.Dir`: ```go -fileSystem := httpfs.EmbeddedDir("./assets", Asset, AssetInfo, AssetNames) +fileServer := httpfs.FileServer(AssetFile(), httpfs.DefaultOptions) +``` + +To cache and compress files(gzip, deflate, snappy and brotli) before server ran, wrap any file system (embedded or physical) with the `MustAsset(http.FileSystem, CacheOptions)` function: + + +```go +var fileSystem http.FileSystem + +// fileSystem = http.Dir("./assets") +fileSystem = AssetFile() +fileSystem = httpfs.MustCache(fileSystem, httpfs.DefaultCacheOptions) + fileServer := httpfs.FileServer(fileSystem, httpfs.DefaultOptions) ``` diff --git a/_examples/basic/main.go b/_examples/basic/main.go index 59887e7..4c12ac9 100644 --- a/_examples/basic/main.go +++ b/_examples/basic/main.go @@ -60,7 +60,11 @@ func main() { // http.Handle("/", fileServer) // With a prefix, use the httpfs.StripPrefix: - fileServer := httpfs.FileServer(http.Dir("./assets"), opts) + fileSystem := http.Dir("./assets") + fileServer := httpfs.FileServer(fileSystem, opts) + // with (compressed) cache: + // fileServer := httpfs.FileServer(httpfs.MustCache(fileSystem, httpfs.DefaultCacheOptions), opts) + http.Handle("/public/", http.StripPrefix("/public/", fileServer)) // http.Handle("/", fileServer) diff --git a/_examples/embedded/bindata.go b/_examples/embedded/bindata.go index 48a4c0c..0a9696f 100644 --- a/_examples/embedded/bindata.go +++ b/_examples/embedded/bindata.go @@ -1,5 +1,6 @@ -// Code generated by go-bindata. DO NOT EDIT. -// sources: +// Code generated by go-bindata. (@generated) DO NOT EDIT. + +//Package main generated by go-bindata.// sources: // ../basic/assets/app2/app2app3/css/main.css // ../basic/assets/app2/app2app3/dirs/dir1/text.txt // ../basic/assets/app2/app2app3/dirs/dir2/text.txt @@ -20,6 +21,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "os" "path/filepath" "strings" @@ -29,7 +31,7 @@ import ( func bindataRead(data, name string) ([]byte, error) { gz, err := gzip.NewReader(strings.NewReader(data)) if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) + return nil, fmt.Errorf("read %q: %v", name, err) } var buf bytes.Buffer @@ -37,7 +39,7 @@ func bindataRead(data, name string) ([]byte, error) { clErr := gz.Close() if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) + return nil, fmt.Errorf("read %q: %v", name, err) } if clErr != nil { return nil, err @@ -58,261 +60,358 @@ type bindataFileInfo struct { modTime time.Time } +// Name return file name func (fi bindataFileInfo) Name() string { return fi.name } + +// Size return file size func (fi bindataFileInfo) Size() int64 { return fi.size } + +// Mode return file mode func (fi bindataFileInfo) Mode() os.FileMode { return fi.mode } + +// ModTime return file modify time func (fi bindataFileInfo) ModTime() time.Time { return fi.modTime } + +// IsDir return file whether a directory func (fi bindataFileInfo) IsDir() bool { - return false + return fi.mode&os.ModeDir != 0 } + +// Sys return file is sys mode func (fi bindataFileInfo) Sys() interface{} { return nil } -var _assetsApp2App2app3CssMainCss = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\xca\x4f\xa9\x54\xa8\xe6\xe5\x52\x50\x50\x50\x48\x4a\x4c\xce\x4e\x2f\xca\x2f\xcd\x4b\xd1\x4d\xce\xcf\xc9\x2f\xb2\x52\x48\xca\x29\x4d\xb5\xe6\xe5\xaa\x05\x04\x00\x00\xff\xff\x52\xd7\xbb\x8b\x26\x00\x00\x00" +type assetFile struct { + *bytes.Reader + name string + childInfos []os.FileInfo + childInfoOffset int +} + +type assetOperator struct{} + +// Open implement http.FileSystem interface +func (f *assetOperator) Open(name string) (http.File, error) { + var err error + if len(name) > 0 && name[0] == '/' { + name = name[1:] + } + content, err := Asset(name) + if err == nil { + return &assetFile{name: name, Reader: bytes.NewReader(content)}, nil + } + children, err := AssetDir(name) + if err == nil { + childInfos := make([]os.FileInfo, 0, len(children)) + for _, child := range children { + childPath := filepath.Join(name, child) + info, errInfo := AssetInfo(filepath.Join(name, child)) + if errInfo == nil { + childInfos = append(childInfos, info) + } else { + childInfos = append(childInfos, newDirFileInfo(childPath)) + } + } + return &assetFile{name: name, childInfos: childInfos}, nil + } else { + // If the error is not found, return an error that will + // result in a 404 error. Otherwise the server returns + // a 500 error for files not found. + if strings.Contains(err.Error(), "not found") { + return nil, os.ErrNotExist + } + return nil, err + } +} -func assetsApp2App2app3CssMainCssBytes() ([]byte, error) { +// Close no need do anything +func (f *assetFile) Close() error { + return nil +} + +// Readdir read dir's children file info +func (f *assetFile) Readdir(count int) ([]os.FileInfo, error) { + if len(f.childInfos) == 0 { + return nil, os.ErrNotExist + } + if count <= 0 { + return f.childInfos, nil + } + if f.childInfoOffset+count > len(f.childInfos) { + count = len(f.childInfos) - f.childInfoOffset + } + offset := f.childInfoOffset + f.childInfoOffset += count + return f.childInfos[offset : offset+count], nil +} + +// Stat read file info from asset item +func (f *assetFile) Stat() (os.FileInfo, error) { + if len(f.childInfos) != 0 { + return newDirFileInfo(f.name), nil + } + return AssetInfo(f.name) +} + +// newDirFileInfo return default dir file info +func newDirFileInfo(name string) os.FileInfo { + return &bindataFileInfo{ + name: name, + size: 0, + mode: os.FileMode(2147484068), // equal os.FileMode(0644)|os.ModeDir + modTime: time.Time{}} +} + +// AssetFile return a http.FileSystem instance that data backend by asset +func AssetFile() http.FileSystem { + return &assetOperator{} +} + +var _app2App2app3CssMainCss = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\xca\x4f\xa9\x54\xa8\xe6\xe5\x52\x50\x50\x50\x48\x4a\x4c\xce\x4e\x2f\xca\x2f\xcd\x4b\xd1\x4d\xce\xcf\xc9\x2f\xb2\x52\x48\xca\x29\x4d\xb5\xe6\xe5\xaa\x05\x04\x00\x00\xff\xff\x52\xd7\xbb\x8b\x26\x00\x00\x00" + +func app2App2app3CssMainCssBytes() ([]byte, error) { return bindataRead( - _assetsApp2App2app3CssMainCss, - "assets/app2/app2app3/css/main.css", + _app2App2app3CssMainCss, + "app2/app2app3/css/main.css", ) } -func assetsApp2App2app3CssMainCss() (*asset, error) { - bytes, err := assetsApp2App2app3CssMainCssBytes() +func app2App2app3CssMainCss() (*asset, error) { + bytes, err := app2App2app3CssMainCssBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "assets/app2/app2app3/css/main.css", size: 38, mode: os.FileMode(438), modTime: time.Unix(1595043712, 0)} + info := bindataFileInfo{name: "app2/app2app3/css/main.css", size: 38, mode: os.FileMode(438), modTime: time.Unix(1595043712, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsApp2App2app3DirsDir1TextTxt = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\x2c\x28\x30\xd2\x07\x11\x89\x05\x05\xc6\xfa\x29\x99\x45\xc5\x20\xc2\x50\xbf\x24\xb5\xa2\x44\xaf\xa4\xa2\x04\x10\x00\x00\xff\xff\x87\xaf\x9d\x00\x20\x00\x00\x00" +var _app2App2app3DirsDir1TextTxt = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\x2c\x28\x30\xd2\x07\x11\x89\x05\x05\xc6\xfa\x29\x99\x45\xc5\x20\xc2\x50\xbf\x24\xb5\xa2\x44\xaf\xa4\xa2\x04\x10\x00\x00\xff\xff\x87\xaf\x9d\x00\x20\x00\x00\x00" -func assetsApp2App2app3DirsDir1TextTxtBytes() ([]byte, error) { +func app2App2app3DirsDir1TextTxtBytes() ([]byte, error) { return bindataRead( - _assetsApp2App2app3DirsDir1TextTxt, - "assets/app2/app2app3/dirs/dir1/text.txt", + _app2App2app3DirsDir1TextTxt, + "app2/app2app3/dirs/dir1/text.txt", ) } -func assetsApp2App2app3DirsDir1TextTxt() (*asset, error) { - bytes, err := assetsApp2App2app3DirsDir1TextTxtBytes() +func app2App2app3DirsDir1TextTxt() (*asset, error) { + bytes, err := app2App2app3DirsDir1TextTxtBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "assets/app2/app2app3/dirs/dir1/text.txt", size: 32, mode: os.FileMode(438), modTime: time.Unix(1594843207, 0)} + info := bindataFileInfo{name: "app2/app2app3/dirs/dir1/text.txt", size: 32, mode: os.FileMode(438), modTime: time.Unix(1594843207, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsApp2App2app3DirsDir2TextTxt = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\x2c\x28\x30\xd2\x07\x11\x89\x05\x05\xc6\xfa\x29\x99\x45\xc5\x20\xc2\x48\xbf\x24\xb5\xa2\x44\xaf\xa4\xa2\x04\x10\x00\x00\xff\xff\x84\x14\xaa\xeb\x20\x00\x00\x00" +var _app2App2app3DirsDir2TextTxt = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\x2c\x28\x30\xd2\x07\x11\x89\x05\x05\xc6\xfa\x29\x99\x45\xc5\x20\xc2\x48\xbf\x24\xb5\xa2\x44\xaf\xa4\xa2\x04\x10\x00\x00\xff\xff\x84\x14\xaa\xeb\x20\x00\x00\x00" -func assetsApp2App2app3DirsDir2TextTxtBytes() ([]byte, error) { +func app2App2app3DirsDir2TextTxtBytes() ([]byte, error) { return bindataRead( - _assetsApp2App2app3DirsDir2TextTxt, - "assets/app2/app2app3/dirs/dir2/text.txt", + _app2App2app3DirsDir2TextTxt, + "app2/app2app3/dirs/dir2/text.txt", ) } -func assetsApp2App2app3DirsDir2TextTxt() (*asset, error) { - bytes, err := assetsApp2App2app3DirsDir2TextTxtBytes() +func app2App2app3DirsDir2TextTxt() (*asset, error) { + bytes, err := app2App2app3DirsDir2TextTxtBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "assets/app2/app2app3/dirs/dir2/text.txt", size: 32, mode: os.FileMode(438), modTime: time.Unix(1594843207, 0)} + info := bindataFileInfo{name: "app2/app2app3/dirs/dir2/text.txt", size: 32, mode: os.FileMode(438), modTime: time.Unix(1594843207, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsApp2App2app3DirsTextTxt = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\x2c\x28\x30\xd2\x07\x11\x89\x05\x05\xc6\xfa\x29\x99\x45\xc5\xfa\x25\xa9\x15\x25\x7a\x25\x15\x25\x80\x00\x00\x00\xff\xff\x64\xfe\x96\xd6\x1b\x00\x00\x00" +var _app2App2app3DirsTextTxt = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\x2c\x28\x30\xd2\x07\x11\x89\x05\x05\xc6\xfa\x29\x99\x45\xc5\xfa\x25\xa9\x15\x25\x7a\x25\x15\x25\x80\x00\x00\x00\xff\xff\x64\xfe\x96\xd6\x1b\x00\x00\x00" -func assetsApp2App2app3DirsTextTxtBytes() ([]byte, error) { +func app2App2app3DirsTextTxtBytes() ([]byte, error) { return bindataRead( - _assetsApp2App2app3DirsTextTxt, - "assets/app2/app2app3/dirs/text.txt", + _app2App2app3DirsTextTxt, + "app2/app2app3/dirs/text.txt", ) } -func assetsApp2App2app3DirsTextTxt() (*asset, error) { - bytes, err := assetsApp2App2app3DirsTextTxtBytes() +func app2App2app3DirsTextTxt() (*asset, error) { + bytes, err := app2App2app3DirsTextTxtBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "assets/app2/app2app3/dirs/text.txt", size: 27, mode: os.FileMode(438), modTime: time.Unix(1594843207, 0)} + info := bindataFileInfo{name: "app2/app2app3/dirs/text.txt", size: 27, mode: os.FileMode(438), modTime: time.Unix(1594843207, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsApp2App2app3IndexHtml = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\x90\x3f\x4f\xc3\x40\x0c\xc5\xf7\x48\xf9\x0e\xc6\x33\xcd\x91\x76\x61\xb8\x8b\x54\xf1\x47\x6c\x30\x94\x81\xf1\xb8\x18\xce\xc2\xb9\x9c\x72\x56\x4b\xbf\x3d\x4a\x68\x90\x98\xac\x67\xbf\xdf\x93\xfc\xec\xd5\xfd\xf3\xdd\xe1\xed\xe5\x01\xa2\x0e\xd2\xd5\x95\x9d\x27\x88\x4f\x9f\x0e\x29\x61\x57\x57\xf3\x8e\x7c\xdf\xd5\x15\x00\x80\x1d\x48\x3d\x84\xe8\xa7\x42\xea\xf0\xf5\xf0\xb8\xb9\xc5\x7f\xb7\xe4\x07\x72\x78\x64\x3a\xe5\x71\x52\x84\x30\x26\xa5\xa4\x0e\x4f\xdc\x6b\x74\x3d\x1d\x39\xd0\x66\x11\xd7\xc0\x89\x95\xbd\x6c\x4a\xf0\x42\xae\x6d\x6e\xfe\xb2\x84\xd3\x17\x4c\x24\x0e\x8b\x9e\x85\x4a\x24\x52\x84\x38\xd1\x87\xc3\xc6\xf8\x9c\xb7\x3e\xe7\x9d\x09\xa5\x98\xc1\x73\x6a\x42\x29\x08\x66\xa5\x95\x55\xa8\xdb\xe7\xbc\xdd\xe7\xbc\xb3\xe6\x57\xd7\x95\x35\x97\x5f\xea\xca\xbe\x8f\xfd\x79\xf5\xc7\xb6\x7b\x22\x91\x11\x56\x04\x38\xf5\xf4\x6d\x4d\x6c\x17\xea\xe2\x5d\x02\xe6\xa2\x7e\x02\x00\x00\xff\xff\xde\xa6\x1a\xd7\x38\x01\x00\x00" +var _app2App2app3IndexHtml = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\x90\x3f\x4f\xc3\x40\x0c\xc5\xf7\x48\xf9\x0e\xc6\x33\xcd\x91\x76\x61\xb8\x8b\x54\xf1\x47\x6c\x30\x94\x81\xf1\xb8\x18\xce\xc2\xb9\x9c\x72\x56\x4b\xbf\x3d\x4a\x68\x90\x98\xac\x67\xbf\xdf\x93\xfc\xec\xd5\xfd\xf3\xdd\xe1\xed\xe5\x01\xa2\x0e\xd2\xd5\x95\x9d\x27\x88\x4f\x9f\x0e\x29\x61\x57\x57\xf3\x8e\x7c\xdf\xd5\x15\x00\x80\x1d\x48\x3d\x84\xe8\xa7\x42\xea\xf0\xf5\xf0\xb8\xb9\xc5\x7f\xb7\xe4\x07\x72\x78\x64\x3a\xe5\x71\x52\x84\x30\x26\xa5\xa4\x0e\x4f\xdc\x6b\x74\x3d\x1d\x39\xd0\x66\x11\xd7\xc0\x89\x95\xbd\x6c\x4a\xf0\x42\xae\x6d\x6e\xfe\xb2\x84\xd3\x17\x4c\x24\x0e\x8b\x9e\x85\x4a\x24\x52\x84\x38\xd1\x87\xc3\xc6\xf8\x9c\xb7\x3e\xe7\x9d\x09\xa5\x98\xc1\x73\x6a\x42\x29\x08\x66\xa5\x95\x55\xa8\xdb\xe7\xbc\xdd\xe7\xbc\xb3\xe6\x57\xd7\x95\x35\x97\x5f\xea\xca\xbe\x8f\xfd\x79\xf5\xc7\xb6\x7b\x22\x91\x11\x56\x04\x38\xf5\xf4\x6d\x4d\x6c\x17\xea\xe2\x5d\x02\xe6\xa2\x7e\x02\x00\x00\xff\xff\xde\xa6\x1a\xd7\x38\x01\x00\x00" -func assetsApp2App2app3IndexHtmlBytes() ([]byte, error) { +func app2App2app3IndexHtmlBytes() ([]byte, error) { return bindataRead( - _assetsApp2App2app3IndexHtml, - "assets/app2/app2app3/index.html", + _app2App2app3IndexHtml, + "app2/app2app3/index.html", ) } -func assetsApp2App2app3IndexHtml() (*asset, error) { - bytes, err := assetsApp2App2app3IndexHtmlBytes() +func app2App2app3IndexHtml() (*asset, error) { + bytes, err := app2App2app3IndexHtmlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "assets/app2/app2app3/index.html", size: 312, mode: os.FileMode(438), modTime: time.Unix(1595055134, 0)} + info := bindataFileInfo{name: "app2/app2app3/index.html", size: 312, mode: os.FileMode(438), modTime: time.Unix(1595055134, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsApp2IndexHtml = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb2\xc9\x30\xb4\xf3\x48\xcd\xc9\xc9\x57\x70\x2c\x28\x30\x52\xc8\xcc\x4b\x49\xad\xb0\xd1\xcf\x30\xb4\x03\x04\x00\x00\xff\xff\x75\x17\xab\xfa\x19\x00\x00\x00" +var _app2IndexHtml = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb2\xc9\x30\xb4\xf3\x48\xcd\xc9\xc9\x57\x70\x2c\x28\x30\x52\xc8\xcc\x4b\x49\xad\xb0\xd1\xcf\x30\xb4\x03\x04\x00\x00\xff\xff\x75\x17\xab\xfa\x19\x00\x00\x00" -func assetsApp2IndexHtmlBytes() ([]byte, error) { +func app2IndexHtmlBytes() ([]byte, error) { return bindataRead( - _assetsApp2IndexHtml, - "assets/app2/index.html", + _app2IndexHtml, + "app2/index.html", ) } -func assetsApp2IndexHtml() (*asset, error) { - bytes, err := assetsApp2IndexHtmlBytes() +func app2IndexHtml() (*asset, error) { + bytes, err := app2IndexHtmlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "assets/app2/index.html", size: 25, mode: os.FileMode(438), modTime: time.Unix(1565946440, 0)} + info := bindataFileInfo{name: "app2/index.html", size: 25, mode: os.FileMode(438), modTime: time.Unix(1565946440, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsApp2MydirTextTxt = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xca\x2a\x2d\x2e\x51\x48\x54\x28\x49\xad\x28\xd1\x03\x04\x00\x00\xff\xff\x2f\xf9\x22\x98\x0c\x00\x00\x00" +var _app2MydirTextTxt = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xca\x2a\x2d\x2e\x51\x48\x54\x28\x49\xad\x28\xd1\x03\x04\x00\x00\xff\xff\x2f\xf9\x22\x98\x0c\x00\x00\x00" -func assetsApp2MydirTextTxtBytes() ([]byte, error) { +func app2MydirTextTxtBytes() ([]byte, error) { return bindataRead( - _assetsApp2MydirTextTxt, - "assets/app2/mydir/text.txt", + _app2MydirTextTxt, + "app2/mydir/text.txt", ) } -func assetsApp2MydirTextTxt() (*asset, error) { - bytes, err := assetsApp2MydirTextTxtBytes() +func app2MydirTextTxt() (*asset, error) { + bytes, err := app2MydirTextTxtBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "assets/app2/mydir/text.txt", size: 12, mode: os.FileMode(438), modTime: time.Unix(1594787248, 0)} + info := bindataFileInfo{name: "app2/mydir/text.txt", size: 12, mode: os.FileMode(438), modTime: time.Unix(1594787248, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsCssMainCss = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\xca\x4f\xa9\x54\xa8\xe6\xe5\x52\x50\x50\x50\x48\x4a\x4c\xce\x4e\x2f\xca\x2f\xcd\x4b\xd1\x4d\xce\xcf\xc9\x2f\xb2\x52\x48\xca\x49\x4c\xce\xb6\xe6\xe5\xaa\xe5\xe5\x02\x04\x00\x00\xff\xff\x03\x25\x9c\x89\x29\x00\x00\x00" +var _cssMainCss = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\xca\x4f\xa9\x54\xa8\xe6\xe5\x52\x50\x50\x50\x48\x4a\x4c\xce\x4e\x2f\xca\x2f\xcd\x4b\xd1\x4d\xce\xcf\xc9\x2f\xb2\x52\x48\xca\x49\x4c\xce\xb6\xe6\xe5\xaa\xe5\xe5\x02\x04\x00\x00\xff\xff\x03\x25\x9c\x89\x29\x00\x00\x00" -func assetsCssMainCssBytes() ([]byte, error) { +func cssMainCssBytes() ([]byte, error) { return bindataRead( - _assetsCssMainCss, - "assets/css/main.css", + _cssMainCss, + "css/main.css", ) } -func assetsCssMainCss() (*asset, error) { - bytes, err := assetsCssMainCssBytes() +func cssMainCss() (*asset, error) { + bytes, err := cssMainCssBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "assets/css/main.css", size: 41, mode: os.FileMode(438), modTime: time.Unix(1565946440, 0)} + info := bindataFileInfo{name: "css/main.css", size: 41, mode: os.FileMode(438), modTime: time.Unix(1565946440, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsFaviconIco = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5a\x0b\x70\x55\xc7\x79\xde\x7b\xce\x45\x12\xb2\x90\xc4\xc3\xe6\x61\x3b\x90\xf8\x31\xc4\x19\x6c\x32\xe3\xc4\x34\xe3\xc6\x34\x6d\xed\xd4\x69\x62\x32\x49\x9a\xa6\x75\xea\x36\x33\xee\xd8\x9e\xb4\x75\xdc\x7a\xa6\xc5\x31\x02\xa6\xd3\x84\x47\x28\x6f\x3b\xc6\x3c\xcc\xeb\x9e\xbd\x08\x21\x04\x92\x28\x12\x0e\x08\x5b\x3c\xec\x02\x92\x78\x08\x21\x09\x41\x25\x24\x10\xe8\x71\xb5\xe7\xdc\xd7\xb9\xf7\xef\xfc\xff\x9e\x73\x74\x25\xdd\x97\x24\x82\x33\x1e\xce\xcc\x3f\x7b\xee\x9e\xdd\xff\xff\xf6\xdf\x7f\xff\xfd\xf7\xdf\xcb\x98\x8b\xa9\x2c\x3f\x1f\xcb\x19\xec\x15\x37\x63\x5f\x67\x8c\xcd\x98\x21\x7f\x6b\xf9\x8c\x6d\x72\x33\x36\x7b\xb6\xf5\xfb\x11\xc6\x9e\xbd\x97\xb1\x99\x8c\xb1\x7c\x6c\xc7\x64\x3d\x3d\x6e\x76\xdb\x1f\xe1\x75\xab\x42\x53\x7e\x22\x3c\x6c\x91\xf0\xb0\x02\x22\xae\x14\x08\x8f\xab\x40\xec\x64\x92\xf0\x5d\x53\xfe\x59\xec\x64\x5f\x15\x1e\xa6\x08\x4f\x7f\x7f\xdf\x7a\x96\xa1\x97\x3c\xb8\x3f\x78\xfa\x0d\x08\x9e\x5d\x08\xc1\xda\x5f\x82\x51\xf6\x65\x30\xca\x67\x41\xf0\xcc\xbf\x11\x19\x07\x1e\x07\xbd\x78\x2a\x18\x65\x8f\xb5\x0b\x4d\x7d\xad\x6f\x07\x73\x0b\xcd\x45\xfd\xbb\x17\xb3\x0c\xa3\x62\xce\xfe\xa8\xd1\x0a\xf8\x98\x6d\x25\x10\x38\xfe\x53\x88\xdc\x3a\x01\x66\x6b\x11\x11\xbe\x07\x8e\xfd\x2d\x84\x2e\x2c\x05\xff\xd1\x79\x3d\xc2\xc3\x7e\x20\x78\x06\x13\x5c\x89\xe9\xdf\x06\xd1\x50\x2f\x04\x4e\xfc\x0c\xcc\x6b\xa5\x60\x1c\xfd\x21\x74\x6c\x1e\x47\x84\xef\x58\x17\xf8\xe4\x65\x30\x6f\x54\x81\x71\xe0\x89\xea\xbe\x2d\x6c\xd2\x80\xfe\xfe\x76\x30\x6f\x1c\x81\x60\xed\x7c\x08\x9e\x5d\x0c\x2d\xeb\xc7\x41\xed\xd2\x29\x50\xbb\x74\x32\xbd\x63\x1d\x7e\xc3\x36\xa1\x73\xff\x19\x14\x1e\x36\x4f\x78\xc7\xc4\xf4\xef\x80\x70\xd3\xfb\x10\x6e\x5c\x0f\xdd\x65\x73\xe1\xdc\xf2\x89\xd0\xb1\x25\x0f\x6e\x7c\x90\x07\xe7\x7f\x33\x81\xea\xf0\x5b\xb8\x79\x13\x61\xd0\x8b\xa7\x2c\x09\x7c\xfc\x57\xac\x7b\x11\xf6\x7f\x6a\x5f\xd4\xb8\x06\xa1\xfa\x15\x10\x6e\x7a\x0f\x3a\xb4\xc7\xa0\x61\x55\x3e\x04\x8f\xfc\x39\x84\x3e\xfa\x4b\x68\x5c\x93\x4f\x75\xf8\x2d\x74\x71\x05\x44\x45\x13\xea\x54\xeb\x7c\x85\xa9\x48\xf8\x1e\x15\xcd\xf4\xcd\xee\x7f\x71\x65\x3e\x04\x3e\x7c\x86\x78\x5c\x5a\x1d\xd3\xbf\x7e\x05\xa0\x2c\x94\x89\xb2\x03\xd5\x3f\x66\x88\x05\x31\x21\x36\x1b\xff\xd9\xe5\x13\xe1\xda\xc6\x5c\x68\xdf\x94\x4b\x63\x71\xf0\x37\xbd\x0f\x38\x56\x1c\x33\x8e\x1d\x75\x80\xba\x40\x9d\x0c\xd6\x5f\xcd\x92\x29\x44\x83\xf5\x87\xba\x76\xfa\x73\x85\xe1\x5c\x18\x07\x9e\x38\x86\x18\x68\x8e\xac\xf9\xbb\xbe\x39\x87\xc8\x99\xbf\x13\x3f\xa3\x39\xc6\xb9\x8e\xed\x4f\xb6\xe0\x61\x3f\x40\xdb\x40\x1b\x41\x5b\x19\x62\x3f\xc7\x7f\x4a\xb6\x85\x0f\xda\x9a\xdd\x9f\xd6\x80\xe6\x62\xd2\x26\xd5\xd7\xd0\x46\xc9\x56\x0f\x3c\xde\x6f\xbf\xe5\xb3\xc8\xa6\xd1\xb6\xc9\xc6\x4f\xbf\x01\x68\xf3\x68\xfb\xce\x3a\xf2\x30\x26\x76\x32\x85\xd6\x08\xae\x95\xc1\xeb\x87\xd6\x14\xb3\x69\x11\xad\x39\xaf\x5b\x1d\xed\xfa\x05\x60\x8c\x65\x32\xc6\x54\x8b\x5c\x31\x64\x3d\x0b\x63\xe8\xb0\x45\x2d\x56\xdf\x99\x96\x8f\x99\x1b\xeb\x67\xf2\x47\x8b\xea\xf3\xf9\x08\xae\x22\xe5\x09\xae\x4e\x17\x5c\xfd\xc2\x28\x69\x8a\xe0\xca\x58\xb4\x3d\xdb\x17\xa6\x29\xff\x5f\x04\x57\x5b\x85\xe6\xba\x92\x90\xb8\x1a\x43\x4a\x4c\xbd\x62\xd7\xb7\x08\xae\xd6\x0b\xae\xfe\x8f\xe0\xea\x9b\x38\x1e\x1f\x1f\x4b\xfe\x34\xb5\x7c\xa5\x40\xec\xca\x02\xbd\x64\x3a\xe8\xfb\xbe\x38\x94\x4a\xa6\x83\xf0\x66\x82\xd0\x14\x10\x9a\x0b\x44\xe1\x38\x5c\x2b\x44\xf8\x4e\x75\x5c\x01\xe1\x75\x03\xf2\x11\x9a\x2b\x2a\xb8\x52\x2b\xb8\x3a\x4f\x68\x8a\x92\x0c\x03\xc9\xf7\xb0\x02\xa3\xfc\x2b\x10\xe9\xae\x85\xa8\x7e\x15\xa2\xfa\x95\x18\xba\x0a\xa1\xb3\x8b\x49\xbe\xbe\xe7\x5e\x08\xfe\xef\x3f\x81\xd9\x51\x09\x91\xbe\x4b\x44\xf8\x8e\x75\xf8\x4d\x70\x37\x04\x3e\xfe\x11\xf9\x14\xbd\x68\x3c\xe2\xe8\x14\x5c\x7d\x49\x78\x99\x2b\x11\x06\x47\xfe\x81\xd9\x10\x0d\xde\x82\xc1\x8f\x79\xe3\x30\xe8\x7b\xa7\x81\x51\xfa\x28\x98\xd7\xca\x00\xa2\xa6\xfc\x80\x65\xcc\x3b\x7e\xc3\x36\xfa\xde\x07\x08\x93\xd9\x5a\x0c\xfa\xfe\x87\x41\x68\xac\x43\x70\xf5\x59\x92\xe3\x1d\xea\x1a\x92\xc9\x8f\x06\x3a\xc1\xff\xe1\x5c\xda\x67\x91\x27\x3e\x91\xde\x0b\xe4\xaf\xfc\x47\x5f\x20\xc2\x77\xac\x23\xac\x1d\x95\xd4\xd6\xff\xe1\x9f\x10\x2f\xfa\x5d\xf2\x05\xc4\x70\x4c\x70\xf5\x7e\x94\x35\x1c\xf9\xa1\xc6\x77\x68\x3e\x43\x17\x57\x4a\xfe\xe8\xc3\xcb\xbe\x8c\xfc\xa0\xcf\x23\x09\xdf\xb1\x0e\xbf\x51\x9f\x8b\x2b\x65\x9f\xc6\x77\xe8\x37\xee\x4f\x62\xd7\x58\xb4\x8f\x05\x3e\x9e\x35\xc4\x1e\x13\xc9\x8f\x06\xbb\xc0\xa8\xfc\x23\xf0\x1f\xfa\x63\x88\x86\xba\x21\x72\xeb\x24\x18\xfb\x1f\x82\xde\x1d\x2e\x68\xdd\x30\x0e\x1a\x56\x4f\x24\xc2\x77\xac\xc3\x6f\xd8\x06\xdb\x62\x1f\xec\x8b\xef\x10\xd6\xc1\xff\xd1\xf7\x11\x67\x83\xe0\xea\x97\x06\xeb\x20\x91\x7c\xd2\xdd\xee\x3c\x08\x37\x6f\x04\x30\x0d\x08\x7c\x34\x8f\xe4\x34\xae\x99\x00\xa7\x7f\x3d\x0d\x4e\xfd\xea\x7e\x22\x7c\xc7\x3a\xfc\x86\x6d\xb0\x6d\xb8\x69\x23\xf5\xb5\xe7\xcc\x6c\x3f\x00\x62\x77\x6e\x54\x68\xae\x57\xad\xf5\x96\x52\x7e\xb0\x6e\x01\xe8\xfb\x66\xd0\x1a\x30\xaf\x95\x83\x28\xcc\x81\x2b\xef\xe6\x92\x3c\x24\x8a\x63\x96\x4d\x71\x7e\xe3\x37\x6c\x83\x6d\xb1\x0f\xf6\x0d\xd6\x15\x48\x5d\x86\x7c\x64\x47\xc2\xc3\xf6\x0a\xae\x66\xc6\xea\x20\xae\xfc\x48\x08\xfc\x55\xdf\x05\x7f\xd5\x77\x00\x22\x41\x08\x7e\xfa\x2a\x74\x6d\x53\xa1\x6e\xf9\x64\x92\x75\x69\xcd\x44\xb8\xb9\x35\x13\x6e\x6d\xcb\x80\xe6\x75\xe3\xa9\x0e\xbf\x61\x1b\x6c\x8b\x7d\xb0\xaf\xbf\xea\x7b\xc4\x8b\xec\xe2\xec\x22\x9c\x83\x66\xc1\xd5\x19\xa9\xe4\xd3\xdc\x1f\x98\x0d\xc1\x33\x6f\x42\x34\xec\x03\x7f\xe5\x1c\x68\xdb\x30\x96\xe4\x9c\xfb\xcd\x7d\xd0\xbd\x63\x0c\xe8\x85\x63\x41\x2f\xbc\x07\x7a\x3d\x6e\xa8\x5f\x39\x89\xbe\x61\x1b\x6c\x8b\x7d\xb0\xaf\xe4\xd9\x65\xd9\x6e\x19\x88\x5d\xd9\xba\xd0\x5c\x73\x53\xca\xd7\xff\x0f\xf4\xfd\x0f\x41\xa8\x61\x35\xf9\x1f\x7d\xdf\x74\x68\x5e\x97\x47\xf3\xdd\xf2\x4e\x1e\xc9\x0d\x9f\xfc\x3b\x30\x4f\xbd\x02\xc6\x9e\x09\xd0\xba\x21\x07\x4e\xfd\x6a\x1a\xb5\xc1\xb6\xe4\xb3\x1a\x56\x13\x0f\xe4\x25\xd7\xed\x39\x5c\x9b\x51\xa1\xb1\x17\xe3\xc8\x7f\x0b\x63\x13\x5c\xef\xd4\xb6\xaf\x91\x7c\x6b\xf8\xf2\x16\x5a\xdb\x62\xcf\x64\xb8\xb4\x7a\x3c\xc9\x68\xdb\x90\x0d\xfe\xb2\x87\x01\x2e\x2d\x07\x68\x5a\x05\x81\xca\x27\xe1\xfa\xe6\x4c\x39\x2f\xab\xc7\x53\xdb\x88\xaf\x9e\xfa\x22\x0f\xe4\xe5\xc4\x47\xa5\x8f\xa2\x0d\xfc\x22\x8e\xfc\x5f\xe0\x37\x3b\x5e\x4f\x2e\xff\x1e\xf0\x97\x7e\x11\xa2\x0d\x4b\x00\x9a\x56\x42\xe0\xe0\x13\x70\x7d\x53\x56\x6a\xf9\x81\x4e\x8a\xbf\x70\xac\x43\xe4\x6b\xec\x45\xd4\x0d\xea\x28\xbe\xfe\x67\x38\xfa\x6f\x5e\x97\x2f\xfd\xcb\xd1\xe7\x21\x74\xec\x87\xa0\xef\xce\x81\xab\xbf\x1d\x67\x7d\xcb\xb3\xd6\xcc\x50\xfd\xe3\xdc\xe2\x1c\xcb\xb3\xd0\x60\xf9\xae\xb9\x68\x1b\xe4\xdf\x53\xd8\x1f\xda\x39\xda\xbe\xee\x55\x41\xf7\xba\xc9\x16\xcf\xaf\xb8\x2f\xa5\xfd\x25\x95\x8f\x6b\x42\x63\xcd\xb8\xcf\x25\x5c\x7f\x5b\xe5\xfa\xc3\x39\xb8\xf0\xdf\xf7\x42\xfb\xc6\x7b\xa0\x63\x53\x36\x5c\x5c\x35\x29\xad\xf5\x97\x42\x7e\x26\xfa\x06\xda\x37\xc2\xbe\xb4\xfc\x0f\xd2\x99\x25\xd3\xd2\xf2\x3f\xc9\xe5\x2b\xf6\x1c\xbc\x86\x3e\x12\x7d\xe5\x50\xff\xeb\x8f\xe3\x7f\x25\x0d\xf5\xbf\xfe\x21\xfe\x37\x99\xfc\x18\x1d\x7c\x09\xf7\x08\xdc\x2b\xc0\xd4\x69\xef\x30\x2a\x46\xb9\xff\x58\x73\x9f\x5a\xbe\xc2\x7c\x1a\xed\x8d\x0b\x70\xaf\xc4\x3d\x53\xee\xbf\xef\x8e\x7a\xff\x4d\x47\x7e\x8c\x0e\xee\xc7\x58\x01\x63\x06\xd4\x1d\xf6\x41\x9b\x18\x51\xfc\x61\xf9\xb2\xb4\xe5\x7b\x55\x1b\xc3\xb3\x18\x33\x61\xec\x84\x31\x14\xf1\xdc\xfb\x40\x5a\xf1\x97\x4e\xf1\xd7\x34\x8a\xd9\x06\x3f\xa9\xe4\x3b\xb6\x48\xb1\xa2\xfa\x12\xc6\x8e\x18\x43\x62\x2c\x89\x31\x25\xc6\x96\x29\xe3\x4f\x6f\x26\xc5\xaa\xf1\x62\x58\x8c\x6d\x31\xc6\x4d\x26\xbf\x7f\x3d\x60\xcc\xac\xce\xa3\x18\x1a\x63\x69\x8c\xa9\xb9\x5b\xc6\xd8\x14\x7f\xe7\xc4\xc4\xdf\x39\xb2\x0e\x63\x73\x8c\x91\x93\xc5\xf0\xc4\x47\x49\x2a\x9f\x30\x68\x0a\xf3\x15\x65\x31\x6b\xaf\x7e\xd3\x3a\x53\xd4\x5b\x67\x8c\x44\xe7\x0f\x49\xc9\xcf\x30\xad\xd6\x59\x27\xa9\xfc\x58\x5d\xf4\x69\x14\x2f\x8d\x95\x67\xab\x51\x9f\xcf\xa6\x5b\x67\xbd\xb4\xe4\x7f\xde\x1f\x7b\x6d\x1c\x66\x2a\x1c\x66\x0c\xe9\x99\xc3\x8c\x4d\xb7\x28\xcf\xa2\x4c\x8b\xd4\x74\xa8\xc5\xa2\x1e\x8b\x02\x16\x99\x8c\xa9\xc0\x48\x90\x6a\xcb\x9d\xc9\x18\x9b\xcd\x18\xfb\xfb\xd8\x3c\xc5\xc3\x9f\xb5\x56\xee\x3e\x77\x9f\x3f\xcc\xc7\xda\x1f\x1f\x16\x5c\x7d\x5b\x70\x75\xa1\xe0\x6a\xc1\xef\x89\x6c\xde\xff\x2a\xb8\xfa\x37\x82\x2b\xb3\x05\x57\x72\x7a\x35\xc6\x0c\x2d\x79\x3e\x29\x0d\xfc\xdf\x16\x5c\x0d\x0a\xae\xc2\x1d\x22\x53\x70\xb5\x53\x70\xf5\x20\xed\xeb\x5c\xc9\xc7\x58\xc3\x48\x33\x3f\x97\x18\xbf\x42\xb1\x57\x5a\x84\x6d\x07\x60\x4a\xd2\x37\x6e\x5b\x97\xfd\x1b\xf5\x56\x2a\xb8\xfa\x24\xec\x67\x4c\xe7\xc3\x1b\x83\x83\x5f\x73\x05\x31\xc6\x0b\xd6\xbe\x45\xe7\xf2\x64\x14\x3c\xbb\x08\x8c\x8a\xa7\xfa\x71\x21\xc6\xc2\x1c\x30\x0e\x7e\x0d\x02\x27\xfe\x81\x78\x20\xe1\x3b\xd6\xc9\x78\x84\x39\xd8\xf5\xe2\x29\x60\x94\xcd\x04\xe1\xcd\x8a\x1d\x47\xa3\xe0\xea\x0b\x7e\xce\x5c\x7a\x8a\xfc\x64\x7c\xfc\x2c\x48\xf1\x75\xb8\x6f\x48\x9c\x37\xf8\x31\xdb\x4a\x64\x9c\x84\xb2\xbd\x19\xe0\x3f\xf2\x17\x60\xb6\xee\x85\x68\xe0\x26\x40\x34\x12\x13\x20\x46\xa8\x0e\xbf\x61\x1b\x6c\x8b\x7d\xf4\xe2\xc9\x10\x6a\x58\x0b\xe1\xcb\x5b\xc1\xa8\xfc\x46\xff\x9c\x70\xb5\x0d\xc7\xd0\xa3\x65\x33\x91\xe6\x3c\x0c\x17\x7f\xa4\xeb\x14\xc5\xb4\xc2\xc3\xe8\x3c\x43\xb1\x65\xb0\x3b\xe5\x98\xb1\x0d\xb6\xc5\x3e\xf2\x7c\x30\x13\x22\xdd\x35\x10\xf5\x5f\xa7\x73\xa9\x28\xbc\xc7\x1e\x43\x93\xe0\xca\x53\x14\x73\x16\xa6\x8e\x89\x86\x83\x9f\xce\x41\x55\xdf\x23\xec\x62\x77\x2e\x9d\xc7\xed\xb3\xa8\x1c\x5c\x10\xa2\xa2\x05\x22\xb7\x3e\x21\xc2\x77\xac\xeb\xff\x1e\xa2\x3e\xd8\x17\x79\x20\xaf\x68\xa8\x87\xda\x84\xea\x97\xcb\xbc\xb3\x1c\x43\xa5\xe0\xea\xe4\x74\x62\xba\xe1\xe0\x0f\x35\xac\x91\x36\xc0\xdd\x10\xac\x7b\xbb\x1f\x7b\x34\x0c\x66\x47\x05\xdd\x3d\xe9\xa5\x8f\x80\x5e\x34\x51\x52\xe9\x23\xf2\x3e\xaa\xa3\x82\xda\xd8\x63\x08\xd6\xfe\x52\xc6\xee\xde\x0c\xe2\x69\x8f\x1d\xcf\x4d\x92\xbf\x1a\x45\x7f\xeb\xf3\x32\x57\x5f\x0a\x3b\x4a\x17\x3f\xea\xd2\x28\x9f\x25\xf5\x76\xe4\x3b\xfd\x79\xad\x70\x1f\x84\xce\xff\x17\xe8\x7b\x26\x59\xfe\xc6\xca\xdb\xdb\x3e\x46\x63\xf4\x0d\xdb\xd8\xbc\xe9\x0c\x78\xe4\x79\xe2\x85\x3c\x69\x9e\x68\x7e\x7b\x21\x50\xfd\x63\x7b\x4d\x5f\x13\x5c\xfd\x7a\xca\x73\x45\x9a\xf8\x69\x7e\xad\xb5\x67\xde\xa8\xea\xd7\x59\xdd\x02\x79\x0f\xc1\xa5\x1f\xe9\xd9\x39\x06\x6e\x6e\xcd\x22\xc2\x77\xb9\x36\x5d\xd4\x06\xdb\xda\xf6\x64\xde\x38\x42\xbc\x90\x27\xf2\x76\x4c\xac\xbb\x86\x72\x44\xd6\x18\xde\xd3\xb9\xcb\x9d\xcc\x1f\xa5\x83\x9f\xf2\x5c\x15\x73\x48\x5f\x81\x4f\x5f\x75\xce\xa8\xe1\xcb\x5b\x1c\x9b\x45\xac\x57\xde\xcd\xa3\x5c\x66\xcd\xd2\x29\x44\xf8\x8e\x75\x72\x1c\x0a\xb5\xc5\x3e\x92\xa9\x49\xbc\x68\x0e\x2a\xe6\x0c\x38\x5b\x87\x2e\x2c\xb1\xce\x86\x6a\xbb\xe0\xea\x57\x93\x9f\x2d\x53\xe3\xa7\x5c\xcf\xae\x6c\xb2\x03\xb3\xb3\x5a\xea\xa9\xaf\x51\x9e\x5d\x35\x06\xdd\xdb\x33\x9c\x9c\x53\x3c\xc2\x6f\xd8\x86\xfc\x4e\xf9\x57\x9c\xfc\x9f\xd9\xf9\xb1\xb4\xbb\xc2\x6c\x30\xdb\xcb\xfb\xf5\xa5\x5f\x95\x79\x41\xb9\x67\x2c\x12\xdc\x9d\xf0\xce\x2a\x1d\xfc\xe4\xdf\xc8\x5f\x7c\x97\x72\xf1\x54\x57\xf3\xef\x64\x17\xbd\x1e\x37\xe5\x6c\x12\x61\xb7\x09\xdb\x60\x5b\xec\x83\x7d\xe5\x00\x0c\xe2\x89\xbc\x51\xc6\x00\x99\x75\x0b\x6d\x1b\x3a\x2e\xb8\x32\x61\xa4\xf8\xf1\x37\xe5\xe9\x35\x06\xa1\x86\x55\x96\x7e\x5a\xc0\x28\x9d\x49\x75\xad\xef\x8d\x1b\x82\xb5\x76\xd9\xc0\x7b\x00\x9b\xb0\xad\xed\xfb\xa3\xfa\x15\x69\x2b\x17\x57\x51\x1d\xca\x88\x95\x8d\xfe\x57\xde\x9f\x29\x3e\xc1\xd5\x6f\x26\xce\x8f\x24\xc7\x1f\xf1\x35\x80\x5e\xf2\x00\xe8\xbb\xf3\x21\x72\xf3\x98\x65\xf7\x1f\x80\xf0\x8e\x81\x9e\x1d\x63\xc8\xc6\x6d\x7c\x35\x4b\xa7\x92\xbd\xdf\xda\x96\x09\x5d\xdb\x33\xa1\x6d\x43\x8e\x73\xbf\x60\xe7\xf9\xb1\x0f\xfa\x48\xdc\x7b\x89\xff\xcd\x6a\xe2\x4d\x79\x65\x5f\xc3\x40\xbd\xfd\xee\x5b\xb6\x0d\x51\x7e\x3b\xde\x3a\x4e\x85\xdf\xec\x38\x44\xb6\x6f\x94\x3d\x46\xff\x67\xc0\x27\x70\xf2\x65\xe2\x7b\xed\xfd\x9c\x98\xbc\xe7\x54\xa9\x5f\x27\x16\x50\x40\xe7\x2a\x5c\xdf\x9c\x4d\xf7\x32\x76\x3b\xec\x83\x7d\x03\x9f\xfc\xa3\xc4\xe9\x6f\x27\xde\x28\x03\x65\x0d\xb0\xa1\x9a\xff\xb0\xf1\x6f\xd3\xb9\x2b\xee\x9d\x69\x2a\xfc\xa4\x6b\x9c\xdf\xc3\xcf\x01\x44\x02\x94\xcb\x35\x2a\x9f\xa6\xba\xa6\xb5\x13\x9c\x7c\x29\xae\x51\x9f\x47\xe6\x93\xf4\xc2\x6c\xd0\x77\x8f\x73\xe2\xcd\xcb\xeb\xf3\x9d\xfc\x2a\xf6\x21\x7e\x87\x9e\x96\x79\xe1\x48\x80\x78\x63\x1d\xca\x1a\x20\xfb\x2a\xb7\xf7\xb3\x6a\xc1\x95\xdc\x91\xe0\x0f\x5d\x58\x26\xfd\xe6\xf1\x97\xa4\xbe\xc4\x65\xca\x5d\xf5\xee\x54\x29\xe7\x6d\xeb\xb5\x0d\xf5\xca\x5d\x60\xec\x9d\x4a\x77\x30\x91\xda\x37\xc0\x5f\x3e\x93\xc6\xd3\xf9\xc1\x58\xb2\x2d\x3b\x4f\xee\xdb\xa9\x12\x0f\xe4\x45\xf3\x79\xfc\x25\x92\x81\xb2\x06\xd8\xee\xcd\x13\xf2\xce\x98\x2b\x4d\x82\xab\x0f\x26\xc9\x91\x3e\x97\x08\x3f\xed\x4f\x3b\x19\x04\x4f\xbd\x2e\x79\xf6\xd4\xd2\xbe\xd3\xbd\x7d\x0c\x9c\xb5\x6c\x1b\xb1\xe1\x7e\xa5\x7b\x55\x08\x1d\xfb\x11\x40\xf3\x5a\x22\xf3\xf4\xcf\x69\x2e\xd0\xff\xa3\xed\x23\x7e\xec\xd3\xbd\xdd\x4d\x31\x74\xa4\xa7\x4e\xca\x38\xf5\xba\x94\x81\xfb\x5b\xac\xef\x10\xcd\x74\xf7\x64\xdd\x79\xcf\x4a\x82\xff\x5b\x42\x63\x86\x71\xf0\xc9\x21\xb1\x24\xf9\x4e\xe4\x5d\x3b\xdf\xd2\xc9\x71\xd0\x8b\xf2\xa1\x6b\x5b\x86\xe3\x63\xb0\xec\xda\x9e\x41\xf7\x6f\x66\xcd\xeb\x00\xcd\x6b\x00\x9a\x56\x43\xb4\x7e\x31\x18\x7b\x27\x83\xcf\x23\xe7\x0a\xf1\x3b\x6d\x8b\xf2\xe9\xff\x3a\x24\xa3\x76\xbe\x94\x31\xc8\x87\x62\x6c\x4a\x31\x8b\xc6\xfa\x04\x57\x9f\x4e\x82\xff\x69\x6c\x43\xb1\x88\xff\xfa\xc8\xf0\x6f\xcb\x00\x7d\x57\x16\x98\x67\x7e\xde\x8f\xff\x42\x01\x18\xc5\x93\x08\xff\xf9\x91\xe0\x0f\x76\x03\xea\x14\x75\x4b\x3a\x4e\x8c\x7f\x16\xe5\x96\xf7\xcd\xa0\x39\x4b\x6e\x3f\x75\x34\xf7\x68\x03\xb6\xfd\xa0\xef\xb9\xb1\x85\xee\xd3\x21\x78\xe4\xcf\xe8\x0e\x10\xed\x27\x7c\xe2\x45\xd0\xbd\x19\xd0\xbd\x23\x83\xda\x0e\xdb\x7e\xc2\x7d\x74\x67\x81\xb6\x4d\x36\x9e\x18\xff\x83\xb8\x46\xf4\xa2\x09\x74\xdf\x31\x92\xf5\x7b\xf5\xb7\xb9\x96\xef\xc9\x81\x60\xd5\x73\x10\xaa\xfe\x3e\xe8\xc5\xf7\x82\xce\x15\xba\xd3\xc2\x31\x9e\xfa\xb5\xbd\x7e\x95\xb4\xd6\xef\x20\xfc\xdf\x8e\x8f\x9f\x72\xeb\xb9\xe4\xa3\x70\x5f\xb9\xca\x07\xf0\x48\xd7\x7f\x9e\x5b\x81\x7b\x53\x46\x7f\xec\x6c\x91\x4f\xeb\x8f\x2f\xb0\x6d\xe3\x30\xfc\x67\xba\xf8\xe5\xde\xa0\x6e\x47\x1e\xc1\x9a\xf9\x03\x78\xa4\xbb\x7f\x21\x35\xad\x1b\x0f\xbd\x9e\x31\xd6\x3d\xa6\x0a\x7d\x9a\x9b\xe6\x05\x75\x3f\x64\xff\x3a\xf9\xb2\xb5\x46\x13\xef\x5f\xe9\xe0\xd7\xed\xbb\x3e\xdc\xa3\x51\x2f\xbf\xfb\xd3\x81\x71\x48\xdc\xf8\x61\x6b\xdc\xf8\xc1\xde\xc7\x30\x6e\x40\x9c\xa8\xeb\x58\xec\xc3\x89\x1f\xd2\xc5\x1f\xb3\x06\xbe\x29\xb8\xd2\xa7\xef\xb9\x0f\x22\x5d\x9f\x0e\xe0\xe1\xc4\x6f\x17\xd3\x8b\xdf\x12\xd1\x70\xe2\xb7\xe1\xe1\xa7\x35\x30\x81\x62\x55\xcd\x45\xe7\xd0\xd8\xe7\xb3\x88\x9f\x87\x85\x5f\x53\x18\xfd\x3f\x95\xab\x8b\xe5\x19\xe3\x71\x3a\x43\x38\x6b\xe0\xf7\x76\x7e\xa9\x8e\x7b\x7e\x19\x2e\xfe\x18\x1b\xc2\xb3\x5a\x07\x9e\xdd\xf0\x0c\xe7\xf0\xb9\x23\xe7\xc7\x9b\xa3\xc4\x4f\x7e\xc8\x8d\x67\x66\x3a\xa7\xef\x7f\x88\xce\xd2\xf6\x93\xf6\xf9\x7d\x47\xcc\xf9\x7d\x47\xb2\xf3\x7b\x55\xdc\xf3\xfb\x48\xf1\xc7\xcc\xc1\xd7\x28\x77\x81\x3e\xae\xfa\xaf\x29\xa7\x01\x43\xf2\x27\xcf\x0f\xcc\x9f\x9c\xbb\x3d\xf9\x93\xd1\xe2\xef\xe3\x2e\xe6\xe3\x4c\x91\x67\x66\x35\x8a\x7e\x8e\xd6\xb2\xa5\xb3\xd4\xf9\xab\x17\xc1\x28\x7d\x04\xf4\x3d\x13\x89\x8c\x44\xf9\xab\xba\xb7\x87\xe6\xaf\x6e\x03\xfe\x98\x39\x98\x2c\xb8\x7a\xc8\xb6\xd9\x50\xfd\x32\x99\x1b\x0c\xf5\x38\xfe\xe2\xf6\xe5\x0f\x13\xe7\x4e\x47\x84\xbf\x50\xb5\xfd\xe9\x1c\x2b\x97\x4a\xb9\x55\xfa\xdf\x50\xe0\x3a\x44\xba\xcf\xc8\xdc\x37\xda\x8a\x9d\xbf\x4d\x82\xc1\xc1\x62\xe5\x6f\xb1\x0f\xe5\x7e\x4b\x1f\xa5\x5c\x70\xd2\x3e\x23\xc0\x4f\x63\xf0\xba\x58\x8f\x27\x1b\xe7\xe1\x05\x99\xd3\x56\x68\xbe\x8d\xca\x6f\xd0\xbe\x19\x6a\x58\xeb\xac\x3d\x27\x7f\xde\x56\x92\x7e\xfe\xbc\xe4\x41\x30\xdb\xf6\xa5\x1e\xf3\x08\xf1\xe3\xa3\x7b\x15\x66\xec\xa2\xff\x6d\xbc\x60\xdd\x2d\x48\xbc\xbb\xb2\xe8\xbf\x31\x7a\xf1\xd4\x24\xf7\x17\xf3\x89\x12\xdd\x5f\x18\x15\x4f\xd1\x1d\x48\xca\x7b\x92\xda\xb7\xac\xff\xda\xba\x86\x8d\x9f\xc6\xc0\x5d\x0c\xbc\xe4\x5b\xd1\x27\x95\x39\x77\x64\x8e\x8f\x19\xcd\xfd\xd1\xb0\xee\xa9\x46\x84\x1f\x1f\x43\x73\xd9\x6b\x7a\xbc\xbc\x6b\xa3\x3b\xb7\x4e\xeb\x0e\xee\x4e\xdd\xf7\x8d\x18\xbf\x33\x0e\x8f\xc2\x7a\x8b\x54\x8c\x35\x72\x04\x57\x67\x0b\xae\xfe\xc4\xba\x0b\x2d\xb8\x03\xf7\xae\x6f\x5b\x77\xbc\x23\xc6\x7f\xf7\xb9\xfb\x7c\x9e\x1e\xb9\x43\x24\x2e\x5b\x18\x63\xcf\x58\x65\x9e\x55\x66\x5a\xa5\x6b\x50\xc9\xec\xb2\xc0\x2a\x9f\x19\x54\x4e\x4f\x50\xe6\x25\x28\x33\x6f\x5f\xd9\x93\xa0\x0c\x24\x28\xcd\x41\x65\xd4\x2a\xc1\x2e\x17\x0e\x2a\x5b\xac\xb2\xc7\x2a\x4d\xab\x4c\xa1\xdf\xff\x0f\x00\x00\xff\xff\xc6\xb9\x24\x2f\xee\x3a\x00\x00" +var _faviconIco = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5a\x0b\x70\x55\xc7\x79\xde\x7b\xce\x45\x12\xb2\x90\xc4\xc3\xe6\x61\x3b\x90\xf8\x31\xc4\x19\x6c\x32\xe3\xc4\x34\xe3\xc6\x34\x6d\xed\xd4\x69\x62\x32\x49\x9a\xa6\x75\xea\x36\x33\xee\xd8\x9e\xb4\x75\xdc\x7a\xa6\xc5\x31\x02\xa6\xd3\x84\x47\x28\x6f\x3b\xc6\x3c\xcc\xeb\x9e\xbd\x08\x21\x04\x92\x28\x12\x0e\x08\x5b\x3c\xec\x02\x92\x78\x08\x21\x09\x41\x25\x24\x10\xe8\x71\xb5\xe7\xdc\xd7\xb9\xf7\xef\xfc\xff\x9e\x73\x74\x25\xdd\x97\x24\x82\x33\x1e\xce\xcc\x3f\x7b\xee\x9e\xdd\xff\xff\xf6\xdf\x7f\xff\xfd\xf7\xdf\xcb\x98\x8b\xa9\x2c\x3f\x1f\xcb\x19\xec\x15\x37\x63\x5f\x67\x8c\xcd\x98\x21\x7f\x6b\xf9\x8c\x6d\x72\x33\x36\x7b\xb6\xf5\xfb\x11\xc6\x9e\xbd\x97\xb1\x99\x8c\xb1\x7c\x6c\xc7\x64\x3d\x3d\x6e\x76\xdb\x1f\xe1\x75\xab\x42\x53\x7e\x22\x3c\x6c\x91\xf0\xb0\x02\x22\xae\x14\x08\x8f\xab\x40\xec\x64\x92\xf0\x5d\x53\xfe\x59\xec\x64\x5f\x15\x1e\xa6\x08\x4f\x7f\x7f\xdf\x7a\x96\xa1\x97\x3c\xb8\x3f\x78\xfa\x0d\x08\x9e\x5d\x08\xc1\xda\x5f\x82\x51\xf6\x65\x30\xca\x67\x41\xf0\xcc\xbf\x11\x19\x07\x1e\x07\xbd\x78\x2a\x18\x65\x8f\xb5\x0b\x4d\x7d\xad\x6f\x07\x73\x0b\xcd\x45\xfd\xbb\x17\xb3\x0c\xa3\x62\xce\xfe\xa8\xd1\x0a\xf8\x98\x6d\x25\x10\x38\xfe\x53\x88\xdc\x3a\x01\x66\x6b\x11\x11\xbe\x07\x8e\xfd\x2d\x84\x2e\x2c\x05\xff\xd1\x79\x3d\xc2\xc3\x7e\x20\x78\x06\x13\x5c\x89\xe9\xdf\x06\xd1\x50\x2f\x04\x4e\xfc\x0c\xcc\x6b\xa5\x60\x1c\xfd\x21\x74\x6c\x1e\x47\x84\xef\x58\x17\xf8\xe4\x65\x30\x6f\x54\x81\x71\xe0\x89\xea\xbe\x2d\x6c\xd2\x80\xfe\xfe\x76\x30\x6f\x1c\x81\x60\xed\x7c\x08\x9e\x5d\x0c\x2d\xeb\xc7\x41\xed\xd2\x29\x50\xbb\x74\x32\xbd\x63\x1d\x7e\xc3\x36\xa1\x73\xff\x19\x14\x1e\x36\x4f\x78\xc7\xc4\xf4\xef\x80\x70\xd3\xfb\x10\x6e\x5c\x0f\xdd\x65\x73\xe1\xdc\xf2\x89\xd0\xb1\x25\x0f\x6e\x7c\x90\x07\xe7\x7f\x33\x81\xea\xf0\x5b\xb8\x79\x13\x61\xd0\x8b\xa7\x2c\x09\x7c\xfc\x57\xac\x7b\x11\xf6\x7f\x6a\x5f\xd4\xb8\x06\xa1\xfa\x15\x10\x6e\x7a\x0f\x3a\xb4\xc7\xa0\x61\x55\x3e\x04\x8f\xfc\x39\x84\x3e\xfa\x4b\x68\x5c\x93\x4f\x75\xf8\x2d\x74\x71\x05\x44\x45\x13\xea\x54\xeb\x7c\x85\xa9\x48\xf8\x1e\x15\xcd\xf4\xcd\xee\x7f\x71\x65\x3e\x04\x3e\x7c\x86\x78\x5c\x5a\x1d\xd3\xbf\x7e\x05\xa0\x2c\x94\x89\xb2\x03\xd5\x3f\x66\x88\x05\x31\x21\x36\x1b\xff\xd9\xe5\x13\xe1\xda\xc6\x5c\x68\xdf\x94\x4b\x63\x71\xf0\x37\xbd\x0f\x38\x56\x1c\x33\x8e\x1d\x75\x80\xba\x40\x9d\x0c\xd6\x5f\xcd\x92\x29\x44\x83\xf5\x87\xba\x76\xfa\x73\x85\xe1\x5c\x18\x07\x9e\x38\x86\x18\x68\x8e\xac\xf9\xbb\xbe\x39\x87\xc8\x99\xbf\x13\x3f\xa3\x39\xc6\xb9\x8e\xed\x4f\xb6\xe0\x61\x3f\x40\xdb\x40\x1b\x41\x5b\x19\x62\x3f\xc7\x7f\x4a\xb6\x85\x0f\xda\x9a\xdd\x9f\xd6\x80\xe6\x62\xd2\x26\xd5\xd7\xd0\x46\xc9\x56\x0f\x3c\xde\x6f\xbf\xe5\xb3\xc8\xa6\xd1\xb6\xc9\xc6\x4f\xbf\x01\x68\xf3\x68\xfb\xce\x3a\xf2\x30\x26\x76\x32\x85\xd6\x08\xae\x95\xc1\xeb\x87\xd6\x14\xb3\x69\x11\xad\x39\xaf\x5b\x1d\xed\xfa\x05\x60\x8c\x65\x32\xc6\x54\x8b\x5c\x31\x64\x3d\x0b\x63\xe8\xb0\x45\x2d\x56\xdf\x99\x96\x8f\x99\x1b\xeb\x67\xf2\x47\x8b\xea\xf3\xf9\x08\xae\x22\xe5\x09\xae\x4e\x17\x5c\xfd\xc2\x28\x69\x8a\xe0\xca\x58\xb4\x3d\xdb\x17\xa6\x29\xff\x5f\x04\x57\x5b\x85\xe6\xba\x92\x90\xb8\x1a\x43\x4a\x4c\xbd\x62\xd7\xb7\x08\xae\xd6\x0b\xae\xfe\x8f\xe0\xea\x9b\x38\x1e\x1f\x1f\x4b\xfe\x34\xb5\x7c\xa5\x40\xec\xca\x02\xbd\x64\x3a\xe8\xfb\xbe\x38\x94\x4a\xa6\x83\xf0\x66\x82\xd0\x14\x10\x9a\x0b\x44\xe1\x38\x5c\x2b\x44\xf8\x4e\x75\x5c\x01\xe1\x75\x03\xf2\x11\x9a\x2b\x2a\xb8\x52\x2b\xb8\x3a\x4f\x68\x8a\x92\x0c\x03\xc9\xf7\xb0\x02\xa3\xfc\x2b\x10\xe9\xae\x85\xa8\x7e\x15\xa2\xfa\x95\x18\xba\x0a\xa1\xb3\x8b\x49\xbe\xbe\xe7\x5e\x08\xfe\xef\x3f\x81\xd9\x51\x09\x91\xbe\x4b\x44\xf8\x8e\x75\xf8\x4d\x70\x37\x04\x3e\xfe\x11\xf9\x14\xbd\x68\x3c\xe2\xe8\x14\x5c\x7d\x49\x78\x99\x2b\x11\x06\x47\xfe\x81\xd9\x10\x0d\xde\x82\xc1\x8f\x79\xe3\x30\xe8\x7b\xa7\x81\x51\xfa\x28\x98\xd7\xca\x00\xa2\xa6\xfc\x80\x65\xcc\x3b\x7e\xc3\x36\xfa\xde\x07\x08\x93\xd9\x5a\x0c\xfa\xfe\x87\x41\x68\xac\x43\x70\xf5\x59\x92\xe3\x1d\xea\x1a\x92\xc9\x8f\x06\x3a\xc1\xff\xe1\x5c\xda\x67\x91\x27\x3e\x91\xde\x0b\xe4\xaf\xfc\x47\x5f\x20\xc2\x77\xac\x23\xac\x1d\x95\xd4\xd6\xff\xe1\x9f\x10\x2f\xfa\x5d\xf2\x05\xc4\x70\x4c\x70\xf5\x7e\x94\x35\x1c\xf9\xa1\xc6\x77\x68\x3e\x43\x17\x57\x4a\xfe\xe8\xc3\xcb\xbe\x8c\xfc\xa0\xcf\x23\x09\xdf\xb1\x0e\xbf\x51\x9f\x8b\x2b\x65\x9f\xc6\x77\xe8\x37\xee\x4f\x62\xd7\x58\xb4\x8f\x05\x3e\x9e\x35\xc4\x1e\x13\xc9\x8f\x06\xbb\xc0\xa8\xfc\x23\xf0\x1f\xfa\x63\x88\x86\xba\x21\x72\xeb\x24\x18\xfb\x1f\x82\xde\x1d\x2e\x68\xdd\x30\x0e\x1a\x56\x4f\x24\xc2\x77\xac\xc3\x6f\xd8\x06\xdb\x62\x1f\xec\x8b\xef\x10\xd6\xc1\xff\xd1\xf7\x11\x67\x83\xe0\xea\x97\x06\xeb\x20\x91\x7c\xd2\xdd\xee\x3c\x08\x37\x6f\x04\x30\x0d\x08\x7c\x34\x8f\xe4\x34\xae\x99\x00\xa7\x7f\x3d\x0d\x4e\xfd\xea\x7e\x22\x7c\xc7\x3a\xfc\x86\x6d\xb0\x6d\xb8\x69\x23\xf5\xb5\xe7\xcc\x6c\x3f\x00\x62\x77\x6e\x54\x68\xae\x57\xad\xf5\x96\x52\x7e\xb0\x6e\x01\xe8\xfb\x66\xd0\x1a\x30\xaf\x95\x83\x28\xcc\x81\x2b\xef\xe6\x92\x3c\x24\x8a\x63\x96\x4d\x71\x7e\xe3\x37\x6c\x83\x6d\xb1\x0f\xf6\x0d\xd6\x15\x48\x5d\x86\x7c\x64\x47\xc2\xc3\xf6\x0a\xae\x66\xc6\xea\x20\xae\xfc\x48\x08\xfc\x55\xdf\x05\x7f\xd5\x77\x00\x22\x41\x08\x7e\xfa\x2a\x74\x6d\x53\xa1\x6e\xf9\x64\x92\x75\x69\xcd\x44\xb8\xb9\x35\x13\x6e\x6d\xcb\x80\xe6\x75\xe3\xa9\x0e\xbf\x61\x1b\x6c\x8b\x7d\xb0\xaf\xbf\xea\x7b\xc4\x8b\xec\xe2\xec\x22\x9c\x83\x66\xc1\xd5\x19\xa9\xe4\xd3\xdc\x1f\x98\x0d\xc1\x33\x6f\x42\x34\xec\x03\x7f\xe5\x1c\x68\xdb\x30\x96\xe4\x9c\xfb\xcd\x7d\xd0\xbd\x63\x0c\xe8\x85\x63\x41\x2f\xbc\x07\x7a\x3d\x6e\xa8\x5f\x39\x89\xbe\x61\x1b\x6c\x8b\x7d\xb0\xaf\xe4\xd9\x65\xd9\x6e\x19\x88\x5d\xd9\xba\xd0\x5c\x73\x53\xca\xd7\xff\x0f\xf4\xfd\x0f\x41\xa8\x61\x35\xf9\x1f\x7d\xdf\x74\x68\x5e\x97\x47\xf3\xdd\xf2\x4e\x1e\xc9\x0d\x9f\xfc\x3b\x30\x4f\xbd\x02\xc6\x9e\x09\xd0\xba\x21\x07\x4e\xfd\x6a\x1a\xb5\xc1\xb6\xe4\xb3\x1a\x56\x13\x0f\xe4\x25\xd7\xed\x39\x5c\x9b\x51\xa1\xb1\x17\xe3\xc8\x7f\x0b\x63\x13\x5c\xef\xd4\xb6\xaf\x91\x7c\x6b\xf8\xf2\x16\x5a\xdb\x62\xcf\x64\xb8\xb4\x7a\x3c\xc9\x68\xdb\x90\x0d\xfe\xb2\x87\x01\x2e\x2d\x07\x68\x5a\x05\x81\xca\x27\xe1\xfa\xe6\x4c\x39\x2f\xab\xc7\x53\xdb\x88\xaf\x9e\xfa\x22\x0f\xe4\xe5\xc4\x47\xa5\x8f\xa2\x0d\xfc\x22\x8e\xfc\x5f\xe0\x37\x3b\x5e\x4f\x2e\xff\x1e\xf0\x97\x7e\x11\xa2\x0d\x4b\x00\x9a\x56\x42\xe0\xe0\x13\x70\x7d\x53\x56\x6a\xf9\x81\x4e\x8a\xbf\x70\xac\x43\xe4\x6b\xec\x45\xd4\x0d\xea\x28\xbe\xfe\x67\x38\xfa\x6f\x5e\x97\x2f\xfd\xcb\xd1\xe7\x21\x74\xec\x87\xa0\xef\xce\x81\xab\xbf\x1d\x67\x7d\xcb\xb3\xd6\xcc\x50\xfd\xe3\xdc\xe2\x1c\xcb\xb3\xd0\x60\xf9\xae\xb9\x68\x1b\xe4\xdf\x53\xd8\x1f\xda\x39\xda\xbe\xee\x55\x41\xf7\xba\xc9\x16\xcf\xaf\xb8\x2f\xa5\xfd\x25\x95\x8f\x6b\x42\x63\xcd\xb8\xcf\x25\x5c\x7f\x5b\xe5\xfa\xc3\x39\xb8\xf0\xdf\xf7\x42\xfb\xc6\x7b\xa0\x63\x53\x36\x5c\x5c\x35\x29\xad\xf5\x97\x42\x7e\x26\xfa\x06\xda\x37\xc2\xbe\xb4\xfc\x0f\xd2\x99\x25\xd3\xd2\xf2\x3f\xc9\xe5\x2b\xf6\x1c\xbc\x86\x3e\x12\x7d\xe5\x50\xff\xeb\x8f\xe3\x7f\x25\x0d\xf5\xbf\xfe\x21\xfe\x37\x99\xfc\x18\x1d\x7c\x09\xf7\x08\xdc\x2b\xc0\xd4\x69\xef\x30\x2a\x46\xb9\xff\x58\x73\x9f\x5a\xbe\xc2\x7c\x1a\xed\x8d\x0b\x70\xaf\xc4\x3d\x53\xee\xbf\xef\x8e\x7a\xff\x4d\x47\x7e\x8c\x0e\xee\xc7\x58\x01\x63\x06\xd4\x1d\xf6\x41\x9b\x18\x51\xfc\x61\xf9\xb2\xb4\xe5\x7b\x55\x1b\xc3\xb3\x18\x33\x61\xec\x84\x31\x14\xf1\xdc\xfb\x40\x5a\xf1\x97\x4e\xf1\xd7\x34\x8a\xd9\x06\x3f\xa9\xe4\x3b\xb6\x48\xb1\xa2\xfa\x12\xc6\x8e\x18\x43\x62\x2c\x89\x31\x25\xc6\x96\x29\xe3\x4f\x6f\x26\xc5\xaa\xf1\x62\x58\x8c\x6d\x31\xc6\x4d\x26\xbf\x7f\x3d\x60\xcc\xac\xce\xa3\x18\x1a\x63\x69\x8c\xa9\xb9\x5b\xc6\xd8\x14\x7f\xe7\xc4\xc4\xdf\x39\xb2\x0e\x63\x73\x8c\x91\x93\xc5\xf0\xc4\x47\x49\x2a\x9f\x30\x68\x0a\xf3\x15\x65\x31\x6b\xaf\x7e\xd3\x3a\x53\xd4\x5b\x67\x8c\x44\xe7\x0f\x49\xc9\xcf\x30\xad\xd6\x59\x27\xa9\xfc\x58\x5d\xf4\x69\x14\x2f\x8d\x95\x67\xab\x51\x9f\xcf\xa6\x5b\x67\xbd\xb4\xe4\x7f\xde\x1f\x7b\x6d\x1c\x66\x2a\x1c\x66\x0c\xe9\x99\xc3\x8c\x4d\xb7\x28\xcf\xa2\x4c\x8b\xd4\x74\xa8\xc5\xa2\x1e\x8b\x02\x16\x99\x8c\xa9\xc0\x48\x90\x6a\xcb\x9d\xc9\x18\x9b\xcd\x18\xfb\xfb\xd8\x3c\xc5\xc3\x9f\xb5\x56\xee\x3e\x77\x9f\x3f\xcc\xc7\xda\x1f\x1f\x16\x5c\x7d\x5b\x70\x75\xa1\xe0\x6a\xc1\xef\x89\x6c\xde\xff\x2a\xb8\xfa\x37\x82\x2b\xb3\x05\x57\x72\x7a\x35\xc6\x0c\x2d\x79\x3e\x29\x0d\xfc\xdf\x16\x5c\x0d\x0a\xae\xc2\x1d\x22\x53\x70\xb5\x53\x70\xf5\x20\xed\xeb\x5c\xc9\xc7\x58\xc3\x48\x33\x3f\x97\x18\xbf\x42\xb1\x57\x5a\x84\x6d\x07\x60\x4a\xd2\x37\x6e\x5b\x97\xfd\x1b\xf5\x56\x2a\xb8\xfa\x24\xec\x67\x4c\xe7\xc3\x1b\x83\x83\x5f\x73\x05\x31\xc6\x0b\xd6\xbe\x45\xe7\xf2\x64\x14\x3c\xbb\x08\x8c\x8a\xa7\xfa\x71\x21\xc6\xc2\x1c\x30\x0e\x7e\x0d\x02\x27\xfe\x81\x78\x20\xe1\x3b\xd6\xc9\x78\x84\x39\xd8\xf5\xe2\x29\x60\x94\xcd\x04\xe1\xcd\x8a\x1d\x47\xa3\xe0\xea\x0b\x7e\xce\x5c\x7a\x8a\xfc\x64\x7c\xfc\x2c\x48\xf1\x75\xb8\x6f\x48\x9c\x37\xf8\x31\xdb\x4a\x64\x9c\x84\xb2\xbd\x19\xe0\x3f\xf2\x17\x60\xb6\xee\x85\x68\xe0\x26\x40\x34\x12\x13\x20\x46\xa8\x0e\xbf\x61\x1b\x6c\x8b\x7d\xf4\xe2\xc9\x10\x6a\x58\x0b\xe1\xcb\x5b\xc1\xa8\xfc\x46\xff\x9c\x70\xb5\x0d\xc7\xd0\xa3\x65\x33\x91\xe6\x3c\x0c\x17\x7f\xa4\xeb\x14\xc5\xb4\xc2\xc3\xe8\x3c\x43\xb1\x65\xb0\x3b\xe5\x98\xb1\x0d\xb6\xc5\x3e\xf2\x7c\x30\x13\x22\xdd\x35\x10\xf5\x5f\xa7\x73\xa9\x28\xbc\xc7\x1e\x43\x93\xe0\xca\x53\x14\x73\x16\xa6\x8e\x89\x86\x83\x9f\xce\x41\x55\xdf\x23\xec\x62\x77\x2e\x9d\xc7\xed\xb3\xa8\x1c\x5c\x10\xa2\xa2\x05\x22\xb7\x3e\x21\xc2\x77\xac\xeb\xff\x1e\xa2\x3e\xd8\x17\x79\x20\xaf\x68\xa8\x87\xda\x84\xea\x97\xcb\xbc\xb3\x1c\x43\xa5\xe0\xea\xe4\x74\x62\xba\xe1\xe0\x0f\x35\xac\x91\x36\xc0\xdd\x10\xac\x7b\xbb\x1f\x7b\x34\x0c\x66\x47\x05\xdd\x3d\xe9\xa5\x8f\x80\x5e\x34\x51\x52\xe9\x23\xf2\x3e\xaa\xa3\x82\xda\xd8\x63\x08\xd6\xfe\x52\xc6\xee\xde\x0c\xe2\x69\x8f\x1d\xcf\x4d\x92\xbf\x1a\x45\x7f\xeb\xf3\x32\x57\x5f\x0a\x3b\x4a\x17\x3f\xea\xd2\x28\x9f\x25\xf5\x76\xe4\x3b\xfd\x79\xad\x70\x1f\x84\xce\xff\x17\xe8\x7b\x26\x59\xfe\xc6\xca\xdb\xdb\x3e\x46\x63\xf4\x0d\xdb\xd8\xbc\xe9\x0c\x78\xe4\x79\xe2\x85\x3c\x69\x9e\x68\x7e\x7b\x21\x50\xfd\x63\x7b\x4d\x5f\x13\x5c\xfd\x7a\xca\x73\x45\x9a\xf8\x69\x7e\xad\xb5\x67\xde\xa8\xea\xd7\x59\xdd\x02\x79\x0f\xc1\xa5\x1f\xe9\xd9\x39\x06\x6e\x6e\xcd\x22\xc2\x77\xb9\x36\x5d\xd4\x06\xdb\xda\xf6\x64\xde\x38\x42\xbc\x90\x27\xf2\x76\x4c\xac\xbb\x86\x72\x44\xd6\x18\xde\xd3\xb9\xcb\x9d\xcc\x1f\xa5\x83\x9f\xf2\x5c\x15\x73\x48\x5f\x81\x4f\x5f\x75\xce\xa8\xe1\xcb\x5b\x1c\x9b\x45\xac\x57\xde\xcd\xa3\x5c\x66\xcd\xd2\x29\x44\xf8\x8e\x75\x72\x1c\x0a\xb5\xc5\x3e\x92\xa9\x49\xbc\x68\x0e\x2a\xe6\x0c\x38\x5b\x87\x2e\x2c\xb1\xce\x86\x6a\xbb\xe0\xea\x57\x93\x9f\x2d\x53\xe3\xa7\x5c\xcf\xae\x6c\xb2\x03\xb3\xb3\x5a\xea\xa9\xaf\x51\x9e\x5d\x35\x06\xdd\xdb\x33\x9c\x9c\x53\x3c\xc2\x6f\xd8\x86\xfc\x4e\xf9\x57\x9c\xfc\x9f\xd9\xf9\xb1\xb4\xbb\xc2\x6c\x30\xdb\xcb\xfb\xf5\xa5\x5f\x95\x79\x41\xb9\x67\x2c\x12\xdc\x9d\xf0\xce\x2a\x1d\xfc\xe4\xdf\xc8\x5f\x7c\x97\x72\xf1\x54\x57\xf3\xef\x64\x17\xbd\x1e\x37\xe5\x6c\x12\x61\xb7\x09\xdb\x60\x5b\xec\x83\x7d\xe5\x00\x0c\xe2\x89\xbc\x51\xc6\x00\x99\x75\x0b\x6d\x1b\x3a\x2e\xb8\x32\x61\xa4\xf8\xf1\x37\xe5\xe9\x35\x06\xa1\x86\x55\x96\x7e\x5a\xc0\x28\x9d\x49\x75\xad\xef\x8d\x1b\x82\xb5\x76\xd9\xc0\x7b\x00\x9b\xb0\xad\xed\xfb\xa3\xfa\x15\x69\x2b\x17\x57\x51\x1d\xca\x88\x95\x8d\xfe\x57\xde\x9f\x29\x3e\xc1\xd5\x6f\x26\xce\x8f\x24\xc7\x1f\xf1\x35\x80\x5e\xf2\x00\xe8\xbb\xf3\x21\x72\xf3\x98\x65\xf7\x1f\x80\xf0\x8e\x81\x9e\x1d\x63\xc8\xc6\x6d\x7c\x35\x4b\xa7\x92\xbd\xdf\xda\x96\x09\x5d\xdb\x33\xa1\x6d\x43\x8e\x73\xbf\x60\xe7\xf9\xb1\x0f\xfa\x48\xdc\x7b\x89\xff\xcd\x6a\xe2\x4d\x79\x65\x5f\xc3\x40\xbd\xfd\xee\x5b\xb6\x0d\x51\x7e\x3b\xde\x3a\x4e\x85\xdf\xec\x38\x44\xb6\x6f\x94\x3d\x46\xff\x67\xc0\x27\x70\xf2\x65\xe2\x7b\xed\xfd\x9c\x98\xbc\xe7\x54\xa9\x5f\x27\x16\x50\x40\xe7\x2a\x5c\xdf\x9c\x4d\xf7\x32\x76\x3b\xec\x83\x7d\x03\x9f\xfc\xa3\xc4\xe9\x6f\x27\xde\x28\x03\x65\x0d\xb0\xa1\x9a\xff\xb0\xf1\x6f\xd3\xb9\x2b\xee\x9d\x69\x2a\xfc\xa4\x6b\x9c\xdf\xc3\xcf\x01\x44\x02\x94\xcb\x35\x2a\x9f\xa6\xba\xa6\xb5\x13\x9c\x7c\x29\xae\x51\x9f\x47\xe6\x93\xf4\xc2\x6c\xd0\x77\x8f\x73\xe2\xcd\xcb\xeb\xf3\x9d\xfc\x2a\xf6\x21\x7e\x87\x9e\x96\x79\xe1\x48\x80\x78\x63\x1d\xca\x1a\x20\xfb\x2a\xb7\xf7\xb3\x6a\xc1\x95\xdc\x91\xe0\x0f\x5d\x58\x26\xfd\xe6\xf1\x97\xa4\xbe\xc4\x65\xca\x5d\xf5\xee\x54\x29\xe7\x6d\xeb\xb5\x0d\xf5\xca\x5d\x60\xec\x9d\x4a\x77\x30\x91\xda\x37\xc0\x5f\x3e\x93\xc6\xd3\xf9\xc1\x58\xb2\x2d\x3b\x4f\xee\xdb\xa9\x12\x0f\xe4\x45\xf3\x79\xfc\x25\x92\x81\xb2\x06\xd8\xee\xcd\x13\xf2\xce\x98\x2b\x4d\x82\xab\x0f\x26\xc9\x91\x3e\x97\x08\x3f\xed\x4f\x3b\x19\x04\x4f\xbd\x2e\x79\xf6\xd4\xd2\xbe\xd3\xbd\x7d\x0c\x9c\xb5\x6c\x1b\xb1\xe1\x7e\xa5\x7b\x55\x08\x1d\xfb\x11\x40\xf3\x5a\x22\xf3\xf4\xcf\x69\x2e\xd0\xff\xa3\xed\x23\x7e\xec\xd3\xbd\xdd\x4d\x31\x74\xa4\xa7\x4e\xca\x38\xf5\xba\x94\x81\xfb\x5b\xac\xef\x10\xcd\x74\xf7\x64\xdd\x79\xcf\x4a\x82\xff\x5b\x42\x63\x86\x71\xf0\xc9\x21\xb1\x24\xf9\x4e\xe4\x5d\x3b\xdf\xd2\xc9\x71\xd0\x8b\xf2\xa1\x6b\x5b\x86\xe3\x63\xb0\xec\xda\x9e\x41\xf7\x6f\x66\xcd\xeb\x00\xcd\x6b\x00\x9a\x56\x43\xb4\x7e\x31\x18\x7b\x27\x83\xcf\x23\xe7\x0a\xf1\x3b\x6d\x8b\xf2\xe9\xff\x3a\x24\xa3\x76\xbe\x94\x31\xc8\x87\x62\x6c\x4a\x31\x8b\xc6\xfa\x04\x57\x9f\x4e\x82\xff\x69\x6c\x43\xb1\x88\xff\xfa\xc8\xf0\x6f\xcb\x00\x7d\x57\x16\x98\x67\x7e\xde\x8f\xff\x42\x01\x18\xc5\x93\x08\xff\xf9\x91\xe0\x0f\x76\x03\xea\x14\x75\x4b\x3a\x4e\x8c\x7f\x16\xe5\x96\xf7\xcd\xa0\x39\x4b\x6e\x3f\x75\x34\xf7\x68\x03\xb6\xfd\xa0\xef\xb9\xb1\x85\xee\xd3\x21\x78\xe4\xcf\xe8\x0e\x10\xed\x27\x7c\xe2\x45\xd0\xbd\x19\xd0\xbd\x23\x83\xda\x0e\xdb\x7e\xc2\x7d\x74\x67\x81\xb6\x4d\x36\x9e\x18\xff\x83\xb8\x46\xf4\xa2\x09\x74\xdf\x31\x92\xf5\x7b\xf5\xb7\xb9\x96\xef\xc9\x81\x60\xd5\x73\x10\xaa\xfe\x3e\xe8\xc5\xf7\x82\xce\x15\xba\xd3\xc2\x31\x9e\xfa\xb5\xbd\x7e\x95\xb4\xd6\xef\x20\xfc\xdf\x8e\x8f\x9f\x72\xeb\xb9\xe4\xa3\x70\x5f\xb9\xca\x07\xf0\x48\xd7\x7f\x9e\x5b\x81\x7b\x53\x46\x7f\xec\x6c\x91\x4f\xeb\x8f\x2f\xb0\x6d\xe3\x30\xfc\x67\xba\xf8\xe5\xde\xa0\x6e\x47\x1e\xc1\x9a\xf9\x03\x78\xa4\xbb\x7f\x21\x35\xad\x1b\x0f\xbd\x9e\x31\xd6\x3d\xa6\x0a\x7d\x9a\x9b\xe6\x05\x75\x3f\x64\xff\x3a\xf9\xb2\xb5\x46\x13\xef\x5f\xe9\xe0\xd7\xed\xbb\x3e\xdc\xa3\x51\x2f\xbf\xfb\xd3\x81\x71\x48\xdc\xf8\x61\x6b\xdc\xf8\xc1\xde\xc7\x30\x6e\x40\x9c\xa8\xeb\x58\xec\xc3\x89\x1f\xd2\xc5\x1f\xb3\x06\xbe\x29\xb8\xd2\xa7\xef\xb9\x0f\x22\x5d\x9f\x0e\xe0\xe1\xc4\x6f\x17\xd3\x8b\xdf\x12\xd1\x70\xe2\xb7\xe1\xe1\xa7\x35\x30\x81\x62\x55\xcd\x45\xe7\xd0\xd8\xe7\xb3\x88\x9f\x87\x85\x5f\x53\x18\xfd\x3f\x95\xab\x8b\xe5\x19\xe3\x71\x3a\x43\x38\x6b\xe0\xf7\x76\x7e\xa9\x8e\x7b\x7e\x19\x2e\xfe\x18\x1b\xc2\xb3\x5a\x07\x9e\xdd\xf0\x0c\xe7\xf0\xb9\x23\xe7\xc7\x9b\xa3\xc4\x4f\x7e\xc8\x8d\x67\x66\x3a\xa7\xef\x7f\x88\xce\xd2\xf6\x93\xf6\xf9\x7d\x47\xcc\xf9\x7d\x47\xb2\xf3\x7b\x55\xdc\xf3\xfb\x48\xf1\xc7\xcc\xc1\xd7\x28\x77\x81\x3e\xae\xfa\xaf\x29\xa7\x01\x43\xf2\x27\xcf\x0f\xcc\x9f\x9c\xbb\x3d\xf9\x93\xd1\xe2\xef\xe3\x2e\xe6\xe3\x4c\x91\x67\x66\x35\x8a\x7e\x8e\xd6\xb2\xa5\xb3\xd4\xf9\xab\x17\xc1\x28\x7d\x04\xf4\x3d\x13\x89\x8c\x44\xf9\xab\xba\xb7\x87\xe6\xaf\x6e\x03\xfe\x98\x39\x98\x2c\xb8\x7a\xc8\xb6\xd9\x50\xfd\x32\x99\x1b\x0c\xf5\x38\xfe\xe2\xf6\xe5\x0f\x13\xe7\x4e\x47\x84\xbf\x50\xb5\xfd\xe9\x1c\x2b\x97\x4a\xb9\x55\xfa\xdf\x50\xe0\x3a\x44\xba\xcf\xc8\xdc\x37\xda\x8a\x9d\xbf\x4d\x82\xc1\xc1\x62\xe5\x6f\xb1\x0f\xe5\x7e\x4b\x1f\xa5\x5c\x70\xd2\x3e\x23\xc0\x4f\x63\xf0\xba\x58\x8f\x27\x1b\xe7\xe1\x05\x99\xd3\x56\x68\xbe\x8d\xca\x6f\xd0\xbe\x19\x6a\x58\xeb\xac\x3d\x27\x7f\xde\x56\x92\x7e\xfe\xbc\xe4\x41\x30\xdb\xf6\xa5\x1e\xf3\x08\xf1\xe3\xa3\x7b\x15\x66\xec\xa2\xff\x6d\xbc\x60\xdd\x2d\x48\xbc\xbb\xb2\xe8\xbf\x31\x7a\xf1\xd4\x24\xf7\x17\xf3\x89\x12\xdd\x5f\x18\x15\x4f\xd1\x1d\x48\xca\x7b\x92\xda\xb7\xac\xff\xda\xba\x86\x8d\x9f\xc6\xc0\x5d\x0c\xbc\xe4\x5b\xd1\x27\x95\x39\x77\x64\x8e\x8f\x19\xcd\xfd\xd1\xb0\xee\xa9\x46\x84\x1f\x1f\x43\x73\xd9\x6b\x7a\xbc\xbc\x6b\xa3\x3b\xb7\x4e\xeb\x0e\xee\x4e\xdd\xf7\x8d\x18\xbf\x33\x0e\x8f\xc2\x7a\x8b\x54\x8c\x35\x72\x04\x57\x67\x0b\xae\xfe\xc4\xba\x0b\x2d\xb8\x03\xf7\xae\x6f\x5b\x77\xbc\x23\xc6\x7f\xf7\xb9\xfb\x7c\x9e\x1e\xb9\x43\x24\x2e\x5b\x18\x63\xcf\x58\x65\x9e\x55\x66\x5a\xa5\x6b\x50\xc9\xec\xb2\xc0\x2a\x9f\x19\x54\x4e\x4f\x50\xe6\x25\x28\x33\x6f\x5f\xd9\x93\xa0\x0c\x24\x28\xcd\x41\x65\xd4\x2a\xc1\x2e\x17\x0e\x2a\x5b\xac\xb2\xc7\x2a\x4d\xab\x4c\xa1\xdf\xff\x0f\x00\x00\xff\xff\xc6\xb9\x24\x2f\xee\x3a\x00\x00" -func assetsFaviconIcoBytes() ([]byte, error) { +func faviconIcoBytes() ([]byte, error) { return bindataRead( - _assetsFaviconIco, - "assets/favicon.ico", + _faviconIco, + "favicon.ico", ) } -func assetsFaviconIco() (*asset, error) { - bytes, err := assetsFaviconIcoBytes() +func faviconIco() (*asset, error) { + bytes, err := faviconIcoBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "assets/favicon.ico", size: 15086, mode: os.FileMode(438), modTime: time.Unix(1565946440, 0)} + info := bindataFileInfo{name: "favicon.ico", size: 15086, mode: os.FileMode(438), modTime: time.Unix(1565946440, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsIndexHtml = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x64\x91\xb1\x6e\xe3\x30\x10\x44\x7b\x01\xfa\x87\xf5\x56\x77\xc0\x49\xbc\x74\x29\x44\x35\x4e\xdc\x26\x40\x9c\x22\x25\x4d\xad\xad\xb5\x29\x4a\x20\xd7\xb2\xfd\xf7\x01\x25\x39\x48\x90\x8a\x9c\x59\xce\xf0\x81\xac\x56\x4f\x2f\xeb\xed\xc7\xeb\x33\xb4\xd2\xb9\x3a\xcf\xaa\xb4\x82\x33\xfe\xa0\x91\x3c\xd6\x79\x96\x3c\x32\x4d\x9d\x67\x00\x00\x55\x47\x62\xc0\xb6\x26\x44\x12\x8d\xef\xdb\x4d\xf1\x88\x3f\x66\xde\x74\xa4\x71\x64\xba\x0c\x7d\x10\x04\xdb\x7b\x21\x2f\x1a\x2f\xdc\x48\xab\x1b\x1a\xd9\x52\x31\x89\x7f\xc0\x9e\x85\x8d\x2b\xa2\x35\x8e\xf4\x43\xf9\xff\xab\xcb\xb1\x3f\x41\x20\xa7\x91\x6d\xef\x11\xda\x40\x7b\x8d\x7b\x33\x26\x59\xb2\xed\x11\xe4\x36\x90\x46\xee\xcc\x81\xd4\xb5\x98\x8f\xa9\xdf\xf9\x28\x37\x47\xb1\x25\x92\x7b\x4b\xa9\x6c\x8c\xaa\x33\xec\x4b\x1b\xe3\xb7\x90\xb0\x38\xaa\x37\xec\x08\xde\x28\x8c\x14\x2a\x35\x5b\x79\x56\xa9\xe5\x15\xf2\xac\xda\xf5\xcd\xed\x1e\x61\x3f\x9c\x65\x41\xd9\x9d\x45\x12\x44\xef\xad\x63\x7b\xd2\xd8\xfb\x75\xda\xfc\xf9\x8b\x30\x1a\x77\x26\x8d\x93\x86\x8e\x56\xf3\xad\x4b\x49\xb4\x81\x87\x7b\x8b\xd0\x55\xd4\xd1\x8c\x66\x76\x11\x62\xb0\x89\xf9\xb8\x20\x1f\x23\xd6\x95\x9a\x87\x13\xd8\x82\x33\x31\xa6\x5f\xfc\x0c\x00\x00\xff\xff\x53\x77\x5a\x24\xd5\x01\x00\x00" +var _indexHtml = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x64\x91\xb1\x6e\xe3\x30\x10\x44\x7b\x01\xfa\x87\xf5\x56\x77\xc0\x49\xbc\x74\x29\x44\x35\x4e\xdc\x26\x40\x9c\x22\x25\x4d\xad\xad\xb5\x29\x4a\x20\xd7\xb2\xfd\xf7\x01\x25\x39\x48\x90\x8a\x9c\x59\xce\xf0\x81\xac\x56\x4f\x2f\xeb\xed\xc7\xeb\x33\xb4\xd2\xb9\x3a\xcf\xaa\xb4\x82\x33\xfe\xa0\x91\x3c\xd6\x79\x96\x3c\x32\x4d\x9d\x67\x00\x00\x55\x47\x62\xc0\xb6\x26\x44\x12\x8d\xef\xdb\x4d\xf1\x88\x3f\x66\xde\x74\xa4\x71\x64\xba\x0c\x7d\x10\x04\xdb\x7b\x21\x2f\x1a\x2f\xdc\x48\xab\x1b\x1a\xd9\x52\x31\x89\x7f\xc0\x9e\x85\x8d\x2b\xa2\x35\x8e\xf4\x43\xf9\xff\xab\xcb\xb1\x3f\x41\x20\xa7\x91\x6d\xef\x11\xda\x40\x7b\x8d\x7b\x33\x26\x59\xb2\xed\x11\xe4\x36\x90\x46\xee\xcc\x81\xd4\xb5\x98\x8f\xa9\xdf\xf9\x28\x37\x47\xb1\x25\x92\x7b\x4b\xa9\x6c\x8c\xaa\x33\xec\x4b\x1b\xe3\xb7\x90\xb0\x38\xaa\x37\xec\x08\xde\x28\x8c\x14\x2a\x35\x5b\x79\x56\xa9\xe5\x15\xf2\xac\xda\xf5\xcd\xed\x1e\x61\x3f\x9c\x65\x41\xd9\x9d\x45\x12\x44\xef\xad\x63\x7b\xd2\xd8\xfb\x75\xda\xfc\xf9\x8b\x30\x1a\x77\x26\x8d\x93\x86\x8e\x56\xf3\xad\x4b\x49\xb4\x81\x87\x7b\x8b\xd0\x55\xd4\xd1\x8c\x66\x76\x11\x62\xb0\x89\xf9\xb8\x20\x1f\x23\xd6\x95\x9a\x87\x13\xd8\x82\x33\x31\xa6\x5f\xfc\x0c\x00\x00\xff\xff\x53\x77\x5a\x24\xd5\x01\x00\x00" -func assetsIndexHtmlBytes() ([]byte, error) { +func indexHtmlBytes() ([]byte, error) { return bindataRead( - _assetsIndexHtml, - "assets/index.html", + _indexHtml, + "index.html", ) } -func assetsIndexHtml() (*asset, error) { - bytes, err := assetsIndexHtmlBytes() +func indexHtml() (*asset, error) { + bytes, err := indexHtmlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "assets/index.html", size: 469, mode: os.FileMode(438), modTime: time.Unix(1594803541, 0)} + info := bindataFileInfo{name: "index.html", size: 469, mode: os.FileMode(438), modTime: time.Unix(1594803541, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsJsMainJs = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\xce\xcf\x2b\xce\xcf\x49\xd5\xcb\xc9\x4f\xd7\x50\x4a\xad\x48\xcc\x2d\xc8\x49\x55\xd2\xb4\xe6\xe5\xe2\xe5\x4a\x2b\xcd\x4b\x2e\xc9\xcc\xcf\x53\xc8\xcf\x73\xce\xc9\x4c\xce\xd6\xd0\x54\xa8\xe6\xe5\x52\x50\x50\x50\x28\xcf\xcc\x4b\xc9\x2f\xd7\x4b\xcc\x49\x2d\x2a\xd1\x50\x4a\x2a\x2d\x29\xc9\xcf\x53\x48\x06\xa9\x49\x4d\x01\x6b\xae\x05\x04\x00\x00\xff\xff\xa4\xb7\x99\x52\x57\x00\x00\x00" +var _jsMainJs = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\xce\xcf\x2b\xce\xcf\x49\xd5\xcb\xc9\x4f\xd7\x50\x4a\xad\x48\xcc\x2d\xc8\x49\x55\xd2\xb4\xe6\xe5\xe2\xe5\x4a\x2b\xcd\x4b\x2e\xc9\xcc\xcf\x53\xc8\xcf\x73\xce\xc9\x4c\xce\xd6\xd0\x54\xa8\xe6\xe5\x52\x50\x50\x50\x28\xcf\xcc\x4b\xc9\x2f\xd7\x4b\xcc\x49\x2d\x2a\xd1\x50\x4a\x2a\x2d\x29\xc9\xcf\x53\x48\x06\xa9\x49\x4d\x01\x6b\xae\x05\x04\x00\x00\xff\xff\xa4\xb7\x99\x52\x57\x00\x00\x00" -func assetsJsMainJsBytes() ([]byte, error) { +func jsMainJsBytes() ([]byte, error) { return bindataRead( - _assetsJsMainJs, - "assets/js/main.js", + _jsMainJs, + "js/main.js", ) } -func assetsJsMainJs() (*asset, error) { - bytes, err := assetsJsMainJsBytes() +func jsMainJs() (*asset, error) { + bytes, err := jsMainJsBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "assets/js/main.js", size: 87, mode: os.FileMode(438), modTime: time.Unix(1594787764, 0)} + info := bindataFileInfo{name: "js/main.js", size: 87, mode: os.FileMode(438), modTime: time.Unix(1594787764, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _assetsJsMainJsMap = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2a\xc9\xc8\x2c\x56\x28\xce\xc8\x2f\xcd\x49\x51\xc8\xcb\x2f\x51\x48\x4a\x55\xf0\x08\x09\x09\xd0\x37\xd2\x2d\x28\x2d\xce\x48\x4d\xd1\x03\x04\x00\x00\xff\xff\x69\x2f\x9b\xb9\x21\x00\x00\x00" +var _jsMainJsMap = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2a\xc9\xc8\x2c\x56\x28\xce\xc8\x2f\xcd\x49\x51\xc8\xcb\x2f\x51\x48\x4a\x55\xf0\x08\x09\x09\xd0\x37\xd2\x2d\x28\x2d\xce\x48\x4d\xd1\x03\x04\x00\x00\xff\xff\x69\x2f\x9b\xb9\x21\x00\x00\x00" -func assetsJsMainJsMapBytes() ([]byte, error) { +func jsMainJsMapBytes() ([]byte, error) { return bindataRead( - _assetsJsMainJsMap, - "assets/js/main.js.map", + _jsMainJsMap, + "js/main.js.map", ) } -func assetsJsMainJsMap() (*asset, error) { - bytes, err := assetsJsMainJsMapBytes() +func jsMainJsMap() (*asset, error) { + bytes, err := jsMainJsMapBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "assets/js/main.js.map", size: 33, mode: os.FileMode(438), modTime: time.Unix(1595036177, 0)} + info := bindataFileInfo{name: "js/main.js.map", size: 33, mode: os.FileMode(438), modTime: time.Unix(1595036177, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -369,18 +468,18 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "assets/app2/app2app3/css/main.css": assetsApp2App2app3CssMainCss, - "assets/app2/app2app3/dirs/dir1/text.txt": assetsApp2App2app3DirsDir1TextTxt, - "assets/app2/app2app3/dirs/dir2/text.txt": assetsApp2App2app3DirsDir2TextTxt, - "assets/app2/app2app3/dirs/text.txt": assetsApp2App2app3DirsTextTxt, - "assets/app2/app2app3/index.html": assetsApp2App2app3IndexHtml, - "assets/app2/index.html": assetsApp2IndexHtml, - "assets/app2/mydir/text.txt": assetsApp2MydirTextTxt, - "assets/css/main.css": assetsCssMainCss, - "assets/favicon.ico": assetsFaviconIco, - "assets/index.html": assetsIndexHtml, - "assets/js/main.js": assetsJsMainJs, - "assets/js/main.js.map": assetsJsMainJsMap, + "app2/app2app3/css/main.css": app2App2app3CssMainCss, + "app2/app2app3/dirs/dir1/text.txt": app2App2app3DirsDir1TextTxt, + "app2/app2app3/dirs/dir2/text.txt": app2App2app3DirsDir2TextTxt, + "app2/app2app3/dirs/text.txt": app2App2app3DirsTextTxt, + "app2/app2app3/index.html": app2App2app3IndexHtml, + "app2/index.html": app2IndexHtml, + "app2/mydir/text.txt": app2MydirTextTxt, + "css/main.css": cssMainCss, + "favicon.ico": faviconIco, + "index.html": indexHtml, + "js/main.js": jsMainJs, + "js/main.js.map": jsMainJsMap, } // AssetDir returns the file names below a certain @@ -424,38 +523,36 @@ type bintree struct { } var _bintree = &bintree{nil, map[string]*bintree{ - "assets": {nil, map[string]*bintree{ - "app2": {nil, map[string]*bintree{ - "app2app3": {nil, map[string]*bintree{ - "css": {nil, map[string]*bintree{ - "main.css": {assetsApp2App2app3CssMainCss, map[string]*bintree{}}, + "app2": {nil, map[string]*bintree{ + "app2app3": {nil, map[string]*bintree{ + "css": {nil, map[string]*bintree{ + "main.css": {app2App2app3CssMainCss, map[string]*bintree{}}, + }}, + "dirs": {nil, map[string]*bintree{ + "dir1": {nil, map[string]*bintree{ + "text.txt": {app2App2app3DirsDir1TextTxt, map[string]*bintree{}}, }}, - "dirs": {nil, map[string]*bintree{ - "dir1": {nil, map[string]*bintree{ - "text.txt": {assetsApp2App2app3DirsDir1TextTxt, map[string]*bintree{}}, - }}, - "dir2": {nil, map[string]*bintree{ - "text.txt": {assetsApp2App2app3DirsDir2TextTxt, map[string]*bintree{}}, - }}, - "text.txt": {assetsApp2App2app3DirsTextTxt, map[string]*bintree{}}, + "dir2": {nil, map[string]*bintree{ + "text.txt": {app2App2app3DirsDir2TextTxt, map[string]*bintree{}}, }}, - "index.html": {assetsApp2App2app3IndexHtml, map[string]*bintree{}}, + "text.txt": {app2App2app3DirsTextTxt, map[string]*bintree{}}, }}, - "index.html": {assetsApp2IndexHtml, map[string]*bintree{}}, - "mydir": {nil, map[string]*bintree{ - "text.txt": {assetsApp2MydirTextTxt, map[string]*bintree{}}, - }}, - }}, - "css": {nil, map[string]*bintree{ - "main.css": {assetsCssMainCss, map[string]*bintree{}}, + "index.html": {app2App2app3IndexHtml, map[string]*bintree{}}, }}, - "favicon.ico": {assetsFaviconIco, map[string]*bintree{}}, - "index.html": {assetsIndexHtml, map[string]*bintree{}}, - "js": {nil, map[string]*bintree{ - "main.js": {assetsJsMainJs, map[string]*bintree{}}, - "main.js.map": {assetsJsMainJsMap, map[string]*bintree{}}, + "index.html": {app2IndexHtml, map[string]*bintree{}}, + "mydir": {nil, map[string]*bintree{ + "text.txt": {app2MydirTextTxt, map[string]*bintree{}}, }}, }}, + "css": {nil, map[string]*bintree{ + "main.css": {cssMainCss, map[string]*bintree{}}, + }}, + "favicon.ico": {faviconIco, map[string]*bintree{}}, + "index.html": {indexHtml, map[string]*bintree{}}, + "js": {nil, map[string]*bintree{ + "main.js": {jsMainJs, map[string]*bintree{}}, + "main.js.map": {jsMainJsMap, map[string]*bintree{}}, + }}, }} // RestoreAsset restores an asset under the given directory diff --git a/_examples/embedded/main.go b/_examples/embedded/main.go index d7f1a2b..3a127a2 100644 --- a/_examples/embedded/main.go +++ b/_examples/embedded/main.go @@ -9,11 +9,11 @@ import ( ) // Follow the steps below: -// $ go get -u github.com/go-bindata/go-bindata/... +// $ go get -u github.com/go-bindata/go-bindata/v3/go-bindata // -// $ go-bindata -nomemcopy -prefix "../basic/" ../basic/assets/... +// $ go-bindata -fs -nomemcopy -prefix "../basic/assets" ../basic/assets/... // # OR if the ./assets directory was inside this example foder: -// # go-bindata -nomemcopy ./assets/... +// # go-bindata -fs -nomemcopy -prefix "assets" ./assets/... // // $ go run . // Physical files are not used, you can delete the "assets" folder and run the example. @@ -32,10 +32,10 @@ var opts = httpfs.Options{ } func main() { - fileSystem := httpfs.EmbeddedDir("./assets", Asset, AssetInfo, AssetNames) + fileSystem := AssetFile() + // with (compressed) cache: + // fileSystem := httpfs.MustCache(fileSystem, httpfs.DefaultCacheOptions) fileServer := httpfs.FileServer(fileSystem, opts) - // fileServer = http.StripPrefix("/public/", fileServer) - // http.Handle("/public/", fileServer) http.Handle("/", fileServer) log.Println("Server started at: https://127.0.0.1") diff --git a/cache.go b/cache.go new file mode 100644 index 0000000..3cc57c0 --- /dev/null +++ b/cache.go @@ -0,0 +1,426 @@ +package httpfs + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" + "os" + "path" + "path/filepath" + "regexp" + "sort" + "strings" + "time" + + "github.com/kataras/compress" +) + +// CacheOptions holds the options for the cached file system. +// See `Cache` package-level function. +type CacheOptions struct { + CompressMinSize int64 + CompressIgnore *regexp.Regexp + Encodings []string +} + +// DefaultCacheOptions holds the recommended settings +// for `CacheOptions` to pass on `Cache` function. +var DefaultCacheOptions = CacheOptions{ + CompressMinSize: 300 * B, // Another good value is 1400. + // .pdf, .jpg, .jpeg, .gif, .png, .tif, .tiff + CompressIgnore: regexp.MustCompile("((.*).pdf|(.*).jpg|(.*).jpeg|(.*).gif|(.*).tif|(.*).tiff)$"), + // gzip, deflate, br(brotli), snappy + Encodings: compress.DefaultOffers, +} + +// MustCache same as `Cache` but it panics on init errors. +func MustCache(fs http.FileSystem, options CacheOptions) http.FileSystem { + c, err := Cache(fs, options) + if err != nil { + panic(err) + } + + return c +} + +// Cache returns a http.FileSystem which serves in-memory cached (compressed) files. +func Cache(fs http.FileSystem, options CacheOptions) (http.FileSystem, error) { + names, err := findNames(fs, "/") + if err != nil { + return fs, err + } + + sort.Slice(names, func(i, j int) bool { + return strings.Count(names[j], "/") > strings.Count(names[i], "/") + }) + + dirs, err := findDirs(fs, names) + if err != nil { + return fs, err + } + + files, err := cacheFiles(fs, names, + options.Encodings, options.CompressMinSize, options.CompressIgnore) + if err != nil { + return fs, err + } + + c := &cacheFS{dirs: dirs, files: files, algs: options.Encodings} + return c, nil +} + +type cacheFS struct { + dirs map[string]*dir + files fileMap + algs []string +} + +var _ http.FileSystem = (*cacheFS)(nil) + +// Open returns the http.File based on "name". +// If file, it always returns a cached file of uncompressed data. +// See `Ropen` too. +func (c *cacheFS) Open(name string) (http.File, error) { + // we always fetch with the sep, + // as http requests will do, + // and the filename's info.Name() is always base + // and without separator prefix + // (keep note, we need that fileInfo + // wrapper because go-bindata's Name originally + // returns the fullname while the http.Dir returns the basename). + if name == "" || name[0] != '/' { + name = "/" + name + } + + if d, ok := c.dirs[name]; ok { + return d, nil + } + + if f, ok := c.files[name]; ok { + return f.Get("") + } + + return nil, os.ErrNotExist +} + +// Ropen returns the http.File based on "name". +// If file, it negotiates the content encoding, +// based on the given algorithms, and +// returns the cached file with compressed data, +// if the encoding was empty then it +// returns the cached file with its original, uncompressed data. +// +// A check of `GetEncoding(file)` is required to set +// response headers. +// +// Note: We don't require a response writer to set the headers +// because the caller of this method may stop the operation +// before file's contents are written to the client. +func (c *cacheFS) Ropen(name string, r *http.Request) (http.File, error) { + if name == "" || name[0] != '/' { + name = "/" + name + } + + if d, ok := c.dirs[name]; ok { + return d, nil + } + + if f, ok := c.files[name]; ok { + encoding, _ := compress.GetEncoding(r, c.algs) + return f.Get(encoding) + } + + return nil, os.ErrNotExist +} + +// GetEncoding returns the encoding of an http.File. +// If the "f" file was created by a `Cache` call then +// it returns the content encoding that this file was cached with. +// It returns empty string for files that +// were too small or ignored to be compressed. +// +// It also reports whether the "f" is a cached file or not. +func GetEncoding(f http.File) (string, bool) { + if f == nil { + return "", false + } + + ff, ok := f.(*file) + if !ok { + return "", false + } + + return ff.alg, true +} + +// type fileMap map[string] /* path */ map[string] /*compression alg or empty for original */ []byte /*contents */ +type fileMap map[string]*file + +func cacheFiles(fs http.FileSystem, names []string, compressAlgs []string, compressMinSize int64, compressIgnore *regexp.Regexp) (fileMap, error) { + list := make(fileMap, len(names)) + + for _, name := range names { + f, err := fs.Open(name) + if err != nil { + return nil, err + } + + inf, err := f.Stat() + if err != nil { + f.Close() + return nil, err + } + + fi := newFileInfo(path.Base(name), inf.Mode(), inf.ModTime()) + + contents, err := ioutil.ReadAll(f) + f.Close() + if err != nil { + return nil, err + } + + algs := make(map[string][]byte, len(compressAlgs)+1) + algs[""] = contents // original contents. + + list[name] = newFile(name, fi, algs) + if compressMinSize > 0 && compressMinSize > int64(len(contents)) { + continue + } + + if compressIgnore != nil && compressIgnore.MatchString(name) { + continue + } + + buf := new(bytes.Buffer) + for _, alg := range compressAlgs { + if alg == "brotli" { + alg = "br" + } + + w, err := compress.NewWriter(buf, strings.ToLower(alg), -1) + if err != nil { + return nil, err + } + _, err = w.Write(contents) + w.Close() + if err != nil { + return nil, err + } + + bs := buf.Bytes() + dest := make([]byte, len(bs)) + copy(dest, bs) + algs[alg] = dest + + buf.Reset() + } + } + + return list, nil +} + +type cacheStoreFile interface { + Get(compressionAlgorithm string) (http.File, error) +} + +type file struct { + io.ReadSeeker // nil on cache store and filled on file Get. + algs map[string][]byte // non empty for store and nil for files. + alg string // empty for cache store, filled with the compression algorithm of this file (useful to decompress). + name string + baseName string + info os.FileInfo +} + +var _ http.File = (*file)(nil) +var _ cacheStoreFile = (*file)(nil) + +func newFile(name string, fi os.FileInfo, algs map[string][]byte) *file { + return &file{ + name: name, + baseName: path.Base(name), + info: fi, + algs: algs, + } +} + +func (f *file) Close() error { return nil } +func (f *file) Readdir(count int) ([]os.FileInfo, error) { return nil, os.ErrNotExist } +func (f *file) Stat() (os.FileInfo, error) { return f.info, nil } + +// Get returns a new http.File to be served. +// Caller should check if a specific http.File has this method as well. +func (f *file) Get(alg string) (http.File, error) { + // The "alg" can be empty for non-compressed file contents. + // We don't need a new structure. + + if contents, ok := f.algs[alg]; ok { + return &file{ + name: f.name, + baseName: f.baseName, + info: f.info, + alg: alg, + ReadSeeker: bytes.NewReader(contents), + }, nil + } + + // When client accept compression but cached contents are not compressed, + // e.g. file too small or ignored one. + return f.Get("") +} + +/* +Remember, the decompressed data already exist in the empty Get(""). +We do NOT need this (unless requested): +func (f *file) Decompress(w io.Writer) (int64, error) { + if f.alg == "" { + return 0, os.ErrNotExist + } + + cr, err := compress.NewReader(f, f.alg) + if err != nil { + return 0, err + } + n, err := io.Copy(w, cr) + cr.Close() + return n, err +} +*/ + +func findNames(fs http.FileSystem, name string) ([]string, error) { + f, err := fs.Open(name) + if err != nil { + return nil, err + } + defer f.Close() + + fi, err := f.Stat() + if err != nil { + return nil, err + } + + if !fi.IsDir() { + return []string{name}, nil + } + + fileinfos, err := f.Readdir(-1) + if err != nil { + return nil, err + } + + files := make([]string, 0) + + for _, info := range fileinfos { + // Note: + // go-bindata has absolute names with os.Separator, + // http.Dir the basename. + filename := path.Base(filepath.ToSlash(info.Name())) + fullname := path.Join(name, filename) + if fullname == name { // prevent looping through itself when fs is cacheFS. + continue + } + rfiles, err := findNames(fs, fullname) + if err != nil { + return nil, err + } + + files = append(files, rfiles...) + } + + return files, nil +} + +type fileInfo struct { + baseName string + modTime time.Time + isDir bool + mode os.FileMode +} + +var _ os.FileInfo = (*fileInfo)(nil) + +func newFileInfo(baseName string, mode os.FileMode, modTime time.Time) *fileInfo { + return &fileInfo{ + baseName: baseName, + modTime: modTime, + mode: mode, + isDir: mode == os.ModeDir, + } +} + +func (fi *fileInfo) Close() error { return nil } +func (fi *fileInfo) Name() string { return fi.baseName } +func (fi *fileInfo) Mode() os.FileMode { return fi.mode } +func (fi *fileInfo) ModTime() time.Time { return fi.modTime } +func (fi *fileInfo) IsDir() bool { return fi.isDir } +func (fi *fileInfo) Size() int64 { return 0 } +func (fi *fileInfo) Sys() interface{} { return fi } + +type dir struct { + os.FileInfo // *fileInfo + io.ReadSeeker // nil + + name string // fullname, for any case. + baseName string + children []os.FileInfo // a slice of *fileInfo +} + +var _ os.FileInfo = (*dir)(nil) +var _ http.File = (*dir)(nil) + +func (d *dir) Close() error { return nil } +func (d *dir) Name() string { return d.baseName } +func (d *dir) Stat() (os.FileInfo, error) { return d.FileInfo, nil } + +func (d *dir) Readdir(count int) ([]os.FileInfo, error) { + return d.children, nil +} + +func newDir(fi os.FileInfo, fullname string) *dir { + baseName := path.Base(fullname) + return &dir{ + FileInfo: newFileInfo(baseName, os.ModeDir, fi.ModTime()), + name: fullname, + baseName: baseName, + } +} + +var _ http.File = (*dir)(nil) + +// returns unorderded map of directories both reclusive and flat. +func findDirs(fs http.FileSystem, names []string) (map[string]*dir, error) { + dirs := make(map[string]*dir, 0) + + for _, name := range names { + f, err := fs.Open(name) + if err != nil { + return nil, err + } + inf, err := f.Stat() + if err != nil { + return nil, err + } + + dirName := path.Dir(name) + d, ok := dirs[dirName] + if !ok { + fi := newFileInfo(path.Base(dirName), os.ModeDir, inf.ModTime()) + d = newDir(fi, dirName) + dirs[dirName] = d + } + + fi := newFileInfo(path.Base(name), inf.Mode(), inf.ModTime()) + + // Add the directory file info (=this dir) to the parent one, + // so `ShowList` can render sub-directories of this dir. + parentName := path.Dir(dirName) + if parent, hasParent := dirs[parentName]; hasParent { + parent.children = append(parent.children, d) + } + + d.children = append(d.children, fi) + } + + return dirs, nil +} diff --git a/embedded.go b/embedded.go deleted file mode 100644 index a02a4da..0000000 --- a/embedded.go +++ /dev/null @@ -1,216 +0,0 @@ -package httpfs - -import ( - "bytes" - "fmt" - "io" - "net/http" - "os" - "path" - "path/filepath" - "sort" - "strings" - "time" -) - -// EmbeddedDir returns a new `http.FileSystem` -// for an embedded directory generated by `go-bindata`. -// -// It accepts the virtual directory following -// by the generated functions for accessing embedded files. -// -// Usage: -// fileSystem := EmbeddedDir("./assets", Asset, AssetInfo, AssetNames) -// fileServer := FileServer(fileSystem, DefaultOptions) -func EmbeddedDir(vdir string, - assetFn func(name string) ([]byte, error), - assetInfoFn func(name string) (os.FileInfo, error), - assetNamesFn func() []string) http.FileSystem { - - if vdir != "" { - if len(vdir) > 1 { - if vdir[0] == '.' && len(vdir) > 1 { - vdir = vdir[1:] - } - - if vdir[0] == '/' || vdir[0] == os.PathSeparator { - vdir = vdir[1:] - } - } - - if trailingSlashIdx := len(vdir) - 1; vdir[trailingSlashIdx] == '/' { - vdir = vdir[0:trailingSlashIdx] - } - } - - // Select only the paths that we care; - // that have prefix of the directory and - // skip any unnecessary the end-dev or the 3rd party tool may set. - var names []string - for _, name := range assetNamesFn() { - if !strings.HasPrefix(name, vdir) { - continue - } - - names = append(names, filepath.ToSlash(name)) - } - - if len(names) == 0 { - panic("FileServer: zero embedded files") - } - - asset := assetFn - // make .Name() infos like http.Dir (base names instead of full names). - assetInfo := func(name string) (os.FileInfo, error) { - info, err := assetInfoFn(name) - if err != nil { - return nil, err - } - return &embeddedBaseFileInfo{ - baseName: path.Base(info.Name()), - FileInfo: info, - }, nil - } - - dirNames := make(map[string]*embeddedDir) - - // sort filenames by smaller path. - sort.Slice(names, func(i, j int) bool { - return strings.Count(names[j], "/") > strings.Count(names[i], "/") - }) - - for _, name := range names { - dirName := path.Dir(name) - d, ok := dirNames[dirName] - - if !ok { - d = &embeddedDir{ - name: dirName, - baseName: path.Base(dirName), - modTimeUnix: time.Now().Unix(), - } - dirNames[dirName] = d - } - - info, err := assetInfo(name) - if err != nil { - panic(fmt.Sprintf("FileServer: report as bug: file info: %s not found in: %s", name, dirName)) - } - - // Add the directory file info (=this dir) to the parent one, - // so `ShowList` can render sub-directories of this dir. - if parent, hasParent := dirNames[path.Dir(dirName)]; hasParent { - parent.list = append(parent.list, d) - } - - f := &embeddedBaseFileInfo{path.Base(name), info} - d.list = append(d.list, f) - } - - return &embeddedFileSystem{ - vdir: vdir, - dirNames: dirNames, - - asset: asset, - assetInfo: assetInfo, - } -} - -type embeddedFileSystem struct { - vdir string - // Go-bindata tools don't give that info, - // so we initialize it in order to support - // `ShowList` on embedded files as well. - dirNames map[string]*embeddedDir - - asset func(name string) ([]byte, error) - assetInfo func(name string) (os.FileInfo, error) -} - -var _ http.FileSystem = (*embeddedFileSystem)(nil) - -// Open implements FileSystem using os.Open, opening files for reading rooted -// and relative to the virtual directory. -func (fs *embeddedFileSystem) Open(name string) (http.File, error) { - if name != "/" { - // http://localhost:8080/app2/app2app3/dirs/ - // = http://localhost:8080/app2/app2app3/dirs - name = strings.TrimSuffix(name, "/") - } - - name = path.Join(fs.vdir, path.Clean("/"+name)) - if d, ok := fs.dirNames[name]; ok { - return d, nil - } - - info, err := fs.assetInfo(name) - if err != nil { - return nil, err - } - b, err := fs.asset(name) - if err != nil { - return nil, err - } - - return &embeddedFile{ - FileInfo: info, - ReadSeeker: bytes.NewReader(b), - }, nil -} - -type embeddedFile struct { - os.FileInfo - io.ReadSeeker -} - -var _ http.File = (*embeddedFile)(nil) - -func (f *embeddedFile) Close() error { - return nil -} - -func (f *embeddedFile) Readdir(count int) ([]os.FileInfo, error) { - return nil, nil // should never happen, read directories is done by `embeddedDir`. -} - -func (f *embeddedFile) Stat() (os.FileInfo, error) { - return f.FileInfo, nil -} - -type embeddedBaseFileInfo struct { - baseName string - os.FileInfo -} - -func (info *embeddedBaseFileInfo) Name() string { - return info.baseName - // return path.Base(info.Name()) -} - -type embeddedDir struct { - name string - baseName string - modTimeUnix int64 - list []os.FileInfo - *bytes.Reader // never used, will always be nil. -} - -var _ http.File = (*embeddedDir)(nil) - -func (f *embeddedDir) Close() error { return nil } -func (f *embeddedDir) Name() string { return f.baseName } -func (f *embeddedDir) Size() int64 { return 0 } -func (f *embeddedDir) Mode() os.FileMode { return os.ModeDir } -func (f *embeddedDir) ModTime() time.Time { return time.Unix(f.modTimeUnix, 0) } -func (f *embeddedDir) IsDir() bool { return true } -func (f *embeddedDir) Sys() interface{} { return f } -func (f *embeddedDir) Stat() (os.FileInfo, error) { return f, nil } - -func (f *embeddedDir) Readdir(count int) ([]os.FileInfo, error) { - if count != -1 { - // this should never happen, show dirs is already checked on the handler level before this call. - return nil, nil - } - - return f.list, nil -} diff --git a/go.mod b/go.mod index c1292dc..3b31f63 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/kataras/httpfs go 1.14 require ( - github.com/kataras/compress v0.0.4 + github.com/kataras/compress v0.0.5 + github.com/klauspost/compress v1.10.10 golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e ) diff --git a/go.sum b/go.sum index c2b7c0f..1aa1013 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/kataras/compress v0.0.4 h1:y9Er6o8V5//oc/WC4HuN7PemiJt69CrYdGX27Thc5bk= -github.com/kataras/compress v0.0.4/go.mod h1:xru59oerl89gl/p3nzbmGR12C9+XMdlZ8jNF43XyEPA= +github.com/kataras/compress v0.0.5 h1:5eB7hq9XQOInw0jUVIqKL634PtjrPz7WhiRz2NR2SwM= +github.com/kataras/compress v0.0.5/go.mod h1:xru59oerl89gl/p3nzbmGR12C9+XMdlZ8jNF43XyEPA= github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I= github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= diff --git a/httpfs.go b/httpfs.go index 8ac407d..5fe8266 100644 --- a/httpfs.go +++ b/httpfs.go @@ -44,11 +44,19 @@ func FileServer(fs http.FileSystem, options Options) http.Handler { options.PushTargets[path] = filenames } + open := func(name string, _ *http.Request) (http.File, error) { + return fs.Open(name) + } + + if cfs, ok := fs.(*cacheFS); ok { + open = cfs.Ropen + } + handler := func(w http.ResponseWriter, r *http.Request) { name := prefix(r.URL.Path, "/") r.URL.Path = name - f, err := fs.Open(name) + f, err := open(name, r) if err != nil { w.WriteHeader(http.StatusNotFound) return @@ -62,16 +70,16 @@ func FileServer(fs http.FileSystem, options Options) http.Handler { } indexFound := false - var indexDirectory http.File + // var indexDirectory http.File // use contents of index.html for directory, if present if info.IsDir() && options.IndexName != "" { index := strings.TrimSuffix(name, "/") + options.IndexName - fIndex, err := fs.Open(index) + fIndex, err := open(index, r) if err == nil { defer fIndex.Close() infoIndex, err := fIndex.Stat() if err == nil { - indexDirectory = f + // indexDirectory = f indexFound = true info = infoIndex f = fIndex @@ -109,7 +117,7 @@ func FileServer(fs http.FileSystem, options Options) http.Handler { } if options.Allow != nil { - if !options.Allow(w, r, info.Name()) { // status code should be written. + if !options.Allow(w, r, name) { // status code should be written. return } } @@ -135,7 +143,18 @@ func FileServer(fs http.FileSystem, options Options) http.Handler { } } - if options.Compress { + // the encoding saved from the negotiation. + encoding, isCached := GetEncoding(f) + if isCached { + // if it's cached and its settings didnt allow this file to be compressed + // then don't try to compress it on the fly, even if the options.Compress was set to true. + if encoding != "" { + // Set the response header we need, the data are already compressed. + // w.Header().Set(compress.ContentEncodingHeaderKey, encoding) + // w.Header().Set(compress.VaryHeaderKey, compress.AcceptEncodingHeaderKey) + compress.AddCompressHeaders(w.Header(), encoding) + } + } else if options.Compress { cr, err := compress.NewResponseWriter(w, r, -1) if err == nil { defer cr.Close() @@ -158,7 +177,12 @@ func FileServer(fs http.FileSystem, options Options) http.Handler { indexAsset = path.Join(r.RequestURI, indexAsset) } - if err = pusher.Push(indexAsset, nil); err != nil { + var pushOpts *http.PushOptions + if encoding != "" { + pushOpts = &http.PushOptions{Header: r.Header} + } + + if err = pusher.Push(indexAsset, pushOpts); err != nil { break } } @@ -168,19 +192,30 @@ func FileServer(fs http.FileSystem, options Options) http.Handler { if regex, ok := options.PushTargetsRegexp[r.URL.Path]; ok { if pusher, ok := w.(http.Pusher); ok { prefixURL := strings.TrimSuffix(r.RequestURI, name) + if prefixURL == "" { + prefixURL = "/" + } - for _, indexAsset := range getFilenamesRecursively(fs, indexDirectory, name) { - // it's an index file, do not pushed that. - if strings.HasSuffix("/"+indexAsset, options.IndexName) { - continue - } + names, err := findNames(fs, name) + if err == nil { + for _, indexAsset := range names { + // it's an index file, do not pushed that. + if strings.HasSuffix("/"+indexAsset, options.IndexName) { + continue + } - // match using relative path (without the first '/' slash) - // to keep consistency between the `PushTargets` behavior - if regex.MatchString(indexAsset) { - // println("Regex Matched: " + indexAsset) - if err = pusher.Push(path.Join(prefixURL, indexAsset), nil); err != nil { - break + // match using relative path (without the first '/' slash) + // to keep consistency between the `PushTargets` behavior + if regex.MatchString(indexAsset) { + // println("Pushing: " + path.Join(prefixURL, indexAsset)) + var pushOpts *http.PushOptions + if encoding != "" { + pushOpts = &http.PushOptions{Header: r.Header} + } + + if err = pusher.Push(path.Join(prefixURL, indexAsset), pushOpts); err != nil { + break + } } } } @@ -194,38 +229,6 @@ func FileServer(fs http.FileSystem, options Options) http.Handler { return http.HandlerFunc(handler) } -func getFilenamesRecursively(fs http.FileSystem, f http.File, parent string) []string { - defer f.Close() - info, err := f.Stat() - if err != nil { - return nil - } - - var filenames []string - - if info.IsDir() { - fileinfos, err := f.Readdir(-1) - if err != nil { - return nil - } - - for _, fileinfo := range fileinfos { - fullname := path.Join(parent, fileinfo.Name()) - ff, err := fs.Open(fullname) - if err != nil { - return nil - } - - filenames = append(filenames, getFilenamesRecursively(fs, ff, fullname)...) - } - - return filenames - } - - filenames = append(filenames, path.Dir(path.Join(parent, info.Name()))) - return filenames -} - // rateReadSeeker is a io.ReadSeeker that is rate limited by // the given token bucket. Each token in the bucket // represents one byte. See "golang.org/x/time/rate" package. diff --git a/options.go b/options.go index 8b338cb..5249da6 100644 --- a/options.go +++ b/options.go @@ -9,6 +9,7 @@ import ( "net/url" "os" "path" + "path/filepath" "regexp" "sort" "strings" @@ -117,7 +118,7 @@ func DirList(w http.ResponseWriter, r *http.Request, dirOptions Options, dirName } for _, d := range dirs { - name := d.Name() + name := path.Base(filepath.ToSlash(d.Name())) // dirName is not the full path. // fixes the "app2/app2app3/mydir" @@ -136,7 +137,7 @@ func DirList(w http.ResponseWriter, r *http.Request, dirOptions Options, dirName downloadAttr = " download" // fixes chrome Resource interpreted, other browsers will just ignore this attribute. } - viewName := name + viewName := path.Base(name) if d.IsDir() { viewName += "/" } @@ -215,9 +216,9 @@ func DirListRich(options DirListRichOptions) DirListFunc { } for _, d := range dirs { - name := d.Name() - + name := path.Base(filepath.ToSlash(d.Name())) upath := "" + if !strings.HasSuffix(r.URL.Path, "/") && dirName != "" { upath = "./" + path.Base(dirName) + "/" + name } else { @@ -246,23 +247,26 @@ func DirListRich(options DirListRichOptions) DirListFunc { } } +// FormatBytes returns the string representation of "b" length bytes. +func FormatBytes(b int64) string { + const unit = 1000 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", + float64(b)/float64(div), "kMGTPE"[exp]) +} + // DirListRichTemplate is the html template the `DirListRich` function is using to render // the directories and files. var DirListRichTemplate = template.Must(template.New(""). Funcs(template.FuncMap{ - "formatBytes": func(b int64) string { - const unit = 1000 - if b < unit { - return fmt.Sprintf("%d B", b) - } - div, exp := int64(unit), 0 - for n := b / unit; n >= unit; n /= unit { - div *= unit - exp++ - } - return fmt.Sprintf("%.1f %cB", - float64(b)/float64(div), "kMGTPE"[exp]) - }, + "formatBytes": FormatBytes, }).Parse(`