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 / store / settings.go
settings.go 2.7K
1package store23import (4 "crypto/rand"5 "encoding/hex"6 "encoding/json"7 "errors"8 "fmt"9 "io/fs"10 "os"11 "path/filepath"12)1314// Settings are the app's persisted preferences. Everything else (location,15// tuner, recordings) lives on the server.16type Settings struct {17 // Server is the airwavesd address ("nas" or "http://nas:8089").18 Server string `json:"server"`19 Token string `json:"token"`20 // ClientID identifies this app to the server so its streams are its own.21 ClientID string `json:"clientId"`22 // LastChannel is restored on launch.23 LastChannel string `json:"lastChannel"`24 // Favorites and Hidden hold channel keys: "number|callSign" for antenna25 // channels, "number|custom|name" for the server's custom channels, and26 // "weather" for its weather channel (the app moves older keys along).27 Favorites []string `json:"favorites,omitempty"`28 Hidden []string `json:"hidden,omitempty"`29 // Captions shows closed captions when a channel carries them.30 Captions bool `json:"captions"`31 // AudioLang is the preferred audio language (BCP 47, e.g. "es").32 AudioLang string `json:"audioLang,omitempty"`33 // Scale is the interface size in percent; 0 picks one for the screen.34 Scale int `json:"scale,omitempty"`35}3637// DefaultSettings returns settings for a first launch.38func DefaultSettings() Settings {39 return Settings{}40}4142func (s Settings) normalized() Settings {43 if s.Scale != 0 && (s.Scale < 50 || s.Scale > 300) {44 s.Scale = 045 }46 if s.ClientID == "" {47 b := make([]byte, 6)48 _, _ = rand.Read(b)49 s.ClientID = "app-" + hex.EncodeToString(b)50 }51 return s52}5354func settingsPath(app string) (string, error) {55 base, err := os.UserConfigDir()56 if err != nil {57 return "", fmt.Errorf("locate config dir: %w", err)58 }59 return filepath.Join(base, app, "settings.json"), nil60}6162// LoadSettings reads the settings for app, falling back to defaults.63func LoadSettings(app string) (Settings, error) {64 p, err := settingsPath(app)65 if err != nil {66 return DefaultSettings(), err67 }68 raw, err := os.ReadFile(p)69 if errors.Is(err, fs.ErrNotExist) {70 return DefaultSettings().normalized(), nil71 }72 if err != nil {73 return DefaultSettings(), err74 }75 var s Settings76 if err := json.Unmarshal(raw, &s); err != nil {77 return DefaultSettings(), fmt.Errorf("parse %s: %w", p, err)78 }79 return s.normalized(), nil80}8182// SaveSettings writes s for app and returns the normalized value stored.83func SaveSettings(app string, s Settings) (Settings, error) {84 s = s.normalized()85 p, err := settingsPath(app)86 if err != nil {87 return s, err88 }89 if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {90 return s, err91 }92 raw, err := json.MarshalIndent(s, "", " ")93 if err != nil {94 return s, err95 }96 return s, os.WriteFile(p, raw, 0o644)97}