clone

a desktop tv and program guide for over-the-air broadcasts: what's on air, what your tuner gets, live tv, recordings

main
airwaves / internal / api / tv.go
tv.go 1.5K
1package api23import (4 "crypto/sha256"5 "encoding/hex"6 "io/fs"7 "log"8 "net/http"9 "strings"10)1112// tvFiles serves the TV interface (frontend/dist) with the prefix /tv13// stripped. Pages, scripts and styles are checked again on every load14// (no-cache, with an ETag, so it's a 304 when unchanged) and a new15// airwavesd shows its own at once; fonts, icons and the vendored libraries16// are kept for a day. There are no directory listings.17func tvFiles(fsys fs.FS) http.Handler {18 etags := map[string]string{}19 err := fs.WalkDir(fsys, ".", func(p string, d fs.DirEntry, err error) error {20 if err != nil || d.IsDir() {21 return err22 }23 b, err := fs.ReadFile(fsys, p)24 if err != nil {25 return err26 }27 sum := sha256.Sum256(b)28 etags[p] = `"` + hex.EncodeToString(sum[:8]) + `"`29 return nil30 })31 if err != nil {32 log.Printf("tv: %v", err)33 }34 files := http.FileServerFS(fsys)35 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {36 name := strings.TrimPrefix(r.URL.Path, "/")37 if name == "" {38 name = "index.html"39 }40 etag, ok := etags[name]41 if !ok {42 http.NotFound(w, r)43 return44 }45 w.Header().Set("ETag", etag)46 if strings.HasSuffix(name, ".webmanifest") {47 w.Header().Set("Content-Type", "application/manifest+json")48 }49 if strings.HasPrefix(name, "fonts/") || strings.HasPrefix(name, "vendor/") || strings.HasPrefix(name, "icons/") {50 w.Header().Set("Cache-Control", "public, max-age=86400")51 } else {52 w.Header().Set("Cache-Control", "no-cache")53 }54 files.ServeHTTP(w, r)55 })56}