refactor(app): add Dispatch method

This commit is contained in:
Rob Watson 2025-04-30 22:17:19 +02:00
parent 4be90993ef
commit 2bdb5335dc

View File

@ -184,10 +184,10 @@ func (a *App) Run(ctx context.Context) error {
case <-a.configService.C(): case <-a.configService.C():
// No-op, config updates are handled synchronously for now. // No-op, config updates are handled synchronously for now.
case cmd := <-a.dispatchC: case cmd := <-a.dispatchC:
if ok, err := a.handleCommand(ctx, cmd, state, repl, containerClient, startMediaServerC); err != nil { if _, err := a.handleCommand(ctx, cmd, state, repl, containerClient, startMediaServerC); errors.Is(err, errExit) {
return fmt.Errorf("handle command: %w", err)
} else if !ok {
return nil return nil
} else if err != nil {
return fmt.Errorf("handle command: %w", err)
} }
case <-uiUpdateT.C: case <-uiUpdateT.C:
updateUI() updateUI()
@ -209,8 +209,28 @@ func (a *App) Run(ctx context.Context) error {
} }
} }
// handleCommand handles an incoming command. It returns false if the app type syncCommand struct {
// should not continue, i.e. quit. event.Command
done chan<- event.Event
}
// Dispatch dispatches a command to be executed synchronously.
func (a *App) Dispatch(cmd event.Command) event.Event {
ch := make(chan event.Event, 1)
a.dispatchC <- syncCommand{Command: cmd, done: ch}
return <-ch
}
// errExit is an error that indicates the app should exit.
var errExit = errors.New("exit")
// handleCommand handles an incoming command. It may return an Event which will
// already have been published to the event bus, but which is returned for the
// benefit of synchronous callers. The event may be nil. It may also publish
// other events to the event bus which are not returned. Currently the only
// error that may be returned is [errExit], which indicates to the main event
// loop that the app should exit.
func (a *App) handleCommand( func (a *App) handleCommand(
ctx context.Context, ctx context.Context,
cmd event.Command, cmd event.Command,
@ -218,8 +238,17 @@ func (a *App) handleCommand(
repl *replicator.Actor, repl *replicator.Actor,
containerClient *container.Client, containerClient *container.Client,
startMediaServerC chan struct{}, startMediaServerC chan struct{},
) (bool, error) { ) (evt event.Event, _ error) {
a.logger.Debug("Command received", "cmd", cmd.Name()) a.logger.Debug("Command received", "cmd", cmd.Name())
defer func() {
if evt != nil {
a.eventBus.Send(evt)
}
if c, ok := cmd.(syncCommand); ok {
c.done <- evt
}
}()
switch c := cmd.(type) { switch c := cmd.(type) {
case event.CommandAddDestination: case event.CommandAddDestination:
newCfg := a.cfg newCfg := a.cfg
@ -229,8 +258,7 @@ func (a *App) handleCommand(
}) })
if err := a.configService.SetConfig(newCfg); err != nil { if err := a.configService.SetConfig(newCfg); err != nil {
a.logger.Error("Add destination failed", "err", err) a.logger.Error("Add destination failed", "err", err)
a.eventBus.Send(event.AddDestinationFailedEvent{Err: err}) return event.AddDestinationFailedEvent{Err: err}, nil
break
} }
a.cfg = newCfg a.cfg = newCfg
a.handleConfigUpdate(state) a.handleConfigUpdate(state)
@ -260,15 +288,15 @@ func (a *App) handleCommand(
repl.StopDestination(c.URL) repl.StopDestination(c.URL)
case event.CommandCloseOtherInstance: case event.CommandCloseOtherInstance:
if err := closeOtherInstances(ctx, containerClient); err != nil { if err := closeOtherInstances(ctx, containerClient); err != nil {
return false, fmt.Errorf("close other instances: %w", err) return nil, fmt.Errorf("close other instances: %w", err)
} }
startMediaServerC <- struct{}{} startMediaServerC <- struct{}{}
case event.CommandQuit: case event.CommandQuit:
return false, nil return nil, errExit
} }
return true, nil return nil, nil
} }
// handleConfigUpdate applies the config to the app state, and sends an AppStateChangedEvent. // handleConfigUpdate applies the config to the app state, and sends an AppStateChangedEvent.