Добавил каркас приложения
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
// Package config загружает конфигурацию jellybit из TOML-файла.
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
// Config — корневая конфигурация сервиса (см. config.example.toml).
|
||||
type Config struct {
|
||||
QBittorrent QBittorrent `toml:"qbittorrent"`
|
||||
Paths Paths `toml:"paths"`
|
||||
Storage Storage `toml:"storage"`
|
||||
LLM LLM `toml:"llm"`
|
||||
Metadata Metadata `toml:"metadata"`
|
||||
Worker Worker `toml:"worker"`
|
||||
Recognition Recognition `toml:"recognition"`
|
||||
Telegram Telegram `toml:"telegram"`
|
||||
HTTP HTTP `toml:"http"`
|
||||
Log Log `toml:"log"`
|
||||
}
|
||||
|
||||
// QBittorrent — доступ к qBittorrent WebUI и раскладка путей загрузок.
|
||||
type QBittorrent struct {
|
||||
URL string `toml:"url"`
|
||||
Username string `toml:"username"`
|
||||
Password string `toml:"password"`
|
||||
Category string `toml:"category"`
|
||||
SavePath string `toml:"savepath"`
|
||||
PathMap map[string]string `toml:"path_map"`
|
||||
}
|
||||
|
||||
// Paths — хост-пути медиа-песочницы (см. docs/specs/architecture.md).
|
||||
type Paths struct {
|
||||
Downloads string `toml:"downloads"`
|
||||
Movies string `toml:"movies"`
|
||||
Series string `toml:"series"`
|
||||
}
|
||||
|
||||
// Storage — расположение БД.
|
||||
type Storage struct {
|
||||
DBPath string `toml:"db_path"`
|
||||
}
|
||||
|
||||
// LLM — провайдер распознавания (дискриминатор type).
|
||||
type LLM struct {
|
||||
Type string `toml:"type"`
|
||||
BaseURL string `toml:"base_url"`
|
||||
APIKey string `toml:"api_key"`
|
||||
Model string `toml:"model"`
|
||||
Proxy string `toml:"proxy"`
|
||||
Timeout Duration `toml:"timeout"`
|
||||
MaxRetries int `toml:"max_retries"`
|
||||
}
|
||||
|
||||
// Metadata — внешние базы метаданных (опциональны).
|
||||
type Metadata struct {
|
||||
TMDB MetadataProvider `toml:"tmdb"`
|
||||
TVDB MetadataProvider `toml:"tvdb"`
|
||||
}
|
||||
|
||||
// MetadataProvider — настройки одного провайдера метаданных.
|
||||
type MetadataProvider struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
APIKey string `toml:"api_key"`
|
||||
Proxy string `toml:"proxy"`
|
||||
Timeout Duration `toml:"timeout"`
|
||||
}
|
||||
|
||||
// Worker — параметры фонового цикла.
|
||||
type Worker struct {
|
||||
PollInterval Duration `toml:"poll_interval"`
|
||||
StuckAfter Duration `toml:"stuck_after"`
|
||||
MagnetTimeout Duration `toml:"magnet_timeout"`
|
||||
}
|
||||
|
||||
// Recognition — пороги распознавания.
|
||||
type Recognition struct {
|
||||
AutoConfidenceThreshold float64 `toml:"auto_confidence_threshold"`
|
||||
}
|
||||
|
||||
// Telegram — настройки бота (Ф5).
|
||||
type Telegram struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
Token string `toml:"token"`
|
||||
AllowedUserIDs []int64 `toml:"allowed_user_ids"`
|
||||
}
|
||||
|
||||
// HTTP — параметры веб-сервера.
|
||||
type HTTP struct {
|
||||
Listen string `toml:"listen"`
|
||||
TrustedSubnets []string `toml:"trusted_subnets"`
|
||||
}
|
||||
|
||||
// Log — параметры логирования.
|
||||
type Log struct {
|
||||
Level string `toml:"level"`
|
||||
Format string `toml:"format"`
|
||||
}
|
||||
|
||||
// Duration — time.Duration, читаемый из TOML-строки вида "5s".
|
||||
type Duration time.Duration
|
||||
|
||||
// UnmarshalText разбирает строку длительности (encoding.TextUnmarshaler).
|
||||
func (d *Duration) UnmarshalText(text []byte) error {
|
||||
v, err := time.ParseDuration(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = Duration(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Std возвращает обычный time.Duration.
|
||||
func (d Duration) Std() time.Duration { return time.Duration(d) }
|
||||
|
||||
// Default возвращает конфиг с разумными умолчаниями; значения из файла
|
||||
// перекрывают их при загрузке.
|
||||
func Default() *Config {
|
||||
return &Config{
|
||||
QBittorrent: QBittorrent{
|
||||
URL: "http://qbit:8989",
|
||||
Username: "admin",
|
||||
Category: "jellybit",
|
||||
SavePath: "/srv/media/downloads",
|
||||
},
|
||||
Paths: Paths{
|
||||
Downloads: "/srv/media/downloads",
|
||||
Movies: "/srv/media/movies",
|
||||
Series: "/srv/media/series",
|
||||
},
|
||||
Storage: Storage{DBPath: "/data/jellybit.db"},
|
||||
LLM: LLM{
|
||||
Type: "openai-compat",
|
||||
Timeout: Duration(120 * time.Second),
|
||||
MaxRetries: 3,
|
||||
},
|
||||
Metadata: Metadata{
|
||||
TMDB: MetadataProvider{Timeout: Duration(10 * time.Second)},
|
||||
TVDB: MetadataProvider{Timeout: Duration(10 * time.Second)},
|
||||
},
|
||||
Worker: Worker{
|
||||
PollInterval: Duration(5 * time.Second),
|
||||
StuckAfter: Duration(time.Hour),
|
||||
MagnetTimeout: Duration(30 * time.Minute),
|
||||
},
|
||||
Recognition: Recognition{AutoConfidenceThreshold: 0.85},
|
||||
HTTP: HTTP{Listen: ":8080"},
|
||||
Log: Log{Level: "info", Format: "json"},
|
||||
}
|
||||
}
|
||||
|
||||
// Load читает и валидирует конфиг из path.
|
||||
func Load(path string) (*Config, error) {
|
||||
cfg := Default()
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read config %q: %w", path, err)
|
||||
}
|
||||
if err := toml.Unmarshal(data, cfg); err != nil {
|
||||
return nil, fmt.Errorf("parse config %q: %w", path, err)
|
||||
}
|
||||
if err := cfg.validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid config %q: %w", path, err)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (c *Config) validate() error {
|
||||
if c.HTTP.Listen == "" {
|
||||
return errors.New("http.listen is empty")
|
||||
}
|
||||
if c.Storage.DBPath == "" {
|
||||
return errors.New("storage.db_path is empty")
|
||||
}
|
||||
if c.LLM.Type != "openai-compat" {
|
||||
return fmt.Errorf("unsupported llm.type %q (supported: openai-compat)", c.LLM.Type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user