refactor(app): internalize dispatch channel
Some checks are pending
Some checks are pending
This commit is contained in:
parent
caa543703e
commit
2f263b5725
@ -1,6 +1,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -23,6 +24,7 @@ type App struct {
|
|||||||
cfg config.Config
|
cfg config.Config
|
||||||
configService *config.Service
|
configService *config.Service
|
||||||
eventBus *event.Bus
|
eventBus *event.Bus
|
||||||
|
dispatchC chan event.Command
|
||||||
dockerClient container.DockerClient
|
dockerClient container.DockerClient
|
||||||
screen *terminal.Screen // Screen may be nil.
|
screen *terminal.Screen // Screen may be nil.
|
||||||
clipboardAvailable bool
|
clipboardAvailable bool
|
||||||
@ -35,6 +37,7 @@ type App struct {
|
|||||||
type Params struct {
|
type Params struct {
|
||||||
ConfigService *config.Service
|
ConfigService *config.Service
|
||||||
DockerClient container.DockerClient
|
DockerClient container.DockerClient
|
||||||
|
ChanSize int
|
||||||
Screen *terminal.Screen // Screen may be nil.
|
Screen *terminal.Screen // Screen may be nil.
|
||||||
ClipboardAvailable bool
|
ClipboardAvailable bool
|
||||||
ConfigFilePath string
|
ConfigFilePath string
|
||||||
@ -42,12 +45,16 @@ type Params struct {
|
|||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defaultChanSize is the default size of the dispatch channel.
|
||||||
|
const defaultChanSize = 64
|
||||||
|
|
||||||
// New creates a new application instance.
|
// New creates a new application instance.
|
||||||
func New(params Params) *App {
|
func New(params Params) *App {
|
||||||
return &App{
|
return &App{
|
||||||
cfg: params.ConfigService.Current(),
|
cfg: params.ConfigService.Current(),
|
||||||
configService: params.ConfigService,
|
configService: params.ConfigService,
|
||||||
eventBus: event.NewBus(params.Logger.With("component", "event_bus")),
|
eventBus: event.NewBus(params.Logger.With("component", "event_bus")),
|
||||||
|
dispatchC: make(chan event.Command, cmp.Or(params.ChanSize, defaultChanSize)),
|
||||||
dockerClient: params.DockerClient,
|
dockerClient: params.DockerClient,
|
||||||
screen: params.Screen,
|
screen: params.Screen,
|
||||||
clipboardAvailable: params.ClipboardAvailable,
|
clipboardAvailable: params.ClipboardAvailable,
|
||||||
@ -70,6 +77,7 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
ui, err := terminal.StartUI(ctx, terminal.StartParams{
|
ui, err := terminal.StartUI(ctx, terminal.StartParams{
|
||||||
EventBus: a.eventBus,
|
EventBus: a.eventBus,
|
||||||
|
Dispatcher: func(cmd event.Command) { a.dispatchC <- cmd },
|
||||||
Screen: a.screen,
|
Screen: a.screen,
|
||||||
ClipboardAvailable: a.clipboardAvailable,
|
ClipboardAvailable: a.clipboardAvailable,
|
||||||
ConfigFilePath: a.configFilePath,
|
ConfigFilePath: a.configFilePath,
|
||||||
@ -107,7 +115,7 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
a.eventBus.Send(event.FatalErrorOccurredEvent{Message: msg})
|
a.eventBus.Send(event.FatalErrorOccurredEvent{Message: msg})
|
||||||
|
|
||||||
emptyUI()
|
emptyUI()
|
||||||
<-ui.C()
|
<-a.dispatchC
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer containerClient.Close()
|
defer containerClient.Close()
|
||||||
@ -139,7 +147,7 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
err = fmt.Errorf("create mediaserver: %w", err)
|
err = fmt.Errorf("create mediaserver: %w", err)
|
||||||
a.eventBus.Send(event.FatalErrorOccurredEvent{Message: err.Error()})
|
a.eventBus.Send(event.FatalErrorOccurredEvent{Message: err.Error()})
|
||||||
emptyUI()
|
emptyUI()
|
||||||
<-ui.C()
|
<-a.dispatchC
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
@ -159,7 +167,7 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
if ok, startupErr := doStartupCheck(ctx, containerClient, a.eventBus); startupErr != nil {
|
if ok, startupErr := doStartupCheck(ctx, containerClient, a.eventBus); startupErr != nil {
|
||||||
startupErr = fmt.Errorf("startup check: %w", startupErr)
|
startupErr = fmt.Errorf("startup check: %w", startupErr)
|
||||||
a.eventBus.Send(event.FatalErrorOccurredEvent{Message: startupErr.Error()})
|
a.eventBus.Send(event.FatalErrorOccurredEvent{Message: startupErr.Error()})
|
||||||
<-ui.C()
|
<-a.dispatchC
|
||||||
return startupErr
|
return startupErr
|
||||||
} else if ok {
|
} else if ok {
|
||||||
startMediaServerC <- struct{}{}
|
startMediaServerC <- struct{}{}
|
||||||
@ -175,13 +183,7 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
a.eventBus.Send(event.MediaServerStartedEvent{RTMPURL: srv.RTMPURL(), RTMPSURL: srv.RTMPSURL()})
|
a.eventBus.Send(event.MediaServerStartedEvent{RTMPURL: srv.RTMPURL(), RTMPSURL: srv.RTMPSURL()})
|
||||||
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, ok := <-ui.C():
|
case cmd := <-a.dispatchC:
|
||||||
if !ok {
|
|
||||||
// TODO: keep UI open until all containers have closed
|
|
||||||
a.logger.Info("UI closed")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, err := a.handleCommand(ctx, cmd, state, repl, containerClient, startMediaServerC); err != nil {
|
if ok, err := a.handleCommand(ctx, cmd, state, repl, containerClient, startMediaServerC); err != nil {
|
||||||
return fmt.Errorf("handle command: %w", err)
|
return fmt.Errorf("handle command: %w", err)
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
|
@ -42,7 +42,7 @@ const (
|
|||||||
// UI is responsible for managing the terminal user interface.
|
// UI is responsible for managing the terminal user interface.
|
||||||
type UI struct {
|
type UI struct {
|
||||||
eventBus *event.Bus
|
eventBus *event.Bus
|
||||||
commandC chan event.Command
|
dispatch func(event.Command)
|
||||||
clipboardAvailable bool
|
clipboardAvailable bool
|
||||||
configFilePath string
|
configFilePath string
|
||||||
rtmpURL, rtmpsURL string
|
rtmpURL, rtmpsURL string
|
||||||
@ -96,7 +96,7 @@ type ScreenCapture struct {
|
|||||||
// interface.
|
// interface.
|
||||||
type StartParams struct {
|
type StartParams struct {
|
||||||
EventBus *event.Bus
|
EventBus *event.Bus
|
||||||
ChanSize int
|
Dispatcher func(event.Command)
|
||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
ClipboardAvailable bool
|
ClipboardAvailable bool
|
||||||
ConfigFilePath string
|
ConfigFilePath string
|
||||||
@ -104,13 +104,8 @@ type StartParams struct {
|
|||||||
Screen *Screen // Screen may be nil.
|
Screen *Screen // Screen may be nil.
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultChanSize = 64
|
|
||||||
|
|
||||||
// StartUI starts the terminal user interface.
|
// StartUI starts the terminal user interface.
|
||||||
func StartUI(ctx context.Context, params StartParams) (*UI, error) {
|
func StartUI(ctx context.Context, params StartParams) (*UI, error) {
|
||||||
chanSize := cmp.Or(params.ChanSize, defaultChanSize)
|
|
||||||
commandCh := make(chan event.Command, chanSize)
|
|
||||||
|
|
||||||
app := tview.NewApplication()
|
app := tview.NewApplication()
|
||||||
|
|
||||||
var screen tcell.Screen
|
var screen tcell.Screen
|
||||||
@ -213,8 +208,8 @@ func StartUI(ctx context.Context, params StartParams) (*UI, error) {
|
|||||||
app.EnableMouse(false)
|
app.EnableMouse(false)
|
||||||
|
|
||||||
ui := &UI{
|
ui := &UI{
|
||||||
commandC: commandCh,
|
|
||||||
eventBus: params.EventBus,
|
eventBus: params.EventBus,
|
||||||
|
dispatch: params.Dispatcher,
|
||||||
clipboardAvailable: params.ClipboardAvailable,
|
clipboardAvailable: params.ClipboardAvailable,
|
||||||
configFilePath: params.ConfigFilePath,
|
configFilePath: params.ConfigFilePath,
|
||||||
buildInfo: params.BuildInfo,
|
buildInfo: params.BuildInfo,
|
||||||
@ -271,13 +266,11 @@ func (ui *UI) renderAboutView() {
|
|||||||
ui.aboutView.AddItem(tview.NewTextView().SetDynamicColors(true).SetText("[grey]?[-] About"), 1, 0, false)
|
ui.aboutView.AddItem(tview.NewTextView().SetDynamicColors(true).SetText("[grey]?[-] About"), 1, 0, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// C returns a channel that receives commands from the user interface.
|
|
||||||
func (ui *UI) C() <-chan event.Command {
|
|
||||||
return ui.commandC
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *UI) run(ctx context.Context) {
|
func (ui *UI) run(ctx context.Context) {
|
||||||
defer close(ui.commandC)
|
defer func() {
|
||||||
|
// Ensure the application is stopped when the UI is closed.
|
||||||
|
ui.dispatch(event.CommandQuit{})
|
||||||
|
}()
|
||||||
|
|
||||||
eventC := ui.eventBus.Register()
|
eventC := ui.eventBus.Register()
|
||||||
|
|
||||||
@ -425,9 +418,9 @@ func (ui *UI) handleOtherInstanceDetected(event.OtherInstanceDetectedEvent) {
|
|||||||
false,
|
false,
|
||||||
func(buttonIndex int, _ string) {
|
func(buttonIndex int, _ string) {
|
||||||
if buttonIndex == 0 {
|
if buttonIndex == 0 {
|
||||||
ui.commandC <- event.CommandCloseOtherInstance{}
|
ui.dispatch(event.CommandCloseOtherInstance{})
|
||||||
} else {
|
} else {
|
||||||
ui.commandC <- event.CommandQuit{}
|
ui.dispatch(event.CommandQuit{})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -457,7 +450,7 @@ func (ui *UI) handleFatalErrorOccurred(evt event.FatalErrorOccurredEvent) {
|
|||||||
[]string{"Quit"},
|
[]string{"Quit"},
|
||||||
false,
|
false,
|
||||||
func(int, string) {
|
func(int, string) {
|
||||||
ui.commandC <- event.CommandQuit{}
|
ui.dispatch(event.CommandQuit{})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -702,7 +695,7 @@ func (ui *UI) handleMediaServerClosed(exitReason string) {
|
|||||||
SetBackgroundColor(tcell.ColorBlack).
|
SetBackgroundColor(tcell.ColorBlack).
|
||||||
SetTextColor(tcell.ColorWhite).
|
SetTextColor(tcell.ColorWhite).
|
||||||
SetDoneFunc(func(int, string) {
|
SetDoneFunc(func(int, string) {
|
||||||
ui.commandC <- event.CommandQuit{}
|
ui.dispatch(event.CommandQuit{})
|
||||||
})
|
})
|
||||||
modal.SetBorderStyle(tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite))
|
modal.SetBorderStyle(tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite))
|
||||||
|
|
||||||
@ -873,10 +866,10 @@ func (ui *UI) addDestination() {
|
|||||||
AddInputField(inputLabelName, "My stream", inputLen, nil, nil).
|
AddInputField(inputLabelName, "My stream", inputLen, nil, nil).
|
||||||
AddInputField(inputLabelURL, "rtmp://", inputLen, nil, nil).
|
AddInputField(inputLabelURL, "rtmp://", inputLen, nil, nil).
|
||||||
AddButton("Add", func() {
|
AddButton("Add", func() {
|
||||||
ui.commandC <- event.CommandAddDestination{
|
ui.dispatch(event.CommandAddDestination{
|
||||||
DestinationName: form.GetFormItemByLabel(inputLabelName).(*tview.InputField).GetText(),
|
DestinationName: form.GetFormItemByLabel(inputLabelName).(*tview.InputField).GetText(),
|
||||||
URL: form.GetFormItemByLabel(inputLabelURL).(*tview.InputField).GetText(),
|
URL: form.GetFormItemByLabel(inputLabelURL).(*tview.InputField).GetText(),
|
||||||
}
|
})
|
||||||
}).
|
}).
|
||||||
AddButton("Cancel", func() {
|
AddButton("Cancel", func() {
|
||||||
ui.closeAddDestinationForm()
|
ui.closeAddDestinationForm()
|
||||||
@ -931,7 +924,7 @@ func (ui *UI) removeDestination() {
|
|||||||
false,
|
false,
|
||||||
func(buttonIndex int, _ string) {
|
func(buttonIndex int, _ string) {
|
||||||
if buttonIndex == 0 {
|
if buttonIndex == 0 {
|
||||||
ui.commandC <- event.CommandRemoveDestination{URL: url}
|
ui.dispatch(event.CommandRemoveDestination{URL: url})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -1007,12 +1000,12 @@ func (ui *UI) toggleDestination() {
|
|||||||
switch ss {
|
switch ss {
|
||||||
case startStateNotStarted:
|
case startStateNotStarted:
|
||||||
ui.urlsToStartState[url] = startStateStarting
|
ui.urlsToStartState[url] = startStateStarting
|
||||||
ui.commandC <- event.CommandStartDestination{URL: url}
|
ui.dispatch(event.CommandStartDestination{URL: url})
|
||||||
case startStateStarting:
|
case startStateStarting:
|
||||||
// do nothing
|
// do nothing
|
||||||
return
|
return
|
||||||
case startStateStarted:
|
case startStateStarted:
|
||||||
ui.commandC <- event.CommandStopDestination{URL: url}
|
ui.dispatch(event.CommandStopDestination{URL: url})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1065,7 +1058,7 @@ func (ui *UI) confirmQuit() {
|
|||||||
false,
|
false,
|
||||||
func(buttonIndex int, _ string) {
|
func(buttonIndex int, _ string) {
|
||||||
if buttonIndex == 0 {
|
if buttonIndex == 0 {
|
||||||
ui.commandC <- event.CommandQuit{}
|
ui.dispatch(event.CommandQuit{})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user