114 lines
2.8 KiB
Go
114 lines
2.8 KiB
Go
package kjv
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Everything the program serves or reads besides the Bible itself (pages,
|
|
// scripts, styles, the 1828 dictionary, word lists) lives in the assets table
|
|
// of the companion database, so the program and the database are the only two
|
|
// files that need to be distributed.
|
|
|
|
var assetMIME = map[string]string{
|
|
".html": "text/html; charset=utf-8",
|
|
".css": "text/css; charset=utf-8",
|
|
".js": "application/javascript; charset=utf-8",
|
|
".json": "application/json",
|
|
".svg": "image/svg+xml",
|
|
".dic": "text/plain; charset=utf-8",
|
|
".aff": "text/plain; charset=utf-8",
|
|
}
|
|
|
|
func isAssetFile(rel string) bool {
|
|
dir := path.Dir(rel)
|
|
switch {
|
|
case rel == "kjv_fe.html":
|
|
return true
|
|
case dir == "html", dir == "js", dir == "static":
|
|
return true
|
|
case dir == "styles":
|
|
return strings.HasSuffix(rel, ".css")
|
|
case dir == "data":
|
|
ext := path.Ext(rel)
|
|
return ext == ".json" || ext == ".dic" || ext == ".aff"
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ImportAssets replaces the assets table with the files of a source checkout.
|
|
func ImportAssets(dbPath, srcDir string) error {
|
|
db, err := sql.Open("sqlite", "file:"+dbPath+"?_pragma=busy_timeout(5000)")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
tx, err := db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
for _, stmt := range []string{
|
|
"CREATE TABLE IF NOT EXISTS assets (path TEXT PRIMARY KEY, mime TEXT NOT NULL, body BLOB NOT NULL)",
|
|
"DELETE FROM assets",
|
|
} {
|
|
if _, err := tx.Exec(stmt); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
n := 0
|
|
err = filepath.WalkDir(srcDir, func(p string, d fs.DirEntry, err error) error {
|
|
if err != nil || !d.Type().IsRegular() { // no directories, and never follow a symlink out of the tree
|
|
return err
|
|
}
|
|
rel, _ := filepath.Rel(srcDir, p)
|
|
rel = filepath.ToSlash(rel)
|
|
if !isAssetFile(rel) {
|
|
return nil
|
|
}
|
|
body, err := os.ReadFile(p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mime := assetMIME[path.Ext(rel)]
|
|
if mime == "" {
|
|
mime = "application/octet-stream"
|
|
}
|
|
n++
|
|
_, err = tx.Exec("INSERT INTO assets (path, mime, body) VALUES (?, ?, ?)", rel, mime, body)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if n == 0 {
|
|
return fmt.Errorf("no assets found under %s (expected html/, js/, styles/, data/*.json ...)", srcDir)
|
|
}
|
|
fmt.Printf("imported %d assets into %s\n", n, dbPath)
|
|
return tx.Commit()
|
|
}
|
|
|
|
// LoadAssets reads every asset into memory.
|
|
func (s *Store) LoadAssets() (map[string][]byte, error) {
|
|
rows, err := s.db.Query("SELECT path, body FROM assets")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("no assets table (run: kjv-api import-assets <source dir>): %w", err)
|
|
}
|
|
defer rows.Close()
|
|
out := map[string][]byte{}
|
|
for rows.Next() {
|
|
var p string
|
|
var b []byte
|
|
if err := rows.Scan(&p, &b); err != nil {
|
|
return nil, err
|
|
}
|
|
out[p] = b
|
|
}
|
|
return out, rows.Err()
|
|
}
|