package shell import ( "encoding/json" "errors" "io/fs" "os" "path/filepath" "sync" ) // Settings is what the desktop app keeps between runs. The client's own // state (sessions, keys) lives in the webview's storage, not here. type Settings struct { // Homeserver is the base URL new sign-ins go to, e.g. // https://matrix.example.org. Empty until the first-run page has asked. Homeserver string `json:"homeserver,omitempty"` // TrayHintShown is set once the app has said, the first time its // window was closed, that it keeps running in the tray. TrayHintShown bool `json:"tray_hint_shown,omitempty"` } // Store reads and writes Settings as a JSON file. type Store struct { path string mu sync.Mutex } // NewStore returns a store backed by the file at path. The file and its // directory are created on the first Save. func NewStore(path string) *Store { return &Store{path: path} } // Load returns the saved settings, or the zero value when there are none. func (s *Store) Load() (Settings, error) { s.mu.Lock() defer s.mu.Unlock() var st Settings b, err := os.ReadFile(s.path) if errors.Is(err, fs.ErrNotExist) { return st, nil } if err != nil { return st, err } return st, json.Unmarshal(b, &st) } // Save writes the settings, replacing the file atomically. func (s *Store) Save(st Settings) error { s.mu.Lock() defer s.mu.Unlock() b, err := json.MarshalIndent(st, "", " ") if err != nil { return err } if err := os.MkdirAll(filepath.Dir(s.path), 0o700); err != nil { return err } tmp := s.path + ".tmp" if err := os.WriteFile(tmp, append(b, '\n'), 0o600); err != nil { return err } return os.Rename(tmp, s.path) }