package httpapi import ( "encoding/json" "os" "path/filepath" ) // filePlayerStatusStore wraps inMemoryPlayerStatusStore and persists the // records to a JSON file on every mutation. Reads are served from memory. type filePlayerStatusStore struct { *inMemoryPlayerStatusStore path string } func newFilePlayerStatusStore(path string) (*filePlayerStatusStore, error) { s := &filePlayerStatusStore{ inMemoryPlayerStatusStore: newInMemoryPlayerStatusStore(), path: path, } if err := s.load(); err != nil { return nil, err } return s, nil } // NewStoreFromConfig returns a playerStatusStore. If path is non-empty a // file-backed store is used; otherwise an in-memory store is returned. func NewStoreFromConfig(path string) (playerStatusStore, error) { if path == "" { return newInMemoryPlayerStatusStore(), nil } return newFilePlayerStatusStore(path) } func (s *filePlayerStatusStore) Save(record playerStatusRecord) { s.inMemoryPlayerStatusStore.Save(record) _ = s.persist() } func (s *filePlayerStatusStore) Delete(screenID string) bool { ok := s.inMemoryPlayerStatusStore.Delete(screenID) if ok { _ = s.persist() } return ok } func (s *filePlayerStatusStore) load() error { data, err := os.ReadFile(s.path) if os.IsNotExist(err) { return nil } if err != nil { return err } var records []playerStatusRecord if err := json.Unmarshal(data, &records); err != nil { return err } s.mu.Lock() defer s.mu.Unlock() for _, r := range records { s.records[r.ScreenID] = r } return nil } func (s *filePlayerStatusStore) persist() error { s.mu.RLock() records := make([]playerStatusRecord, 0, len(s.records)) for _, r := range s.records { records = append(records, r) } s.mu.RUnlock() data, err := json.Marshal(records) if err != nil { return err } tmp := s.path + ".tmp" if err := os.WriteFile(tmp, data, 0o644); err != nil { return err } return os.Rename(tmp, filepath.Clean(s.path)) }