switch assets to embed

This commit is contained in:
Nazar Kanaev 2021-02-26 12:33:35 +00:00
parent a3146926b1
commit 121101de9d
8 changed files with 79 additions and 178 deletions

45
assets/assets.go Normal file
View File

@ -0,0 +1,45 @@
package assets
import (
"embed"
"html/template"
"io"
"io/ioutil"
"io/fs"
"os"
)
type assetsfs struct {
embedded *embed.FS
templates map[string]*template.Template
}
var FS assetsfs
func (afs assetsfs) Open(name string) (fs.File, error) {
if afs.embedded != nil {
return afs.embedded.Open(name)
}
return os.DirFS("assets").Open(name)
}
func Render(path string, writer io.Writer, data interface{}) {
var tmpl *template.Template
tmpl, found := FS.templates[path]
if !found {
tmpl = template.Must(template.New(path).Delims("{%", "%}").Funcs(template.FuncMap{
"inline": func(svg string) template.HTML {
svgfile, _ := FS.Open("graphicarts/" + svg)
content, _ := ioutil.ReadAll(svgfile)
svgfile.Close()
return template.HTML(content)
},
}).ParseFS(FS, path))
FS.templates[path] = tmpl
}
tmpl.Execute(writer, data)
}
func init() {
FS.templates = make(map[string]*template.Template)
}

15
assets/assetsfs.go Normal file
View File

@ -0,0 +1,15 @@
// +build release
package assets
import "embed"
//go:embed *.html
//go:embed graphicarts
//go:embed javascripts
//go:embed stylesheets
var embedded embed.FS
func init() {
FS.embedded = &embedded
}

View File

@ -1,4 +1,4 @@
Install `Go >= 1.14` and `gcc`. Get the source code: Install `Go >= 1.16` and `gcc`. Get the source code:
git clone https://github.com/nkanaev/yarr.git git clone https://github.com/nkanaev/yarr.git
git clone https://github.com/nkanaev/gofeed.git yarr/gofeed git clone https://github.com/nkanaev/gofeed.git yarr/gofeed

2
go.mod
View File

