refactor(app): add App type
This commit is contained in:
parent
4b464c680d
commit
d7f8fb49eb
@ -18,8 +18,19 @@ import (
|
|||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunParams holds the parameters for running the application.
|
// App is an instance of the app.
|
||||||
type RunParams struct {
|
type App struct {
|
||||||
|
configService *config.Service
|
||||||
|
dockerClient container.DockerClient
|
||||||
|
screen *terminal.Screen // Screen may be nil.
|
||||||
|
clipboardAvailable bool
|
||||||
|
configFilePath string
|
||||||
|
buildInfo domain.BuildInfo
|
||||||
|
logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params holds the parameters for running the application.
|
||||||
|
type Params struct {
|
||||||
ConfigService *config.Service
|
ConfigService *config.Service
|
||||||
DockerClient container.DockerClient
|
DockerClient container.DockerClient
|
||||||
Screen *terminal.Screen // Screen may be nil.
|
Screen *terminal.Screen // Screen may be nil.
|
||||||
@ -29,14 +40,25 @@ type RunParams struct {
|
|||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func New(params Params) *App {
|
||||||
|
return &App{
|
||||||
|
configService: params.ConfigService,
|
||||||
|
dockerClient: params.DockerClient,
|
||||||
|
screen: params.Screen,
|
||||||
|
clipboardAvailable: params.ClipboardAvailable,
|
||||||
|
configFilePath: params.ConfigFilePath,
|
||||||
|
buildInfo: params.BuildInfo,
|
||||||
|
logger: params.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Run starts the application, and blocks until it exits.
|
// Run starts the application, and blocks until it exits.
|
||||||
func Run(ctx context.Context, params RunParams) error {
|
func (a *App) Run(ctx context.Context) error {
|
||||||
logger := params.Logger
|
eventBus := event.NewBus(a.logger.With("component", "event_bus"))
|
||||||
eventBus := event.NewBus(logger.With("component", "event_bus"))
|
|
||||||
|
|
||||||
// cfg is the current configuration of the application, as reflected in the
|
// cfg is the current configuration of the application, as reflected in the
|
||||||
// config file.
|
// config file.
|
||||||
cfg := params.ConfigService.Current()
|
cfg := a.configService.Current()
|
||||||
|
|
||||||
// state is the current state of the application, as reflected in the UI.
|
// state is the current state of the application, as reflected in the UI.
|
||||||
state := new(domain.AppState)
|
state := new(domain.AppState)
|
||||||
@ -49,11 +71,11 @@ func Run(ctx context.Context, params RunParams) error {
|
|||||||
|
|
||||||
ui, err := terminal.StartUI(ctx, terminal.StartParams{
|
ui, err := terminal.StartUI(ctx, terminal.StartParams{
|
||||||
EventBus: eventBus,
|
EventBus: eventBus,
|
||||||
Screen: params.Screen,
|
Screen: a.screen,
|
||||||
ClipboardAvailable: params.ClipboardAvailable,
|
ClipboardAvailable: a.clipboardAvailable,
|
||||||
ConfigFilePath: params.ConfigFilePath,
|
ConfigFilePath: a.configFilePath,
|
||||||
BuildInfo: params.BuildInfo,
|
BuildInfo: a.buildInfo,
|
||||||
Logger: logger.With("component", "ui"),
|
Logger: a.logger.With("component", "ui"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("start terminal user interface: %w", err)
|
return fmt.Errorf("start terminal user interface: %w", err)
|
||||||
@ -71,7 +93,7 @@ func Run(ctx context.Context, params RunParams) error {
|
|||||||
// non-test code is pretty low.
|
// non-test code is pretty low.
|
||||||
emptyUI := func() { ui.SetState(domain.AppState{}) }
|
emptyUI := func() { ui.SetState(domain.AppState{}) }
|
||||||
|
|
||||||
containerClient, err := container.NewClient(ctx, params.DockerClient, logger.With("component", "container_client"))
|
containerClient, err := container.NewClient(ctx, a.dockerClient, a.logger.With("component", "container_client"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("create container client: %w", err)
|
err = fmt.Errorf("create container client: %w", err)
|
||||||
|
|
||||||
@ -106,7 +128,7 @@ func Run(ctx context.Context, params RunParams) error {
|
|||||||
TLSKeyPath: tlsKeyPath,
|
TLSKeyPath: tlsKeyPath,
|
||||||
StreamKey: mediaserver.StreamKey(cfg.Sources.MediaServer.StreamKey),
|
StreamKey: mediaserver.StreamKey(cfg.Sources.MediaServer.StreamKey),
|
||||||
ContainerClient: containerClient,
|
ContainerClient: containerClient,
|
||||||
Logger: logger.With("component", "mediaserver"),
|
Logger: a.logger.With("component", "mediaserver"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("create mediaserver: %w", err)
|
err = fmt.Errorf("create mediaserver: %w", err)
|
||||||
@ -120,7 +142,7 @@ func Run(ctx context.Context, params RunParams) error {
|
|||||||
repl := replicator.StartActor(ctx, replicator.StartActorParams{
|
repl := replicator.StartActor(ctx, replicator.StartActorParams{
|
||||||
SourceURL: srv.RTMPInternalURL(),
|
SourceURL: srv.RTMPInternalURL(),
|
||||||
ContainerClient: containerClient,
|
ContainerClient: containerClient,
|
||||||
Logger: logger.With("component", "replicator"),
|
Logger: a.logger.With("component", "replicator"),
|
||||||
})
|
})
|
||||||
defer repl.Close()
|
defer repl.Close()
|
||||||
|
|
||||||
@ -146,16 +168,16 @@ func Run(ctx context.Context, params RunParams) error {
|
|||||||
|
|
||||||
eventBus.Send(event.MediaServerStartedEvent{RTMPURL: srv.RTMPURL(), RTMPSURL: srv.RTMPSURL()})
|
eventBus.Send(event.MediaServerStartedEvent{RTMPURL: srv.RTMPURL(), RTMPSURL: srv.RTMPSURL()})
|
||||||
}
|
}
|
||||||
case <-params.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, ok := <-ui.C():
|
||||||
if !ok {
|
if !ok {
|
||||||
// TODO: keep UI open until all containers have closed
|
// TODO: keep UI open until all containers have closed
|
||||||
logger.Info("UI closed")
|
a.logger.Info("UI closed")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("Command received", "cmd", cmd.Name())
|
a.logger.Debug("Command received", "cmd", cmd.Name())
|
||||||
switch c := cmd.(type) {
|
switch c := cmd.(type) {
|
||||||
case domain.CommandAddDestination:
|
case domain.CommandAddDestination:
|
||||||
newCfg := cfg
|
newCfg := cfg
|
||||||
@ -163,8 +185,8 @@ func Run(ctx context.Context, params RunParams) error {
|
|||||||
Name: c.DestinationName,
|
Name: c.DestinationName,
|
||||||
URL: c.URL,
|
URL: c.URL,
|
||||||
})
|
})
|
||||||
if err := params.ConfigService.SetConfig(newCfg); err != nil {
|
if err := a.configService.SetConfig(newCfg); err != nil {
|
||||||
logger.Error("Config update failed", "err", err)
|
a.logger.Error("Config update failed", "err", err)
|
||||||
ui.ConfigUpdateFailed(err)
|
ui.ConfigUpdateFailed(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -177,8 +199,8 @@ func Run(ctx context.Context, params RunParams) error {
|
|||||||
newCfg.Destinations = slices.DeleteFunc(newCfg.Destinations, func(dest config.Destination) bool {
|
newCfg.Destinations = slices.DeleteFunc(newCfg.Destinations, func(dest config.Destination) bool {
|
||||||
return dest.URL == c.URL
|
return dest.URL == c.URL
|
||||||
})
|
})
|
||||||
if err := params.ConfigService.SetConfig(newCfg); err != nil {
|
if err := a.configService.SetConfig(newCfg); err != nil {
|
||||||
logger.Error("Config update failed", "err", err)
|
a.logger.Error("Config update failed", "err", err)
|
||||||
ui.ConfigUpdateFailed(err)
|
ui.ConfigUpdateFailed(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -200,11 +222,11 @@ func Run(ctx context.Context, params RunParams) error {
|
|||||||
case <-uiUpdateT.C:
|
case <-uiUpdateT.C:
|
||||||
updateUI()
|
updateUI()
|
||||||
case serverState := <-srv.C():
|
case serverState := <-srv.C():
|
||||||
logger.Debug("Server state received", "state", serverState)
|
a.logger.Debug("Server state received", "state", serverState)
|
||||||
applyServerState(serverState, state)
|
applyServerState(serverState, state)
|
||||||
updateUI()
|
updateUI()
|
||||||
case replState := <-repl.C():
|
case replState := <-repl.C():
|
||||||
logger.Debug("Replicator state received", "state", replState)
|
a.logger.Debug("Replicator state received", "state", replState)
|
||||||
destErrors := applyReplicatorState(replState, state)
|
destErrors := applyReplicatorState(replState, state)
|
||||||
|
|
||||||
for _, destError := range destErrors {
|
for _, destError := range destErrors {
|
||||||
|
@ -31,10 +31,10 @@ func buildAppParams(
|
|||||||
screen tcell.SimulationScreen,
|
screen tcell.SimulationScreen,
|
||||||
screenCaptureC chan<- terminal.ScreenCapture,
|
screenCaptureC chan<- terminal.ScreenCapture,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
) app.RunParams {
|
) app.Params {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
return app.RunParams{
|
return app.Params{
|
||||||
ConfigService: configService,
|
ConfigService: configService,
|
||||||
DockerClient: dockerClient,
|
DockerClient: dockerClient,
|
||||||
Screen: &terminal.Screen{
|
Screen: &terminal.Screen{
|
||||||
|
@ -132,7 +132,7 @@ func testIntegration(t *testing.T, mediaServerConfig config.MediaServerSource) {
|
|||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := app.Run(ctx, app.RunParams{
|
require.NoError(t, app.New(app.Params{
|
||||||
ConfigService: configService,
|
ConfigService: configService,
|
||||||
DockerClient: dockerClient,
|
DockerClient: dockerClient,
|
||||||
Screen: &terminal.Screen{
|
Screen: &terminal.Screen{
|
||||||
@ -144,8 +144,7 @@ func testIntegration(t *testing.T, mediaServerConfig config.MediaServerSource) {
|
|||||||
ClipboardAvailable: false,
|
ClipboardAvailable: false,
|
||||||
BuildInfo: domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"},
|
BuildInfo: domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"},
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
})
|
}).Run(ctx))
|
||||||
require.NoError(t, err)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
@ -320,7 +319,7 @@ func TestIntegrationCustomHost(t *testing.T) {
|
|||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.NoError(t, app.Run(ctx, buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)))
|
require.NoError(t, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
@ -391,7 +390,7 @@ func TestIntegrationCustomTLSCerts(t *testing.T) {
|
|||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.NoError(t, app.Run(ctx, buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)))
|
require.NoError(t, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
@ -472,7 +471,7 @@ func TestIntegrationRestartDestination(t *testing.T) {
|
|||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.NoError(t, app.Run(ctx, buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)))
|
require.NoError(t, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
@ -609,7 +608,7 @@ func TestIntegrationStartDestinationFailed(t *testing.T) {
|
|||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.NoError(t, app.Run(ctx, buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)))
|
require.NoError(t, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
@ -682,7 +681,7 @@ func TestIntegrationDestinationValidations(t *testing.T) {
|
|||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.NoError(t, app.Run(ctx, buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)))
|
require.NoError(t, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
@ -824,7 +823,7 @@ func TestIntegrationStartupCheck(t *testing.T) {
|
|||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.NoError(t, app.Run(ctx, buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)))
|
require.NoError(t, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
@ -893,7 +892,7 @@ func TestIntegrationMediaServerError(t *testing.T) {
|
|||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.NoError(t, app.Run(ctx, buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)))
|
require.NoError(t, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
@ -934,7 +933,7 @@ func TestIntegrationDockerClientError(t *testing.T) {
|
|||||||
|
|
||||||
require.EqualError(
|
require.EqualError(
|
||||||
t,
|
t,
|
||||||
app.Run(ctx, buildAppParams(t, configService, &dockerClient, screen, screenCaptureC, logger)),
|
app.New(buildAppParams(t, configService, &dockerClient, screen, screenCaptureC, logger)).Run(ctx),
|
||||||
"create container client: network create: boom",
|
"create container client: network create: boom",
|
||||||
)
|
)
|
||||||
}()
|
}()
|
||||||
@ -974,7 +973,7 @@ func TestIntegrationDockerConnectionError(t *testing.T) {
|
|||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := app.Run(ctx, buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger))
|
err := app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx)
|
||||||
require.ErrorContains(t, err, "dial tcp: lookup docker.example.com")
|
require.ErrorContains(t, err, "dial tcp: lookup docker.example.com")
|
||||||
require.ErrorContains(t, err, "no such host")
|
require.ErrorContains(t, err, "no such host")
|
||||||
}()
|
}()
|
||||||
@ -1070,7 +1069,7 @@ func TestIntegrationCopyURLs(t *testing.T) {
|
|||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
require.NoError(t, app.Run(ctx, buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)))
|
require.NoError(t, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
|
9
main.go
9
main.go
@ -97,9 +97,7 @@ func run(ctx context.Context) error {
|
|||||||
return fmt.Errorf("read build info: %w", err)
|
return fmt.Errorf("read build info: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.Run(
|
app := app.New(app.Params{
|
||||||
ctx,
|
|
||||||
app.RunParams{
|
|
||||||
ConfigService: configService,
|
ConfigService: configService,
|
||||||
DockerClient: dockerClient,
|
DockerClient: dockerClient,
|
||||||
ClipboardAvailable: clipboardAvailable,
|
ClipboardAvailable: clipboardAvailable,
|
||||||
@ -111,8 +109,9 @@ func run(ctx context.Context) error {
|
|||||||
Date: date,
|
Date: date,
|
||||||
},
|
},
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
return app.Run(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// editConfigFile opens the config file in the user's editor.
|
// editConfigFile opens the config file in the user's editor.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user