package preferences import ( "encoding/json" "errors" "fmt" "io/fs" "os" "slices" "sync" "ddc/internal/files" ) type Store struct { mutex sync.RWMutex path string current Preferences } func Open(path string, defaults Preferences) (*Store, error) { store := &Store{path: path, current: defaults} data, err := os.ReadFile(path) if errors.Is(err, fs.ErrNotExist) { return store, nil } if err != nil { return nil, err } loaded := defaults if err := json.Unmarshal(data, &loaded); err != nil { return nil, fmt.Errorf("settings file %s: %w", path, err) } if err := loaded.Validate(); err != nil { return nil, fmt.Errorf("settings file %s: %w", path, err) } store.current = loaded return store, nil } func (store *Store) Get() Preferences { store.mutex.RLock() defer store.mutex.RUnlock() current := store.current current.AllowedPorts = slices.Clone(current.AllowedPorts) if current.AllowedPorts == nil { current.AllowedPorts = []uint16{} } current.DownloadPaths = slices.Clone(current.DownloadPaths) if current.DownloadPaths == nil { current.DownloadPaths = []string{} } return current } func (store *Store) Update(next Preferences) error { if err := next.Validate(); err != nil { return err } data, err := json.MarshalIndent(next, "", " ") if err != nil { return err } store.mutex.Lock() defer store.mutex.Unlock() if err := files.WriteAtomically(store.path, data); err != nil { return err } store.current = next return nil }