package settings import ( "fmt" "os" "path/filepath" "sync" "gopkg.in/yaml.v3" ) type Store struct { mu sync.RWMutex path string } type Settings struct { Theme string `yaml:"theme" json:"theme"` RepoURL string `yaml:"repoUrl" json:"repoUrl"` TTLDays int `yaml:"ttlDays" json:"ttlDays"` MaxPayloadBytes int `yaml:"maxPayloadBytes" json:"maxPayloadBytes"` AutoPurgePayloads bool `yaml:"autoPurgePayloads" json:"autoPurgePayloads"` AutoPurgePayloadBytes int `yaml:"autoPurgePayloadBytes" json:"autoPurgePayloadBytes"` AutoExpandDepth int `yaml:"autoExpandDepth" json:"autoExpandDepth"` ImageDetection bool `yaml:"imageDetectionEnabled" json:"imageDetectionEnabled"` HighlightMs int `yaml:"highlightMs" json:"highlightMs"` MQTTProfiles []MQTTProfile `yaml:"mqttProfiles" json:"mqttProfiles"` ActiveProfileID string `yaml:"activeProfileId" json:"activeProfileId"` ApplyViewFilter bool `yaml:"applyViewFilter" json:"applyViewFilter"` ExpandTreeOnStart bool `yaml:"expandTreeOnStart" json:"expandTreeOnStart"` TopicFilters []TopicFilter `yaml:"topicFilters" json:"topicFilters"` UIFontSize int `yaml:"uiFontSize" json:"uiFontSize"` TopicFontSize int `yaml:"topicFontSize" json:"topicFontSize"` PayloadFontSize int `yaml:"payloadFontSize" json:"payloadFontSize"` StatsRefreshMs int `yaml:"statsRefreshMs" json:"statsRefreshMs"` ResizeHandlePx int `yaml:"resizeHandlePx" json:"resizeHandlePx"` } type MQTTProfile struct { ID string `yaml:"id" json:"id"` Name string `yaml:"name" json:"name"` Host string `yaml:"host" json:"host"` Port int `yaml:"port" json:"port"` Username string `yaml:"username" json:"username"` Password string `yaml:"password" json:"password"` IsDefault bool `yaml:"isDefault" json:"isDefault"` } type TopicFilter struct { Topic string `yaml:"topic" json:"topic"` Save bool `yaml:"save" json:"save"` View bool `yaml:"view" json:"view"` } func NewStore(path string) *Store { return &Store{path: path} } func (s *Store) Load(defaults Settings) (Settings, error) { s.mu.RLock() defer s.mu.RUnlock() content, err := os.ReadFile(s.path) if err != nil { if os.IsNotExist(err) { return defaults, nil } return defaults, fmt.Errorf("lecture settings: %w", err) } var loaded Settings if err := yaml.Unmarshal(content, &loaded); err != nil { return defaults, fmt.Errorf("yaml settings: %w", err) } return merge(defaults, loaded), nil } func (s *Store) Save(next Settings) error { s.mu.Lock() defer s.mu.Unlock() dir := filepath.Dir(s.path) if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("mkdir settings: %w", err) } content, err := yaml.Marshal(next) if err != nil { return fmt.Errorf("marshal settings: %w", err) } return os.WriteFile(s.path, content, 0o644) } func merge(base, override Settings) Settings { merged := base if override.Theme != "" { merged.Theme = override.Theme } if override.RepoURL != "" { merged.RepoURL = override.RepoURL } if override.TTLDays != 0 { merged.TTLDays = override.TTLDays } if override.MaxPayloadBytes != 0 { merged.MaxPayloadBytes = override.MaxPayloadBytes } merged.AutoPurgePayloads = override.AutoPurgePayloads if override.AutoPurgePayloadBytes != 0 { merged.AutoPurgePayloadBytes = override.AutoPurgePayloadBytes } if override.AutoExpandDepth != 0 { merged.AutoExpandDepth = override.AutoExpandDepth } merged.ImageDetection = override.ImageDetection if override.HighlightMs != 0 { merged.HighlightMs = override.HighlightMs } if len(override.MQTTProfiles) > 0 { merged.MQTTProfiles = override.MQTTProfiles } if override.ActiveProfileID != "" { merged.ActiveProfileID = override.ActiveProfileID } merged.ApplyViewFilter = override.ApplyViewFilter merged.ExpandTreeOnStart = override.ExpandTreeOnStart if len(override.TopicFilters) > 0 { merged.TopicFilters = override.TopicFilters } if override.UIFontSize != 0 { merged.UIFontSize = override.UIFontSize } if override.TopicFontSize != 0 { merged.TopicFontSize = override.TopicFontSize } if override.PayloadFontSize != 0 { merged.PayloadFontSize = override.PayloadFontSize } if override.StatsRefreshMs != 0 { merged.StatsRefreshMs = override.StatsRefreshMs } if override.ResizeHandlePx != 0 { merged.ResizeHandlePx = override.ResizeHandlePx } return merged }