joshuar-go-hass-agent/internal/components/preferences/worker.go

118 lines
3.8 KiB
Go

// Copyright 2024 Joshua Rich <joshua.rich@gmail.com>.
// SPDX-License-Identifier: MIT
//go:generate go run github.com/matryer/moq -out worker_mocks_test.go . Worker
package preferences
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/pelletier/go-toml/v2"
"github.com/joshuar/go-hass-agent/internal/components/validation"
)
const (
workerPrefsPrefix = "worker"
)
// CommonWorkerPrefs contains worker preferences that all workers can/should
// implement. For e.g., a toggle to completely disable the worker.
type CommonWorkerPrefs struct {
Disabled bool `toml:"disabled" comment:"Set this to true to disable all these sensors."`
}
// IsDisabled will return whether the worker is disabled.
func (p *CommonWorkerPrefs) IsDisabled() bool {
return p.Disabled
}
// Worker represents a Worker from the point of the preferences package. A
// worker has a set of default preferences returned by the DefaultPreferences
// method and an ID that uniquely identifies the worker (and its preferences
// on disk).
type Worker[T any] interface {
PreferencesID() string
DefaultPreferences() T
}
var (
ErrSaveWorkerPrefs = errors.New("error saving worker preferences")
ErrLoadWorkerPrefs = errors.New("error loading worker preferences")
)
// LoadWorker reads the given worker's preferences from file.
func LoadWorker[T any](ctx context.Context, worker Worker[T]) (*T, error) {
var (
prefs T
defaultPrefs T
)
// Get the default preferences.
defaultPrefs = worker.DefaultPreferences()
// Set the key to the worker preferences in the preferences store.
prefsKey := workerPrefsPrefix + "." + worker.PreferencesID()
// Try to retrieve any existing preferences. Use those if possible.
foundPrefs := prefsSrc.Get(prefsKey)
if foundPrefs != nil {
// Marshall the map[string]interface returned to []byte.
data, err := toml.Marshal(foundPrefs)
if err != nil {
prefs = defaultPrefs
} else {
// Unmarshal the []byte back to the proper preferences type T.
if err := toml.Unmarshal(data, &prefs); err != nil {
prefs = defaultPrefs
}
}
} else {
prefs = defaultPrefs
// Save the default preferences to the preferences source.
if err := SaveWorker(ctx, worker, prefs); err != nil {
return &prefs, fmt.Errorf("%w: %w", ErrLoadWorkerPrefs, err)
}
}
// Validate the preferences, warn and use defaults if invalid.
if err := validation.Validate.Struct(prefs); err != nil {
slog.Warn("Worker preferences are invalid, reverting to defaults.",
slog.String("worker", worker.PreferencesID()),
slog.String("problems", validation.ParseValidationErrors(err)))
// Save the default preferences to the preferences source.
if err := SaveWorker(ctx, worker, defaultPrefs); err != nil {
return &prefs, fmt.Errorf("%w: %w", ErrLoadWorkerPrefs, err)
}
return &defaultPrefs, nil
}
// Return preferences.
return &prefs, nil
}
// SaveWorker saves the given worker's preferences to file.
func SaveWorker[T any](ctx context.Context, worker Worker[T], prefs T) error {
// We can't define the structure for every possible worker beforehand, so
// use map[string]any as the structure for saving.
prefsMaps := make(map[string]any)
// Marshal the worker's prefs object into bytes, using the toml tag
// structure.
data, err := toml.Marshal(&prefs)
if err != nil {
return fmt.Errorf("%w: %w", ErrSaveWorkerPrefs, err)
}
// Unmarshal back into a map[string]any that we can save into the preferences
// file.
if err := toml.Unmarshal(data, &prefsMaps); err != nil {
return fmt.Errorf("%w: %w", ErrSaveWorkerPrefs, err)
}
// Merge the worker preferences into the preferences file.
if err := prefsSrc.Set(workerPrefsPrefix+"."+worker.PreferencesID(), prefsMaps); err != nil {
return fmt.Errorf("%w: %w", ErrSaveWorkerPrefs, err)
}
// Save the preferences.
return Save(ctx)
}