package session import ( "encoding/json" "errors" "fmt" "os" "path/filepath" "strings" "time" ) type Data struct { Spotify SpotifyState `json:"spotify"` } type SpotifyState struct { ClientID string `json:"client_id,omitempty"` AccessToken string `json:"access_token,omitempty"` RefreshToken string `json:"refresh_token,omitempty"` TokenType string `json:"token_type,omitempty"` Scope string `json:"scope,omitempty"` ExpiresAt string `json:"expires_at,omitempty"` } func Load(path string) (Data, error) { path, err := expandPath(path) if err != nil { return Data{}, err } b, err := os.ReadFile(path) if err != nil { if errors.Is(err, os.ErrNotExist) { return Data{}, nil } return Data{}, fmt.Errorf("read session file: %w", err) } var d Data if err := json.Unmarshal(b, &d); err != nil { return Data{}, fmt.Errorf("decode session file: %w", err) } return d, nil } func Save(path string, d Data) error { path, err := expandPath(path) if err != nil { return err } dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o700); err != nil { return fmt.Errorf("create session directory: %w", err) } b, err := json.MarshalIndent(d, "", " ") if err != nil { return fmt.Errorf("encode session file: %w", err) } tmp := path + ".tmp" if err := os.WriteFile(tmp, b, 0o600); err != nil { return fmt.Errorf("write temp session file: %w", err) } if err := os.Rename(tmp, path); err != nil { return fmt.Errorf("replace session file: %w", err) } return nil } func (s SpotifyState) ExpiresAtTime() time.Time { t, err := time.Parse(time.RFC3339, strings.TrimSpace(s.ExpiresAt)) if err != nil { return time.Time{} } return t } func expandPath(path string) (string, error) { path = strings.TrimSpace(path) if path == "" { return "", fmt.Errorf("session path cannot be empty") } if strings.HasPrefix(path, "~/") || path == "~" { home, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("resolve home directory: %w", err) } if path == "~" { path = home } else { path = filepath.Join(home, strings.TrimPrefix(path, "~/")) } } return filepath.Clean(path), nil }