gracefully handle legacy config: notify instead of crashing
This commit is contained in:
+20
-6
@@ -28,26 +28,40 @@ func GetConfigPath() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Load() (*Config, error) {
|
func Load() (*Config, error) {
|
||||||
configPath, err := GetConfigPath()
|
config, legacy, err := LoadOrLegacy()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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 {
|
} 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)
|
log.Printf("Config: loading configuration from %s", configPath)
|
||||||
var config Config
|
var config Config
|
||||||
meta, err := toml.DecodeFile(configPath, &config)
|
meta, err := toml.DecodeFile(configPath, &config)
|
||||||
if err != nil {
|
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) {
|
if isLegacyConfig(meta, &config) {
|
||||||
log.Printf("Config: legacy configuration detected - run hyprvoice onboarding")
|
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 {
|
if config.Providers == nil {
|
||||||
@@ -58,7 +72,7 @@ func Load() (*Config, error) {
|
|||||||
config.applyThreadsDefault()
|
config.applyThreadsDefault()
|
||||||
|
|
||||||
log.Printf("Config: configuration loaded successfully")
|
log.Printf("Config: configuration loaded successfully")
|
||||||
return &config, nil
|
return &config, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isLegacyConfig(meta toml.MetaData, config *Config) bool {
|
func isLegacyConfig(meta toml.MetaData, config *Config) bool {
|
||||||
|
|||||||
@@ -22,25 +22,33 @@ type Manager struct {
|
|||||||
debounceTimer *time.Timer
|
debounceTimer *time.Timer
|
||||||
debounceMutex sync.Mutex
|
debounceMutex sync.Mutex
|
||||||
debounceDelay time.Duration
|
debounceDelay time.Duration
|
||||||
|
|
||||||
|
// legacy tracks if config is in legacy format (needs onboarding)
|
||||||
|
legacy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager() (*Manager, error) {
|
func NewManager() (*Manager, error) {
|
||||||
log.Printf("Config manager: initializing configuration system...")
|
log.Printf("Config manager: initializing configuration system...")
|
||||||
|
|
||||||
config, err := Load()
|
config, legacy, err := LoadOrLegacy()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Config manager: failed to load initial configuration: %v", err)
|
log.Printf("Config manager: failed to load initial configuration: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if legacy {
|
||||||
|
log.Printf("Config manager: legacy config detected, daemon will prompt for onboarding")
|
||||||
|
} else {
|
||||||
log.Printf("Config manager: validating initial configuration...")
|
log.Printf("Config manager: validating initial configuration...")
|
||||||
if err := config.Validate(); err != nil {
|
if err := config.Validate(); err != nil {
|
||||||
log.Printf("Config manager: validation warning: %v", err)
|
log.Printf("Config manager: validation warning: %v", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
config: config,
|
config: config,
|
||||||
debounceDelay: 500 * time.Millisecond, // 500ms debounce delay
|
debounceDelay: 500 * time.Millisecond, // 500ms debounce delay
|
||||||
|
legacy: legacy,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Config manager: initialization completed successfully")
|
log.Printf("Config manager: initialization completed successfully")
|
||||||
@@ -56,6 +64,13 @@ func (m *Manager) GetConfig() *Config {
|
|||||||
return &configCopy
|
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 {
|
func (m *Manager) StartWatching(ctx context.Context) error {
|
||||||
configPath, err := GetConfigPath()
|
configPath, err := GetConfigPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -136,12 +151,17 @@ func (m *Manager) watchLoop(ctx context.Context, configPath string) {
|
|||||||
func (m *Manager) reloadConfig() {
|
func (m *Manager) reloadConfig() {
|
||||||
log.Printf("Config manager: starting configuration reload...")
|
log.Printf("Config manager: starting configuration reload...")
|
||||||
|
|
||||||
newConfig, err := Load()
|
newConfig, legacy, err := LoadOrLegacy()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Config manager: failed to reload config: %v", err)
|
log.Printf("Config manager: failed to reload config: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if legacy {
|
||||||
|
log.Printf("Config manager: config still in legacy format, skipping reload")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Config manager: validating new configuration...")
|
log.Printf("Config manager: validating new configuration...")
|
||||||
if err := newConfig.Validate(); err != nil {
|
if err := newConfig.Validate(); err != nil {
|
||||||
log.Printf("Config manager: invalid config after reload: %v", err)
|
log.Printf("Config manager: invalid config after reload: %v", err)
|
||||||
@@ -150,6 +170,7 @@ func (m *Manager) reloadConfig() {
|
|||||||
|
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
m.config = newConfig
|
m.config = newConfig
|
||||||
|
m.legacy = false // clear legacy flag on successful reload
|
||||||
onConfigReload := m.onConfigReload
|
onConfigReload := m.onConfigReload
|
||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,14 @@ func New() (*Daemon, error) {
|
|||||||
conf := configMgr.GetConfig()
|
conf := configMgr.GetConfig()
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
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{
|
d := &Daemon{
|
||||||
notifier: notify.NewNotifier(conf.Notifications.Type, conf.Notifications.Messages.Resolve()),
|
notifier: notify.NewNotifier(notifType, conf.Notifications.Messages.Resolve()),
|
||||||
configMgr: configMgr,
|
configMgr: configMgr,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
@@ -178,6 +184,10 @@ func (d *Daemon) handle(c net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Daemon) toggle() {
|
func (d *Daemon) toggle() {
|
||||||
|
if d.configMgr.IsLegacy() {
|
||||||
|
d.notifier.Error("Legacy config detected. Run: hyprvoice onboarding")
|
||||||
|
return
|
||||||
|
}
|
||||||
conf := d.configMgr.GetConfig()
|
conf := d.configMgr.GetConfig()
|
||||||
switch d.status() {
|
switch d.status() {
|
||||||
case pipeline.Idle:
|
case pipeline.Idle:
|
||||||
|
|||||||
Reference in New Issue
Block a user