@ -1,6 +1,6 @@
module github.com/nkanaev/yarr module github.com/nkanaev/yarr
go 1.14 go 1.16
require ( require (
github.com/PuerkitoBio/goquery v1.5.1 github.com/PuerkitoBio/goquery v1.5.1

View File

@ -1,24 +1,21 @@
VERSION=1.3 VERSION=1.3
GITHASH=$(shell git rev-parse --short=8 HEAD) GITHASH=$(shell git rev-parse --short=8 HEAD)
ASSETS = assets/javascripts/* assets/stylesheets/* assets/graphicarts/* assets/*.html
CGO_ENABLED=1 CGO_ENABLED=1
GO_LDFLAGS = -s -w GO_LDFLAGS = -s -w
GO_LDFLAGS := $(GO_LDFLAGS) -X 'main.Version=$(VERSION)' -X 'main.GitHash=$(GITHASH)' GO_LDFLAGS := $(GO_LDFLAGS) -X 'main.Version=$(VERSION)' -X 'main.GitHash=$(GITHASH)'
default: bundle default: build_default
server/assets.go: $(ASSETS) server/assets.go: $(ASSETS)
go run scripts/bundle_assets.go >/dev/null go run scripts/bundle_assets.go >/dev/null
bundle: server/assets.go build_default:
build_default: bundle
mkdir -p _output mkdir -p _output
go build -tags "sqlite_foreign_keys release" -ldflags="$(GO_LDFLAGS)" -o _output/yarr main.go go build -tags "sqlite_foreign_keys release" -ldflags="$(GO_LDFLAGS)" -o _output/yarr main.go
build_macos: bundle build_macos:
set GOOS=darwin set GOOS=darwin
set GOARCH=amd64 set GOARCH=amd64
mkdir -p _output/macos mkdir -p _output/macos
@ -26,13 +23,13 @@ build_macos: bundle
cp artwork/icon.png _output/macos/icon.png cp artwork/icon.png _output/macos/icon.png
go run scripts/package_macos.go -outdir _output/macos -version "$(VERSION)" go run scripts/package_macos.go -outdir _output/macos -version "$(VERSION)"
build_linux: bundle build_linux:
set GOOS=linux set GOOS=linux
set GOARCH=386 set GOARCH=386
mkdir -p _output/linux mkdir -p _output/linux
go build -tags "sqlite_foreign_keys release linux" -ldflags="$(GO_LDFLAGS)" -o _output/linux/yarr main.go go build -tags "sqlite_foreign_keys release linux" -ldflags="$(GO_LDFLAGS)" -o _output/linux/yarr main.go
build_windows: bundle build_windows:
set GOOS=windows set GOOS=windows
set GOARCH=386 set GOARCH=386
mkdir -p _output/windows mkdir -p _output/windows

View File

@ -1,97 +0,0 @@
package main
import (
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/base64"
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"text/template"
htemplate "html/template"
)
var code_template = `// +build release
// autogenerated. do not edit!
package server
var assets_bundle = map[string]asset{
{{- range .}}
"{{.Name}}": {etag: "{{.Etag}}", body: "{{.Body}}"},
{{- end }}
}
func init() {
assets = assets_bundle
}
`
type asset struct {
Name, Etag, Body string
}
func shasum(b []byte) string {
h := sha256.New()
h.Write(b)
return fmt.Sprintf("%x", h.Sum(nil))[:16]
}
func encode(b []byte) string {
var buf bytes.Buffer
zw := gzip.NewWriter(&buf)
zw.Write(b)
zw.Close()
return base64.StdEncoding.EncodeToString(buf.Bytes())
}
func main() {
assets := make([]asset, 0)
filepatterns := []string{
"assets/login.html",
"assets/graphicarts/*.svg",
"assets/graphicarts/*.png",
"assets/javascripts/*.js",
"assets/stylesheets/*.css",
"assets/stylesheets/*.map",
}
fmt.Printf("%8s %8s %s\n", "original", "encoded", "filename")
for _, pattern := range filepatterns {
filenames, _ := filepath.Glob(pattern)
for _, filename := range filenames {
content, _ := ioutil.ReadFile(filename)
assets = append(assets, asset{
Name: strings.TrimPrefix(strings.ReplaceAll(filename, "\\", "/"), "assets/"),
Etag: shasum(content),
Body: encode(content),
})
fmt.Printf(
"%8d %8d %s\n",
len(content),
len(assets[len(assets)-1].Body),
filename,
)
}
}
var indexbuf bytes.Buffer
htemplate.Must(htemplate.New("index.html").Delims("{%", "%}").Funcs(htemplate.FuncMap{
"inline": func(svg string) htemplate.HTML {
content, _ := ioutil.ReadFile("assets/graphicarts/" + svg)
return htemplate.HTML(content)
},
}).ParseFiles("assets/index.html")).Execute(&indexbuf, nil)
indexcontent := indexbuf.Bytes()
assets = append(assets, asset{
Name: "index.html",
Etag: shasum(indexcontent),
Body: encode(indexcontent),
})
var buf bytes.Buffer
template := template.Must(template.New("code").Parse(code_template))
template.Execute(&buf, assets)
ioutil.WriteFile("server/assets.go", buf.Bytes(), 0644)
}

View File

@ -1,26 +1,22 @@
package server package server
import ( import (
"bytes"
"compress/gzip"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/nkanaev/yarr/storage" "github.com/nkanaev/yarr/storage"
"github.com/nkanaev/yarr/assets"
"html" "html"
"html/template"
"io"
"io/ioutil" "io/ioutil"
"math" "math"
"mime"
"net/http" "net/http"
"os"
"path/filepath"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
) )
// TODO: gzip?
var StaticHandler = http.StripPrefix("/static/", http.FileServer(http.FS(assets.FS))).ServeHTTP
var routes []Route = []Route{ var routes []Route = []Route{
p("/", IndexHandler).ManualAuth(), p("/", IndexHandler).ManualAuth(),
p("/static/*path", StaticHandler).ManualAuth(), p("/static/*path", StaticHandler).ManualAuth(),
@ -43,36 +39,6 @@ var routes []Route = []Route{
p("/logout", LogoutHandler), p("/logout", LogoutHandler),
} }
type asset struct {
etag string
body string // base64(gzip(content))
gzipped *[]byte
decoded *string
}
func (a *asset) gzip() *[]byte {
if a.gzipped == nil {
gzipped, _ := base64.StdEncoding.DecodeString(a.body)
a.gzipped = &gzipped
}
return a.gzipped
}
func (a *asset) text() *string {
if a.decoded == nil {
gzipped, _ := base64.StdEncoding.DecodeString(a.body)
reader, _ := gzip.NewReader(bytes.NewBuffer(gzipped))
decoded, _ := ioutil.ReadAll(reader)
reader.Close()
decoded_string := string(decoded)
a.decoded = &decoded_string
}
return a.decoded
}
var assets map[string]asset
type FolderCreateForm struct { type FolderCreateForm struct {
Title string `json:"title"` Title string `json:"title"`
} }
@ -104,43 +70,17 @@ func IndexHandler(rw http.ResponseWriter, req *http.Request) {
} }
} }
if assets != nil {
asset := assets["login.html"]
rw.Header().Set("Content-Type", "text/html") rw.Header().Set("Content-Type", "text/html")
rw.Header().Set("Content-Encoding", "gzip") assets.Render("login.html", rw, nil)
rw.Write(*asset.gzip())
return
} else {
f, err := os.Open("assets/login.html")
if err != nil {
handler(req).log.Print(err)
return return
} }
io.Copy(rw, f)
return
}
}
if assets != nil {
asset := assets["index.html"]
rw.Header().Set("Content-Type", "text/html") rw.Header().Set("Content-Type", "text/html")
rw.Header().Set("Content-Encoding", "gzip") assets.Render("index.html", rw, nil)
rw.Write(*asset.gzip())
} else {
t := template.Must(template.New("index.html").Delims("{%", "%}").Funcs(template.FuncMap{
"inline": func(svg string) template.HTML {
content, _ := ioutil.ReadFile("assets/graphicarts/" + svg)
return template.HTML(content)
},
}).ParseFiles("assets/index.html"))
rw.Header().Set("Content-Type", "text/html")
t.Execute(rw, nil)
}
} }
/*
func StaticHandler(rw http.ResponseWriter, req *http.Request) { func StaticHandler(rw http.ResponseWriter, req *http.Request) {
path := Vars(req)["path"] http.StripPrefix("/static/", http.FileServer(http.FS(assets.FS))).ServeHTTP(rw, req)
ctype := mime.TypeByExtension(filepath.Ext(path)) ctype := mime.TypeByExtension(filepath.Ext(path))
if assets != nil { if assets != nil {
@ -164,6 +104,7 @@ func StaticHandler(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Content-Type", ctype) rw.Header().Set("Content-Type", ctype)
io.Copy(rw, f) io.Copy(rw, f)
} }
*/
func StatusHandler(rw http.ResponseWriter, req *http.Request) { func StatusHandler(rw http.ResponseWriter, req *http.Request) {
writeJSON(rw, map[string]interface{}{ writeJSON(rw, map[string]interface{}{

View File

@ -19,7 +19,7 @@ func (r Route) ManualAuth() Route {
return r return r
} }
func p(path string, handler func(http.ResponseWriter, *http.Request)) Route { func p(path string, handler http.HandlerFunc) Route {
var urlRegexp string var urlRegexp string
urlRegexp = regexp.MustCompile(`[\*\:]\w+`).ReplaceAllStringFunc(path, func(m string) string { urlRegexp = regexp.MustCompile(`[\*\:]\w+`).ReplaceAllStringFunc(path, func(m string) string {
if m[0:1] == `*` { if m[0:1] == `*` {