gracefully handle legacy config: notify instead of crashing

This commit is contained in:
leonardotrapani
2026-02-02 11:24:56 +01:00
parent 0981317aa2
commit 76b9f08a4d
3 changed files with 57 additions and 12 deletions
+20 -6
View File
@@ -28,26 +28,40 @@ func GetConfigPath() (string, error) {
}
func Load() (*Config, error) {
configPath, err := GetConfigPath()
config, legacy, err := LoadOrLegacy()
if err != nil {
return nil, err
}
if legacy {
log.Printf("Config: legacy configuration detected - run hyprvoice onboarding")
return nil, fmt.Errorf("%w: run hyprvoice onboarding", ErrConfigNotFound)
}
return config, nil
}
// LoadOrLegacy loads config and returns (config, isLegacy, error).
// If config is legacy, returns default config with isLegacy=true instead of error.
func LoadOrLegacy() (*Config, bool, error) {
configPath, err := GetConfigPath()
if err != nil {
return nil, false, err
}
if _, err := os.Stat(configPath); os.IsNotExist(err) {
return nil, fmt.Errorf("%w: run hyprvoice onboarding", ErrConfigNotFound)
return nil, false, fmt.Errorf("%w: run hyprvoice onboarding", ErrConfigNotFound)
} else if err != nil {
return nil, fmt.Errorf("failed to stat config file %s: %w", configPath, err)
return nil, false, fmt.Errorf("failed to stat config file %s: %w", configPath, err)
}
log.Printf("Config: loading configuration from %s", configPath)
var config Config
meta, err := toml.DecodeFile(configPath, &config)
if err != nil {
return nil, fmt.Errorf("failed to parse config file %s: %w", configPath, err)
return nil, false, fmt.Errorf("failed to parse config file %s: %w", configPath, err)
}
if isLegacyConfig(meta, &config) {
log.Printf("Config: legacy configuration detected - run hyprvoice onboarding")
return nil, fmt.Errorf("%w: run hyprvoice onboarding", ErrConfigNotFound)
return DefaultConfig(), true, nil
}
if config.Providers == nil {
@@ -58,7 +72,7 @@ func Load() (*Config, error) {
config.applyThreadsDefault()
log.Printf("Config: configuration loaded successfully")
return &config, nil
return &config, false, nil
}
func isLegacyConfig(meta toml.MetaData, config *Config) bool {
+26 -5
View File
@@ -22,25 +22,33 @@ type Manager struct {
debounceTimer *time.Timer
debounceMutex sync.Mutex
debounceDelay time.Duration
// legacy tracks if config is in legacy format (needs onboarding)
legacy bool
}
func NewManager() (*Manager, error) {
log.Printf("Config manager: initializing configuration system...")
config, err := Load()
config, legacy, err := LoadOrLegacy()
if err != nil {
log.Printf("Config manager: failed to load initial configuration: %v", err)
return nil, err
}
log.Printf("Config manager: validating initial configuration...")
if err := config.Validate(); err != nil {
log.Printf("Config manager: validation warning: %v", err)
if legacy {
log.Printf("Config manager: legacy config detected, daemon will prompt for onboarding")
} else {
log.Printf("Config manager: validating initial configuration...")
if err := config.Validate(); err != nil {
log.Printf("Config manager: validation warning: %v", err)
}
}
m := &Manager{
config: config,
debounceDelay: 500 * time.Millisecond, // 500ms debounce delay
legacy: legacy,
}
log.Printf("Config manager: initialization completed successfully")
@@ -56,6 +64,13 @@ func (m *Manager) GetConfig() *Config {
return &configCopy
}
// IsLegacy returns true if the config is in legacy format and needs onboarding
func (m *Manager) IsLegacy() bool {
m.mu.RLock()
defer m.mu.RUnlock()
return m.legacy
}
func (m *Manager) StartWatching(ctx context.Context) error {
configPath, err := GetConfigPath()
if err != nil {
@@ -136,12 +151,17 @@ func (m *Manager) watchLoop(ctx context.Context, configPath string) {
func (m *Manager) reloadConfig() {
log.Printf("Config manager: starting configuration reload...")
newConfig, err := Load()
newConfig, legacy, err := LoadOrLegacy()
if err != nil {
log.Printf("Config manager: failed to reload config: %v", err)
return
}
if legacy {
log.Printf("Config manager: config still in legacy format, skipping reload")
return
}
log.Printf("Config manager: validating new configuration...")
if err := newConfig.Validate(); err != nil {
log.Printf("Config manager: invalid config after reload: %v", err)
@@ -150,6 +170,7 @@ func (m *Manager) reloadConfig() {
m.mu.Lock()
m.config = newConfig
m.legacy = false // clear legacy flag on successful reload
onConfigReload := m.onConfigReload
m.mu.Unlock()
+11 -1
View File
@@ -39,8 +39,14 @@ func New() (*Daemon, error) {
conf := configMgr.GetConfig()
ctx, cancel := context.WithCancel(context.Background())
// force desktop notifications when legacy config so user sees the onboarding prompt
notifType := conf.Notifications.Type
if configMgr.IsLegacy() {
notifType = "desktop"
}
d := &Daemon{
notifier: notify.NewNotifier(conf.Notifications.Type, conf.Notifications.Messages.Resolve()),
notifier: notify.NewNotifier(notifType, conf.Notifications.Messages.Resolve()),
configMgr: configMgr,
ctx: ctx,
cancel: cancel,
@@ -178,6 +184,10 @@ func (d *Daemon) handle(c net.Conn) {
}
func (d *Daemon) toggle() {
if d.configMgr.IsLegacy() {
d.notifier.Error("Legacy config detected. Run: hyprvoice onboarding")
return
}
conf := d.configMgr.GetConfig()
switch d.status() {
case pipeline.Idle: