Skip to content

Commit

Permalink
refactor(components/preferences): ♻️ preferences refactor
Browse files Browse the repository at this point in the history
- define an Init method called when agent is started
- define a way to load specific parts of the preferences and wrappers for this
- combine setting and saving preferences
- worker preference management no longer requires a context
  • Loading branch information
joshuar committed Jan 26, 2025
1 parent a20f509 commit 72c56f7
Show file tree
Hide file tree
Showing 58 changed files with 258 additions and 335 deletions.
1 change: 1 addition & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ linters-settings:
gosec:
excludes:
- G204
- G115
perfsprint:
errorf: false
strconcat: false
Expand Down
26 changes: 12 additions & 14 deletions internal/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ type Agent struct {
}

// newAgent creates the Agent struct.
func newAgent(ctx context.Context, tracker fyneui.Tracker) *Agent {
func newAgent(ctx context.Context, headless bool, tracker fyneui.Tracker) *Agent {
agent := &Agent{
logger: logging.FromContext(ctx).WithGroup("agent"),
}

// If not running headless, set up the UI.
if !preferences.Headless() {
if !headless {
agent.ui = fyneui.NewFyneUI(ctx, tracker)
}

Expand All @@ -53,9 +53,7 @@ func newAgent(ctx context.Context, tracker fyneui.Tracker) *Agent {

// Run is invoked when Go Hass Agent is run with the `run` command-line option
// (i.e., `go-hass-agent run`).
//
//nolint:funlen
func Run(ctx context.Context, dataCh chan any, tracker fyneui.Tracker) error {
func Run(ctx context.Context, headless bool, dataCh chan any, tracker fyneui.Tracker) error {
var (
wg sync.WaitGroup
regWait sync.WaitGroup
Expand All @@ -68,14 +66,14 @@ func Run(ctx context.Context, dataCh chan any, tracker fyneui.Tracker) error {
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()

agent := newAgent(ctx, tracker)
agent := newAgent(ctx, headless, tracker)

regWait.Add(1)

go func() {
defer regWait.Done()
// Check if the agent is registered. If not, start a registration flow.
if err = checkRegistration(ctx, agent.ui); err != nil {
if err = checkRegistration(ctx, headless, agent.ui); err != nil {
agent.logger.Error("Error checking registration status.", slog.Any("error", err))
cancelFunc()
}
Expand Down Expand Up @@ -144,12 +142,12 @@ func Run(ctx context.Context, dataCh chan any, tracker fyneui.Tracker) error {
// Listen for notifications from Home Assistant.
go func() {
defer wg.Done()
runNotificationsWorker(workerCtx, agent.ui)
runNotificationsWorker(workerCtx, headless, agent.ui)
}()
}()

// Do not run the UI loop if the agent is running in headless mode.
if !preferences.Headless() {
if !headless {
agent.ui.DisplayTrayIcon(ctx, cancelFunc)
agent.ui.Run(ctx)
}
Expand All @@ -162,10 +160,10 @@ func Run(ctx context.Context, dataCh chan any, tracker fyneui.Tracker) 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(ctx context.Context) error {
func Register(ctx context.Context, headless bool) error {
var wg sync.WaitGroup

agent := newAgent(ctx, nil)
agent := newAgent(ctx, headless, nil)

regCtx, cancelReg := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
go func() {
Expand All @@ -178,14 +176,14 @@ func Register(ctx context.Context) error {
go func() {
defer wg.Done()

if err := checkRegistration(regCtx, agent.ui); err != nil {
if err := checkRegistration(regCtx, headless, agent.ui); err != nil {
agent.logger.Error("Error checking registration status", slog.Any("error", err))
}

cancelReg()
}()

if !preferences.Headless() {
if !headless {
agent.ui.Run(regCtx)
}

Expand All @@ -197,7 +195,7 @@ func Register(ctx context.Context) error {
// Reset is invoked when Go Hass Agent is run with the `reset` command-line
// option (i.e., `go-hass-agent reset`).
func Reset(ctx context.Context) error {
agent := newAgent(ctx, nil)
agent := newAgent(ctx, true, nil)
// If MQTT is enabled, reset any saved MQTT config.
if preferences.MQTTEnabled() {
if err := resetMQTTWorkers(ctx); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions internal/agent/agentsensor/connection_latency.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,15 @@ func (w *ConnectionLatencySensorWorker) Start(ctx context.Context) (<-chan senso
return sensorCh, nil
}

func NewConnectionLatencySensorWorker(ctx context.Context) *ConnectionLatencySensorWorker {
func NewConnectionLatencySensorWorker(_ context.Context) *ConnectionLatencySensorWorker {
worker := &ConnectionLatencySensorWorker{
client: resty.New().
SetTimeout(connectionLatencyTimeout).
EnableTrace(),
endpoint: preferences.RestAPIURL(),
}

prefs, err := preferences.LoadWorker(ctx, worker)
prefs, err := preferences.LoadWorker(worker)
if err != nil {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion internal/agent/agentsensor/external_ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func NewExternalIPUpdaterWorker(ctx context.Context) *ExternalIPWorker {
With(slog.String("worker", externalIPWorkerID)),
}

prefs, err := preferences.LoadWorker(ctx, worker)
prefs, err := preferences.LoadWorker(worker)
if err != nil {
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions internal/agent/agentsensor/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ func (w *VersionWorker) Sensors(_ context.Context) ([]sensor.Entity, error) {
return []sensor.Entity{newVersionSensor()}, nil
}

func NewVersionWorker(ctx context.Context) *VersionWorker {
func NewVersionWorker(_ context.Context) *VersionWorker {
worker := &VersionWorker{}

prefs, err := preferences.LoadWorker(ctx, worker)
prefs, err := preferences.LoadWorker(worker)
if err != nil {
return nil
}
Expand Down
20 changes: 3 additions & 17 deletions internal/agent/mqtt.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,22 +59,12 @@ type mqttEntities struct {
cameras []*mqtthass.CameraEntity
}

// setupMQTTCtx will load a context with MQTT preferences and device configuration.
func setupMQTTCtx(ctx context.Context) context.Context {
// Add MQTT preferences to context.
ctx = preferences.MQTTPrefsToCtx(ctx)
// Get MQTT device and add to context.
ctx = preferences.MQTTDeviceToCtx(ctx)

return ctx
}

// createMQTTWorkers returns a slice of MQTT workers, including any custom
// command workers and OS-specific workers.
func createMQTTWorkers(ctx context.Context) []MQTTWorker {
var workers []MQTTWorker
// Set up custom MQTT commands worker.
customCommandsWorker, err := commands.NewCommandsWorker(ctx, preferences.MQTTDeviceFromFromCtx(ctx))
customCommandsWorker, err := commands.NewCommandsWorker(ctx, preferences.MQTTDevice())
if err != nil {
if !errors.Is(err, commands.ErrNoCommands) {
logging.FromContext(ctx).Warn("Could not setup custom MQTT commands.",
Expand Down Expand Up @@ -104,8 +94,6 @@ func processMQTTWorkers(ctx context.Context) {
return
}

// Get the MQTT preferences and device.
ctx = setupMQTTCtx(ctx)
// Create the workers.
workers := createMQTTWorkers(ctx)
// Add the subscriptions and configs from the workers.
Expand All @@ -116,7 +104,7 @@ func processMQTTWorkers(ctx context.Context) {
}
// Create a new connection to the MQTT broker, publish subscriptions and
// configs.
client, err := mqttapi.NewClient(ctx, preferences.MQTTPrefsFromFromCtx(ctx), subscriptions, configs)
client, err := mqttapi.NewClient(ctx, preferences.MQTT(), subscriptions, configs)
if err != nil {
logging.FromContext(ctx).Error("Could not connect to MQTT.",
slog.Any("error", err))
Expand Down Expand Up @@ -147,16 +135,14 @@ func resetMQTTWorkers(ctx context.Context) error {
err error
)

// Get the MQTT preferences and device.
ctx = setupMQTTCtx(ctx)
// Create the workers.
workers := createMQTTWorkers(ctx)

for _, worker := range workers {
configs = append(configs, worker.Configs()...)
}

client, err := mqttapi.NewClient(ctx, preferences.MQTTPrefsFromFromCtx(ctx), nil, nil)
client, err := mqttapi.NewClient(ctx, preferences.MQTT(), nil, nil)
if err != nil {
return fmt.Errorf("could not connect to MQTT: %w", err)
}
Expand Down
15 changes: 8 additions & 7 deletions internal/agent/mqtt_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,10 @@ func setupOSMQTTWorker(ctx context.Context) MQTTWorker {
msgs: make(chan *mqttapi.Msg),
},
}
mqttDevice := preferences.MQTTDevice()

// Add the power controls (suspend, resume, poweroff, etc.).
powerEntities, err := power.NewPowerControl(ctx, preferences.MQTTDeviceFromFromCtx(ctx))
powerEntities, err := power.NewPowerControl(ctx, mqttDevice)
if err != nil {
logging.FromContext(ctx).Warn("Could not create power controls.",
slog.Any("error", err))
Expand All @@ -132,7 +133,7 @@ func setupOSMQTTWorker(ctx context.Context) MQTTWorker {
}

// Add inhibit controls.
inhibitEntities, err := power.NewInhibitControl(ctx, mqttController.Msgs(), preferences.MQTTDeviceFromFromCtx(ctx))
inhibitEntities, err := power.NewInhibitControl(ctx, mqttController.Msgs(), mqttDevice)
if err != nil {
logging.FromContext(ctx).Warn("Could not create inhibit control.",
slog.Any("error", err))
Expand All @@ -141,29 +142,29 @@ func setupOSMQTTWorker(ctx context.Context) MQTTWorker {
}

// Add the screen lock controls.
screenControls, err := power.NewScreenLockControl(ctx, preferences.MQTTDeviceFromFromCtx(ctx))
screenControls, err := power.NewScreenLockControl(ctx, mqttDevice)
if err != nil {
logging.FromContext(ctx).Warn("Could not create screen lock controls.",
slog.Any("error", err))
} else {
mqttController.buttons = append(mqttController.buttons, screenControls...)
}
// Add the volume controls.
volEntity, muteEntity := media.VolumeControl(ctx, mqttController.Msgs(), preferences.MQTTDeviceFromFromCtx(ctx))
volEntity, muteEntity := media.VolumeControl(ctx, mqttController.Msgs(), mqttDevice)
if volEntity != nil && muteEntity != nil {
mqttController.numbers = append(mqttController.numbers, volEntity)
mqttController.switches = append(mqttController.switches, muteEntity)
}
// Add media control.
mprisEntity, err := media.MPRISControl(ctx, preferences.MQTTDeviceFromFromCtx(ctx), mqttController.Msgs())
mprisEntity, err := media.MPRISControl(ctx, mqttDevice, mqttController.Msgs())
if err != nil {
logging.FromContext(ctx).Warn("Could not activate MPRIS controller.",
slog.Any("error", err))
} else {
mqttController.sensors = append(mqttController.sensors, mprisEntity)
}
// Add camera control.
cameraEntities, err := media.NewCameraControl(ctx, mqttController.Msgs(), preferences.MQTTDeviceFromFromCtx(ctx))
cameraEntities, err := media.NewCameraControl(ctx, mqttController.Msgs(), mqttDevice)
if err != nil {
logging.FromContext(ctx).Warn("Could not activate Camera controller.",
slog.Any("error", err))
Expand All @@ -176,7 +177,7 @@ func setupOSMQTTWorker(ctx context.Context) MQTTWorker {
}

// Add the D-Bus command action.
dbusCmdController, err := system.NewDBusCommandSubscription(ctx, preferences.MQTTDeviceFromFromCtx(ctx))
dbusCmdController, err := system.NewDBusCommandSubscription(ctx, mqttDevice)
if err != nil {
logging.FromContext(ctx).Warn("Could not activate D-Bus commands controller.",
slog.Any("error", err))
Expand Down
4 changes: 2 additions & 2 deletions internal/agent/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import (
// runNotificationsWorker will run a goroutine that is listening for
// notification messages from Home Assistant on a websocket connection. Any
// received notifications will be dipslayed on the device running the agent.
func runNotificationsWorker(ctx context.Context, agentUI ui) {
func runNotificationsWorker(ctx context.Context, headless bool, agentUI ui) {
// Don't run if agent is running headless.
if preferences.Headless() {
if headless {
return
}

Expand Down
4 changes: 2 additions & 2 deletions internal/agent/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
// command-line and then checks to see if the agent needs to register to Home
// Assistant. If it does, it will perform the registration via either a
// graphical (user-prompted) or non-graphical (automatic) process.
func checkRegistration(ctx context.Context, agentUI ui) error {
func checkRegistration(ctx context.Context, headless bool, agentUI ui) error {
// Retrieve request options passed on command-line from context.
request := preferences.RegistrationFromCtx(ctx)
if request == nil {
Expand All @@ -33,7 +33,7 @@ func checkRegistration(ctx context.Context, agentUI ui) error {
}

// If not headless, present a UI for the user to configure options.
if !preferences.Headless() {
if !headless {
userInputDoneCh := agentUI.DisplayRegistrationWindow(ctx, request)
if canceled := <-userInputDoneCh; canceled {
return errors.New("user canceled registration")
Expand Down
23 changes: 6 additions & 17 deletions internal/agent/ui/fyneUI/fyneUI.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (i *FyneUI) DisplayTrayIcon(ctx context.Context, cancelFunc context.CancelF
// Preferences/Settings items.
menuItemAppPrefs := fyne.NewMenuItem("App Settings",
func() {
i.agentSettingsWindow(ctx).Show()
i.agentSettingsWindow().Show()
})
menuItemFynePrefs := fyne.NewMenuItem("Fyne Settings",
func() {
Expand Down Expand Up @@ -200,11 +200,6 @@ func (i *FyneUI) DisplayRegistrationWindow(ctx context.Context, prefs *preferenc
func (i *FyneUI) aboutWindow(ctx context.Context) fyne.Window {
var widgets []fyne.CanvasObject

if err := preferences.Load(ctx); err != nil {
logging.FromContext(ctx).Error("Could not start sensor controller.", slog.Any("error", err))
return nil
}

icon := canvas.NewImageFromResource(&ui.TrayIcon{})
icon.FillMode = canvas.ImageFillOriginal

Expand Down Expand Up @@ -250,31 +245,25 @@ func (i *FyneUI) fyneSettingsWindow() fyne.Window {

// agentSettingsWindow creates a window for changing settings related to the
// agent functionality. Most of these settings will be optional.
func (i *FyneUI) agentSettingsWindow(ctx context.Context) fyne.Window {
func (i *FyneUI) agentSettingsWindow() fyne.Window {
var allFormItems []*widget.FormItem

if err := preferences.Load(ctx); err != nil {
i.logger.Error("Could not start sensor controller.",
slog.Any("error", err))
return nil
}

mqttPrefs := preferences.MQTTPrefsFromFromCtx(preferences.MQTTPrefsToCtx(ctx))
mqttPrefs := preferences.MQTT()
// Generate a form of MQTT preferences.
allFormItems = append(allFormItems, mqttConfigItems(mqttPrefs)...)

window := i.app.NewWindow("App Preferences")
settingsForm := widget.NewForm(allFormItems...)
settingsForm.OnSubmit = func() {
// Set the new MQTT preferences.
preferences.SetPreferences(
err := preferences.Set(
preferences.SetMQTTEnabled(mqttPrefs.MQTTEnabled),
preferences.SetMQTTServer(mqttPrefs.MQTTServer),
preferences.SetMQTTUser(mqttPrefs.MQTTUser),
preferences.SetMQTTPassword(mqttPrefs.MQTTPassword),
)
// Save the new MQTT preferences to file.
if err := preferences.Save(ctx); err != nil {
if err != nil {
dialog.ShowError(err, window)
i.logger.Error("Could note save preferences.", slog.Any("error", err))
} else {
Expand Down Expand Up @@ -470,7 +459,7 @@ func (i *FyneUI) registrationFields(prefs *preferences.Registration) []*widget.F

// mqttConfigItems generates a list of for item widgets for configuring the
// agent to use an MQTT for pub/sub functionality.
func mqttConfigItems(prefs *preferences.MQTT) []*widget.FormItem {
func mqttConfigItems(prefs *preferences.MQTTPreferences) []*widget.FormItem {
serverEntry := configEntry(&prefs.MQTTServer, false)
serverEntry.Validator = uriValidator()
serverEntry.Disable()
Expand Down
Loading

0 comments on commit 72c56f7

Please sign in to comment.