config.go 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. package main
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "io/fs"
  7. "log"
  8. "os"
  9. "strings"
  10. "sync"
  11. )
  12. // siteConfig holds the settings in site.json. Every field is optional.
  13. type siteConfig struct {
  14. HomeText string `json:"homeText"` // text of the header link to the index
  15. }
  16. // withDefaults fills in any setting that is missing or blank.
  17. func (c siteConfig) withDefaults() siteConfig {
  18. if strings.TrimSpace(c.HomeText) == "" {
  19. c.HomeText = "Home"
  20. }
  21. return c
  22. }
  23. // configFile reads site.json and re-reads it when its size or modification
  24. // time changes. A missing file means all defaults; a broken file is logged
  25. // and the last good settings stay in use.
  26. type configFile struct {
  27. path string
  28. mu sync.Mutex
  29. sig string
  30. loaded bool
  31. cfg siteConfig
  32. }
  33. func newConfigFile(path string) *configFile {
  34. return &configFile{path: path, cfg: siteConfig{}.withDefaults()}
  35. }
  36. func (c *configFile) Get() siteConfig {
  37. c.mu.Lock()
  38. defer c.mu.Unlock()
  39. sig := "missing"
  40. info, err := os.Stat(c.path)
  41. switch {
  42. case err == nil:
  43. sig = fmt.Sprintf("%d %d", info.Size(), info.ModTime().UnixNano())
  44. case !errors.Is(err, fs.ErrNotExist):
  45. log.Printf("%s: %v; keeping previous settings", c.path, err)
  46. return c.cfg
  47. }
  48. if c.loaded && sig == c.sig {
  49. return c.cfg
  50. }
  51. c.sig, c.loaded = sig, true
  52. if sig == "missing" {
  53. c.cfg = siteConfig{}.withDefaults()
  54. return c.cfg
  55. }
  56. cfg, err := readConfig(c.path)
  57. if err != nil {
  58. log.Printf("%s: %v; keeping previous settings", c.path, err)
  59. return c.cfg
  60. }
  61. c.cfg = cfg
  62. return c.cfg
  63. }
  64. func readConfig(path string) (siteConfig, error) {
  65. f, err := os.Open(path)
  66. if err != nil {
  67. return siteConfig{}, err
  68. }
  69. defer f.Close()
  70. dec := json.NewDecoder(f)
  71. dec.DisallowUnknownFields() // catch typos in setting names
  72. var cfg siteConfig
  73. if err := dec.Decode(&cfg); err != nil {
  74. return siteConfig{}, err
  75. }
  76. return cfg.withDefaults(), nil
  77. }