diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 5738392a..33146d7f 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -38,56 +38,77 @@ type Agent struct { ui ui } -// CtxOption is a functional parameter that will add a value to the agent's -// context. -type CtxOption func(context.Context) context.Context +// agentOptions are the options that can be used to change the behavior of Go +// Hass Agent. +type agentOptions struct { + headless bool + forceRegister bool + ignoreHassURLs bool + registrationServer string + registrationToken string +} -// LoadCtx will "load" a context.Context with the given options (i.e. add values -// to it to be used by the agent). -func newAgentCtx(options ...CtxOption) (context.Context, context.CancelFunc) { - ctx, cancelFunc := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) +// options represents the set of run-time options for the current instance of Go +// Hass Agent. +var options agentOptions + +// Option represents a run-time option for Go Hass Agent. +type Option func(agentOptions) agentOptions - for _, option := range options { - ctx = option(ctx) //nolint:fatcontext +// setup will establish the options for this run of Go Hass Agent. +func setup(givenOptions ...Option) { + for _, option := range givenOptions { + options = option(options) } +} - return ctx, cancelFunc +// SetHeadless indicates Go Hass Agent should run without a UI. +func SetHeadless(value bool) Option { + return func(ao agentOptions) agentOptions { + ao.headless = value + return ao + } } -// SetLogger sets the given logger in the context. -func SetLogger(logger *slog.Logger) CtxOption { - return func(ctx context.Context) context.Context { - ctx = logging.ToContext(ctx, logger) - return ctx +// SetRegistrationInfo contains the information Go Hass Agent should use to +// register with Home Assistant. +func SetRegistrationInfo(server, token string, ignoreURLs bool) Option { + return func(ao agentOptions) agentOptions { + ao.ignoreHassURLs = ignoreURLs + ao.registrationServer = server + ao.registrationToken = token + + return ao } } -// SetHeadless sets the headless flag in the context. -func SetHeadless(value bool) CtxOption { - return func(ctx context.Context) context.Context { - ctx = addToContext(ctx, headlessCtxKey, value) - return ctx +// ForceRegister indicates that Go Hass Agent should ignore its current +// registration status and re-register with Home Assistant. +func SetForceRegister(value bool) Option { + return func(ao agentOptions) agentOptions { + ao.forceRegister = value + return ao } } -// SetRegistrationInfo sets registration details in the context to be used for -// registering the agent. -func SetRegistrationInfo(server, token string, ignoreURLs bool) CtxOption { - return func(ctx context.Context) context.Context { - ctx = addToContext(ctx, serverCtxKey, server) - ctx = addToContext(ctx, tokenCtxKey, token) - ctx = addToContext(ctx, ignoreURLsCtxKey, ignoreURLs) +// newCtx creates a new context for this run of Go Hass Agent. +func newCtx(logger *slog.Logger) (context.Context, context.CancelFunc) { + ctx, cancelFunc := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + ctx = logging.ToContext(ctx, logger) - return ctx - } + return ctx, cancelFunc } -// ForceRegister sets the forceregister flag in the context. -func SetForceRegister(value bool) CtxOption { - return func(ctx context.Context) context.Context { - ctx = addToContext(ctx, forceRegisterCtxKey, value) - return ctx +// newAgent creates the Agent struct. +func newAgent(ctx context.Context) *Agent { + agent := &Agent{} + + // If not running headless, set up the UI. + if !options.headless { + agent.ui = fyneui.NewFyneUI(ctx) } + + return agent } // Run is invoked when Go Hass Agent is run with the `run` command-line option @@ -95,22 +116,20 @@ func SetForceRegister(value bool) CtxOption { // //nolint:funlen //revive:disable:function-length -func Run(options ...CtxOption) error { +func Run(logger *slog.Logger, givenOptions ...Option) error { var ( wg sync.WaitGroup regWait sync.WaitGroup err error ) - ctx, cancelFunc := newAgentCtx(options...) + // Establish run-time options. + setup(givenOptions...) + // Setup context. + ctx, cancelFunc := newCtx(logger) defer cancelFunc() - - agent := &Agent{} - - // If running headless, do not set up the UI. - if !Headless(ctx) { - agent.ui = fyneui.NewFyneUI(ctx) - } + // Create struct. + agent := newAgent(ctx) // Load the preferences from file. Ignore the case where there are no // existing preferences. @@ -204,7 +223,7 @@ func Run(options ...CtxOption) error { }() // Do not run the UI loop if the agent is running in headless mode. - if !Headless(ctx) { + if !options.headless { agent.ui.DisplayTrayIcon(ctx, cancelFunc) agent.ui.Run(ctx) } @@ -217,20 +236,19 @@ func Run(options ...CtxOption) error { // Register is run when Go Hass Agent is invoked with the `register` // command-line option (i.e., `go-hass-agent register`). It will attempt to // register Go Hass Agent with Home Assistant. -func Register(options ...CtxOption) error { +func Register(logger *slog.Logger, givenOptions ...Option) error { var ( wg sync.WaitGroup err error ) - ctx, cancelFunc := newAgentCtx(options...) + // Establish run-time options. + setup(givenOptions...) + // Setup context. + ctx, cancelFunc := newCtx(logger) defer cancelFunc() - - agent := &Agent{} - // If running headless, do not set up the UI. - if !Headless(ctx) { - agent.ui = fyneui.NewFyneUI(ctx) - } + // Create struct. + agent := newAgent(ctx) if err = preferences.Load(); err != nil && !errors.Is(err, preferences.ErrLoadPreferences) { return fmt.Errorf("%w: %w", ErrAgentStart, err) @@ -254,7 +272,7 @@ func Register(options ...CtxOption) error { cancelReg() }() - if !Headless(ctx) { + if !options.headless { agent.ui.Run(regCtx) } @@ -265,8 +283,11 @@ func Register(options ...CtxOption) error { // Reset is invoked when Go Hass Agent is run with the `reset` command-line // option (i.e., `go-hass-agent reset`). -func Reset(options ...CtxOption) error { - ctx, cancelFunc := newAgentCtx(options...) +func Reset(logger *slog.Logger, givenOptions ...Option) error { + // Establish run-time options. + setup(givenOptions...) + // Setup context. + ctx, cancelFunc := newCtx(logger) defer cancelFunc() // Load the preferences so we know what we need to reset. diff --git a/internal/agent/context.go b/internal/agent/context.go deleted file mode 100644 index 1a9dd0ba..00000000 --- a/internal/agent/context.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2024 Joshua Rich -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -package agent - -import ( - "context" - - "github.com/joshuar/go-hass-agent/internal/preferences" -) - -type contextKey string - -const ( - headlessCtxKey contextKey = "headless" - forceRegisterCtxKey contextKey = "forceregister" - ignoreURLsCtxKey contextKey = "ignoreURLS" - serverCtxKey contextKey = "server" - tokenCtxKey contextKey = "token" -) - -func addToContext[T any](ctx context.Context, key contextKey, value T) context.Context { - newCtx := context.WithValue(ctx, key, value) - - return newCtx -} - -func Headless(ctx context.Context) bool { - headless, ok := ctx.Value(headlessCtxKey).(bool) - if !ok { - return false - } - - return headless -} - -func ForceRegister(ctx context.Context) bool { - forceregister, ok := ctx.Value(forceRegisterCtxKey).(bool) - if !ok { - return false - } - - return forceregister -} - -func IgnoreURLs(ctx context.Context) bool { - ignoreURLs, ok := ctx.Value(ignoreURLsCtxKey).(bool) - if !ok { - return false - } - - return ignoreURLs -} - -func Server(ctx context.Context) string { - server, ok := ctx.Value(serverCtxKey).(string) - if !ok { - return preferences.DefaultServer - } - - return server -} - -func Token(ctx context.Context) string { - token, ok := ctx.Value(tokenCtxKey).(string) - if !ok { - return preferences.DefaultSecret - } - - return token -} diff --git a/internal/agent/notifications.go b/internal/agent/notifications.go index 25778086..4b0be15d 100644 --- a/internal/agent/notifications.go +++ b/internal/agent/notifications.go @@ -19,7 +19,7 @@ import ( // received notifications will be dipslayed on the device running the agent. func runNotificationsWorker(ctx context.Context, agentUI ui) { // Don't run if agent is running headless.\ - if Headless(ctx) { + if options.headless { return } diff --git a/internal/agent/register.go b/internal/agent/register.go index 7c5b7b7d..453505f0 100644 --- a/internal/agent/register.go +++ b/internal/agent/register.go @@ -19,19 +19,19 @@ import ( var ErrUserCancelledRegistration = errors.New("user canceled registration") func checkRegistration(ctx context.Context, agentUI ui) error { - if preferences.Registered() && !ForceRegister(ctx) { + if preferences.Registered() && !options.forceRegister { return nil } // Set the registration options as passed in from command-line. registrationOptions := &preferences.Registration{ - Server: Server(ctx), - Token: Token(ctx), - IgnoreHassURLs: IgnoreURLs(ctx), + Server: options.registrationServer, + Token: options.registrationToken, + IgnoreHassURLs: options.ignoreHassURLs, } // If not headless, present a UI for the user to configure options. - if !Headless(ctx) { + if options.headless { userInputDoneCh := agentUI.DisplayRegistrationWindow(ctx, registrationOptions) if canceled := <-userInputDoneCh; canceled { return ErrUserCancelledRegistration @@ -54,7 +54,7 @@ func checkRegistration(ctx context.Context, agentUI ui) error { } // If the registration was forced, reset the sensor registry. - if ForceRegister(ctx) { + if options.forceRegister { if err := registry.Reset(); err != nil { logging.FromContext(ctx).Warn("Problem resetting registry.", slog.Any("error", err)) } diff --git a/internal/cli/registerCmd.go b/internal/cli/registerCmd.go index f4eba142..a7f57387 100644 --- a/internal/cli/registerCmd.go +++ b/internal/cli/registerCmd.go @@ -23,11 +23,10 @@ func (r *RegisterCmd) Help() string { } func (r *RegisterCmd) Run(opts *CmdOpts) error { - if err := agent.Register( + if err := agent.Register(opts.Logger, agent.SetHeadless(opts.Headless), agent.SetRegistrationInfo(r.Server, r.Token, r.IgnoreURLs), agent.SetForceRegister(r.Force), - agent.SetLogger(opts.Logger), ); err != nil { return fmt.Errorf("failed to run: %w", err) } diff --git a/internal/cli/resetCmd.go b/internal/cli/resetCmd.go index 226d7a02..2ce347a1 100644 --- a/internal/cli/resetCmd.go +++ b/internal/cli/resetCmd.go @@ -26,9 +26,8 @@ func (r *ResetCmd) Run(opts *CmdOpts) error { var errs error // Reset agent. - if err := agent.Reset( + if err := agent.Reset(opts.Logger, agent.SetHeadless(opts.Headless), - agent.SetLogger(opts.Logger), ); err != nil { errs = errors.Join(fmt.Errorf("agent reset failed: %w", err)) } diff --git a/internal/cli/runCmd.go b/internal/cli/runCmd.go index f35b2308..61928515 100644 --- a/internal/cli/runCmd.go +++ b/internal/cli/runCmd.go @@ -18,9 +18,8 @@ func (r *RunCmd) Help() string { } func (r *RunCmd) Run(opts *CmdOpts) error { - if err := agent.Run( + if err := agent.Run(opts.Logger, agent.SetHeadless(opts.Headless), - agent.SetLogger(opts.Logger), ); err != nil { return fmt.Errorf("failed to run: %w", err) }