119 lines
3.3 KiB
Go
119 lines
3.3 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Config represents the top-level configuration for NaviWatcher.
|
|
type Config struct {
|
|
Server ServerConfig `yaml:"server"`
|
|
Navidrome NavidromeConfig `yaml:"navidrome"`
|
|
MusicBrainz MusicBrainzConfig `yaml:"musicbrainz"`
|
|
Telegram TelegramConfig `yaml:"telegram"`
|
|
Scanner ScannerConfig `yaml:"scanner"`
|
|
}
|
|
|
|
// ServerConfig holds HTTP server settings.
|
|
type ServerConfig struct {
|
|
Host string `yaml:"host"`
|
|
Port int `yaml:"port"`
|
|
Username string `yaml:"username"`
|
|
Password string `yaml:"password"`
|
|
}
|
|
|
|
// NavidromeConfig holds Subsonic API connection details.
|
|
type NavidromeConfig struct {
|
|
URL string `yaml:"url"`
|
|
User string `yaml:"user"`
|
|
Password string `yaml:"password"`
|
|
}
|
|
|
|
// MusicBrainzConfig holds MusicBrainz API settings.
|
|
type MusicBrainzConfig struct {
|
|
UserAgent string `yaml:"user_agent"`
|
|
CacheTTL time.Duration `yaml:"cache_ttl"`
|
|
}
|
|
|
|
// TelegramConfig holds Telegram bot notification settings.
|
|
type TelegramConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Token string `yaml:"token"`
|
|
ChatID string `yaml:"chat_id"`
|
|
CronSchedule string `yaml:"cron_schedule"`
|
|
}
|
|
|
|
// ScannerConfig holds scanner engine parameters.
|
|
//
|
|
// Bootleg/Compilation filtering is intentionally unconditional: bootlegs,
|
|
// promotions, and pseudo-releases are always excluded (musicbrainz/api.go),
|
|
// and compilations are always included. These are not user-toggleable, so
|
|
// there are no corresponding config fields.
|
|
type ScannerConfig struct {
|
|
FuzzyThreshold float64 `yaml:"fuzzy_threshold"`
|
|
}
|
|
|
|
// LoadConfig reads a YAML file from path, parses it, applies defaults,
|
|
// and validates the configuration.
|
|
func LoadConfig(path string) (*Config, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read config file: %w", err)
|
|
}
|
|
|
|
var cfg Config
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parse config: %w", err)
|
|
}
|
|
|
|
applyDefaults(&cfg)
|
|
|
|
if err := validate(&cfg); err != nil {
|
|
return nil, fmt.Errorf("validate config: %w", err)
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
// applyDefaults sets zero-value defaults for optional fields.
|
|
func applyDefaults(cfg *Config) {
|
|
if cfg.Server.Host == "" {
|
|
cfg.Server.Host = "0.0.0.0"
|
|
}
|
|
if cfg.Server.Port == 0 {
|
|
cfg.Server.Port = 8080
|
|
}
|
|
if cfg.Scanner.FuzzyThreshold == 0 {
|
|
cfg.Scanner.FuzzyThreshold = 0.85
|
|
}
|
|
if cfg.MusicBrainz.CacheTTL == 0 {
|
|
cfg.MusicBrainz.CacheTTL = 24 * time.Hour
|
|
}
|
|
}
|
|
|
|
// validate checks that required fields are set and values are within acceptable ranges.
|
|
func validate(cfg *Config) error {
|
|
if cfg.Navidrome.URL == "" {
|
|
return fmt.Errorf("navidrome.url is required")
|
|
}
|
|
if cfg.Navidrome.User == "" {
|
|
return fmt.Errorf("navidrome.user is required")
|
|
}
|
|
if cfg.Navidrome.Password == "" {
|
|
return fmt.Errorf("navidrome.password is required")
|
|
}
|
|
if cfg.Server.Port < 1 || cfg.Server.Port > 65535 {
|
|
return fmt.Errorf("server.port must be between 1 and 65535, got %d", cfg.Server.Port)
|
|
}
|
|
if cfg.Scanner.FuzzyThreshold < 0.0 || cfg.Scanner.FuzzyThreshold > 1.0 {
|
|
return fmt.Errorf("scanner.fuzzy_threshold must be between 0.0 and 1.0, got %f", cfg.Scanner.FuzzyThreshold)
|
|
}
|
|
if cfg.MusicBrainz.UserAgent == "" {
|
|
return fmt.Errorf("musicbrainz.user_agent is required")
|
|
}
|
|
return nil
|
|
}
|