- Fix 10 err113 violations: extract dynamic errors to package-level sentinels - Fix 12 errcheck violations: handle unchecked error returns in player, metadata, and config packages - Fix 4 revive stutter warnings: rename player.PlayerState to player.State, player.PlayerVolume to player.Volume, queue.QueueTrack to queue.Track, queue.QueueState to queue.State - Fix 2 staticcheck SA4001: simplify *&x to x in assets handler - Fix 5 unused constants: remove dead AudioFileType iota block in models - Fix gci/gofumpt/wsl formatting issues across multiple files - Add gofumpt module-path setting to .golangci.yml for correct import grouping - Fix player test: gate integration test behind YELLOWJACKET_INTEGRATION env var instead of only skipping in CI, and replace t.Errorf+t.Failed with t.Fatalf - Remove continue-on-error from golangci-lint CI step so linting is now required
137 lines
3.2 KiB
Go
137 lines
3.2 KiB
Go
// Package config manages application configuration persistence.
|
|
package config
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
|
|
"yellowjacket/backend/library"
|
|
"yellowjacket/backend/system"
|
|
)
|
|
|
|
// Config represents the application configuration.
|
|
type Config struct {
|
|
ctx context.Context
|
|
logger *slog.Logger
|
|
serveMux *http.ServeMux
|
|
filePath string // required
|
|
Library *library.Config `form:"Library" schema:"library,required"`
|
|
}
|
|
|
|
// NewConfig creates a new config by loading it from disk.
|
|
func NewConfig(logger *slog.Logger) (*Config, error) {
|
|
confDir, err := system.GetUserConfigDirPath()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not get user config directory: %w", err)
|
|
}
|
|
|
|
conf := &Config{
|
|
filePath: path.Join(confDir, "config.toml"),
|
|
serveMux: http.NewServeMux(),
|
|
}
|
|
conf.logger = logger.WithGroup("config").With("config", conf)
|
|
conf.serveMux.HandleFunc("/", conf.handle)
|
|
|
|
if err := conf.Load(); err != nil {
|
|
return nil, fmt.Errorf("could not load config: %w", err)
|
|
}
|
|
|
|
if err := conf.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid config: %w", err)
|
|
}
|
|
|
|
return conf, nil
|
|
}
|
|
|
|
// Validate returns errors if there is a breaking issue with the config.
|
|
func (c *Config) Validate() error {
|
|
var configErrs error
|
|
|
|
if c.Library != nil {
|
|
if len(c.Library.DirectoryPath) != 0 {
|
|
if err := c.Library.Validate(); err != nil {
|
|
configErrs = errors.Join(configErrs, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if configErrs != nil {
|
|
return fmt.Errorf("one or more config parts are invalid: %w", configErrs)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Load reads and parses the config file from disk.
|
|
func (c *Config) Load() error {
|
|
if _, err := os.Stat(c.filePath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
c.logger.Debug("no config file exists, creating empty config")
|
|
|
|
if err := c.Save(); err != nil {
|
|
return fmt.Errorf(
|
|
"could not save empty config to file (%s): %w",
|
|
c.filePath,
|
|
err,
|
|
)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("could not get file info (%s): %w", c.filePath, err)
|
|
}
|
|
}
|
|
|
|
// read in the file
|
|
confFileData, err := os.ReadFile(c.filePath)
|
|
if err != nil {
|
|
return fmt.Errorf("problem reading config file %s: %w", c.filePath, err)
|
|
}
|
|
|
|
// parse it into the config struct
|
|
_, err = toml.Decode(string(confFileData), c)
|
|
if err != nil {
|
|
return fmt.Errorf("problem parsing config file %s: %w", c.filePath, err)
|
|
}
|
|
|
|
// validate the config
|
|
if err = c.Validate(); err != nil {
|
|
return fmt.Errorf("invalid config file at %s: %w", c.filePath, err)
|
|
}
|
|
|
|
c.logger.Debug("loaded config file", "file", c.filePath)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Save writes the config to disk.
|
|
func (c *Config) Save() error {
|
|
if err := c.Validate(); err != nil {
|
|
return fmt.Errorf("invalid config: %w", err)
|
|
}
|
|
|
|
confFileData, err := toml.Marshal(c)
|
|
if err != nil {
|
|
return fmt.Errorf("could not marshal config struct: %w", err)
|
|
}
|
|
|
|
err = os.WriteFile(c.filePath, confFileData, os.FileMode(int(0o666)))
|
|
if err != nil {
|
|
return fmt.Errorf("could not write config file (%s): %w", c.filePath, err)
|
|
}
|
|
|
|
c.logger.Debug("saved config to file", "file", c.filePath)
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetContext sets the Wails runtime context for event emission.
|
|
func (c *Config) SetContext(ctx context.Context) {
|
|
c.ctx = ctx
|
|
}
|