config.go 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  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. DefaultAuthor string `json:"defaultAuthor"` // author of posts that don't name one
  16. }
  17. // withDefaults fills in any setting that is missing or blank.
  18. func (c siteConfig) withDefaults() siteConfig {
  19. if strings.TrimSpace(c.HomeText) == "" {
  20. c.HomeText = "Home"
  21. }
  22. if strings.TrimSpace(c.DefaultAuthor) == "" {
  23. c.DefaultAuthor = "Claude"
  24. }
  25. return c
  26. }
  27. // configFile reads site.json and re-reads it when its size or modification
  28. // time changes. A missing file means all defaults; a broken file is logged
  29. // and the last good settings stay in use.
  30. type configFile struct {
  31. path string
  32. mu sync.Mutex
  33. sig string
  34. loaded bool
  35. cfg siteConfig
  36. }
  37. func newConfigFile(path string) *configFile {
  38. return &configFile{path: path, cfg: siteConfig{}.withDefaults()}
  39. }
  40. func (c *configFile) Get() siteConfig {
  41. c.mu.Lock()
  42. defer c.mu.Unlock()
  43. sig := "missing"
  44. info, err := os.Stat(c.path)
  45. switch {
  46. case err == nil:
  47. sig = fmt.Sprintf("%d %d", info.Size(), info.ModTime().UnixNano())
  48. case !errors.Is(err, fs.ErrNotExist):
  49. log.Printf("%s: %v; keeping previous settings", c.path, err)
  50. return c.cfg
  51. }
  52. if c.loaded && sig == c.sig {
  53. return c.cfg
  54. }
  55. c.sig, c.loaded = sig, true
  56. if sig == "missing" {
  57. c.cfg = siteConfig{}.withDefaults()
  58. return c.cfg
  59. }
  60. cfg, err := readConfig(c.path)
  61. if err != nil {
  62. log.Printf("%s: %v; keeping previous settings", c.path, err)
  63. return c.cfg
  64. }
  65. c.cfg = cfg
  66. return c.cfg
  67. }
  68. func readConfig(path string) (siteConfig, error) {
  69. f, err := os.Open(path)
  70. if err != nil {
  71. return siteConfig{}, err
  72. }
  73. defer f.Close()
  74. dec := json.NewDecoder(f)
  75. dec.DisallowUnknownFields() // catch typos in setting names
  76. var cfg siteConfig
  77. if err := dec.Decode(&cfg); err != nil {
  78. return siteConfig{}, err
  79. }
  80. return cfg.withDefaults(), nil
  81. }