node build fixed
This commit is contained in:
439
seanime-2.9.10/internal/core/config.go
Normal file
439
seanime-2.9.10/internal/core/config.go
Normal file
@@ -0,0 +1,439 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"seanime/internal/constants"
|
||||
"seanime/internal/util"
|
||||
"strconv"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Version string
|
||||
Server struct {
|
||||
Host string
|
||||
Port int
|
||||
Offline bool
|
||||
UseBinaryPath bool // Makes $SEANIME_WORKING_DIR point to the binary's directory
|
||||
Systray bool
|
||||
DoHUrl string
|
||||
Password string
|
||||
}
|
||||
Database struct {
|
||||
Name string
|
||||
}
|
||||
Web struct {
|
||||
AssetDir string
|
||||
}
|
||||
Logs struct {
|
||||
Dir string
|
||||
}
|
||||
Cache struct {
|
||||
Dir string
|
||||
TranscodeDir string
|
||||
}
|
||||
Offline struct {
|
||||
Dir string
|
||||
AssetDir string
|
||||
}
|
||||
Manga struct {
|
||||
DownloadDir string
|
||||
LocalDir string
|
||||
}
|
||||
Data struct { // Hydrated after config is loaded
|
||||
AppDataDir string
|
||||
WorkingDir string
|
||||
}
|
||||
Extensions struct {
|
||||
Dir string
|
||||
}
|
||||
Anilist struct {
|
||||
ClientID string
|
||||
}
|
||||
Experimental struct {
|
||||
MainServerTorrentStreaming bool
|
||||
}
|
||||
}
|
||||
|
||||
type ConfigOptions struct {
|
||||
DataDir string // The path to the Seanime data directory, if any
|
||||
OnVersionChange []func(oldVersion string, newVersion string)
|
||||
EmbeddedLogo []byte // The embedded logo
|
||||
IsDesktopSidecar bool // Run as the desktop sidecar
|
||||
}
|
||||
|
||||
// NewConfig initializes the config
|
||||
func NewConfig(options *ConfigOptions, logger *zerolog.Logger) (*Config, error) {
|
||||
|
||||
logger.Debug().Msg("app: Initializing config")
|
||||
|
||||
// Set Seanime's environment variables
|
||||
if os.Getenv("SEANIME_DATA_DIR") != "" {
|
||||
options.DataDir = os.Getenv("SEANIME_DATA_DIR")
|
||||
}
|
||||
|
||||
defaultHost := "127.0.0.1"
|
||||
defaultPort := 43211
|
||||
|
||||
if os.Getenv("SEANIME_SERVER_HOST") != "" {
|
||||
defaultHost = os.Getenv("SEANIME_SERVER_HOST")
|
||||
}
|
||||
if os.Getenv("SEANIME_SERVER_PORT") != "" {
|
||||
var err error
|
||||
defaultPort, err = strconv.Atoi(os.Getenv("SEANIME_SERVER_PORT"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid SEANIME_SERVER_PORT environment variable: %s", os.Getenv("SEANIME_SERVER_PORT"))
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the app data directory
|
||||
dataDir, configPath, err := initAppDataDir(options.DataDir, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set Seanime's default custom environment variables
|
||||
if err = setDataDirEnv(dataDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Configure viper
|
||||
viper.SetConfigName(constants.ConfigFileName)
|
||||
viper.SetConfigType("toml")
|
||||
viper.SetConfigFile(configPath)
|
||||
|
||||
// Set default values
|
||||
viper.SetDefault("version", constants.Version)
|
||||
viper.SetDefault("server.host", defaultHost)
|
||||
viper.SetDefault("server.port", defaultPort)
|
||||
viper.SetDefault("server.offline", false)
|
||||
// Use the binary's directory as the working directory environment variable on macOS
|
||||
viper.SetDefault("server.useBinaryPath", true)
|
||||
//viper.SetDefault("server.systray", true)
|
||||
viper.SetDefault("database.name", "seanime")
|
||||
viper.SetDefault("web.assetDir", "$SEANIME_DATA_DIR/assets")
|
||||
viper.SetDefault("cache.dir", "$SEANIME_DATA_DIR/cache")
|
||||
viper.SetDefault("cache.transcodeDir", "$SEANIME_DATA_DIR/cache/transcode")
|
||||
viper.SetDefault("manga.downloadDir", "$SEANIME_DATA_DIR/manga")
|
||||
viper.SetDefault("manga.localDir", "$SEANIME_DATA_DIR/manga-local")
|
||||
viper.SetDefault("logs.dir", "$SEANIME_DATA_DIR/logs")
|
||||
viper.SetDefault("offline.dir", "$SEANIME_DATA_DIR/offline")
|
||||
viper.SetDefault("offline.assetDir", "$SEANIME_DATA_DIR/offline/assets")
|
||||
viper.SetDefault("extensions.dir", "$SEANIME_DATA_DIR/extensions")
|
||||
|
||||
// Create and populate the config file if it doesn't exist
|
||||
if err = createConfigFile(configPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read the config file
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unmarshal the config values
|
||||
cfg := &Config{}
|
||||
if err := viper.Unmarshal(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update the config if the version has changed
|
||||
if err := updateVersion(cfg, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Before expanding the values, check if we need to override the working directory
|
||||
if err = setWorkingDirEnv(cfg.Server.UseBinaryPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Expand the values, replacing environment variables
|
||||
expandEnvironmentValues(cfg)
|
||||
cfg.Data.AppDataDir = dataDir
|
||||
cfg.Data.WorkingDir = os.Getenv("SEANIME_WORKING_DIR")
|
||||
|
||||
// Check validity of the config
|
||||
if err := validateConfig(cfg, logger); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go loadLogo(options.EmbeddedLogo, dataDir)
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (cfg *Config) GetServerAddr(df ...string) string {
|
||||
return fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
|
||||
}
|
||||
|
||||
func (cfg *Config) GetServerURI(df ...string) string {
|
||||
pAddr := fmt.Sprintf("http://%s", cfg.GetServerAddr(df...))
|
||||
if cfg.Server.Host == "" || cfg.Server.Host == "0.0.0.0" {
|
||||
pAddr = fmt.Sprintf(":%d", cfg.Server.Port)
|
||||
if len(df) > 0 {
|
||||
pAddr = fmt.Sprintf("http://%s:%d", df[0], cfg.Server.Port)
|
||||
}
|
||||
}
|
||||
return pAddr
|
||||
}
|
||||
|
||||
func getWorkingDir(useBinaryPath bool) (string, error) {
|
||||
// Get the working directory
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
binaryDir := ""
|
||||
if exe, err := os.Executable(); err == nil {
|
||||
if p, err := filepath.EvalSymlinks(exe); err == nil {
|
||||
binaryDir = filepath.Dir(p)
|
||||
binaryDir = filepath.FromSlash(binaryDir)
|
||||
}
|
||||
}
|
||||
|
||||
if useBinaryPath && binaryDir != "" {
|
||||
return binaryDir, nil
|
||||
}
|
||||
|
||||
//// Use the binary's directory as the working directory if needed
|
||||
//if useBinaryPath {
|
||||
// exe, err := os.Executable()
|
||||
// if err != nil {
|
||||
// return wd, nil // Fallback to working dir
|
||||
// }
|
||||
// p, err := filepath.EvalSymlinks(exe)
|
||||
// if err != nil {
|
||||
// return wd, nil // Fallback to working dir
|
||||
// }
|
||||
// wd = filepath.Dir(p) // Set the binary's directory as the working directory
|
||||
// return wd, nil
|
||||
//}
|
||||
return wd, nil
|
||||
}
|
||||
|
||||
func setDataDirEnv(dataDir string) error {
|
||||
// Set the data directory environment variable
|
||||
if os.Getenv("SEANIME_DATA_DIR") == "" {
|
||||
if err := os.Setenv("SEANIME_DATA_DIR", dataDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setWorkingDirEnv(useBinaryPath bool) error {
|
||||
// Set the working directory environment variable
|
||||
wd, err := getWorkingDir(useBinaryPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.Setenv("SEANIME_WORKING_DIR", filepath.FromSlash(wd)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateConfig checks if the config values are valid
|
||||
func validateConfig(cfg *Config, logger *zerolog.Logger) error {
|
||||
if cfg.Server.Host == "" {
|
||||
return errInvalidConfigValue("server.host", "cannot be empty")
|
||||
}
|
||||
if cfg.Server.Port == 0 {
|
||||
return errInvalidConfigValue("server.port", "cannot be 0")
|
||||
}
|
||||
if cfg.Database.Name == "" {
|
||||
return errInvalidConfigValue("database.name", "cannot be empty")
|
||||
}
|
||||
if cfg.Web.AssetDir == "" {
|
||||
return errInvalidConfigValue("web.assetDir", "cannot be empty")
|
||||
}
|
||||
if err := checkIsValidPath(cfg.Web.AssetDir); err != nil {
|
||||
return wrapInvalidConfigValue("web.assetDir", err)
|
||||
}
|
||||
|
||||
if cfg.Cache.Dir == "" {
|
||||
return errInvalidConfigValue("cache.dir", "cannot be empty")
|
||||
}
|
||||
if err := checkIsValidPath(cfg.Cache.Dir); err != nil {
|
||||
return wrapInvalidConfigValue("cache.dir", err)
|
||||
}
|
||||
|
||||
if cfg.Cache.TranscodeDir == "" {
|
||||
return errInvalidConfigValue("cache.transcodeDir", "cannot be empty")
|
||||
}
|
||||
if err := checkIsValidPath(cfg.Cache.TranscodeDir); err != nil {
|
||||
return wrapInvalidConfigValue("cache.transcodeDir", err)
|
||||
}
|
||||
|
||||
if cfg.Logs.Dir == "" {
|
||||
return errInvalidConfigValue("logs.dir", "cannot be empty")
|
||||
}
|
||||
if err := checkIsValidPath(cfg.Logs.Dir); err != nil {
|
||||
return wrapInvalidConfigValue("logs.dir", err)
|
||||
}
|
||||
|
||||
if cfg.Manga.DownloadDir == "" {
|
||||
return errInvalidConfigValue("manga.downloadDir", "cannot be empty")
|
||||
}
|
||||
if err := checkIsValidPath(cfg.Manga.DownloadDir); err != nil {
|
||||
return wrapInvalidConfigValue("manga.downloadDir", err)
|
||||
}
|
||||
|
||||
if cfg.Manga.LocalDir == "" {
|
||||
return errInvalidConfigValue("manga.localDir", "cannot be empty")
|
||||
}
|
||||
if err := checkIsValidPath(cfg.Manga.LocalDir); err != nil {
|
||||
return wrapInvalidConfigValue("manga.localDir", err)
|
||||
}
|
||||
|
||||
if cfg.Extensions.Dir == "" {
|
||||
return errInvalidConfigValue("extensions.dir", "cannot be empty")
|
||||
}
|
||||
if err := checkIsValidPath(cfg.Extensions.Dir); err != nil {
|
||||
return wrapInvalidConfigValue("extensions.dir", err)
|
||||
}
|
||||
|
||||
// Uncomment if "MainServerTorrentStreaming" is no longer an experimental feature
|
||||
if cfg.Experimental.MainServerTorrentStreaming {
|
||||
logger.Warn().Msgf("app: 'Main Server Torrent Streaming' feature is no longer experimental, remove the flag from your config file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkIsValidPath(path string) error {
|
||||
ok := filepath.IsAbs(path)
|
||||
if !ok {
|
||||
return errors.New("path is not an absolute path")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// errInvalidConfigValue returns an error for an invalid config value
|
||||
func errInvalidConfigValue(s string, s2 string) error {
|
||||
return fmt.Errorf("invalid config value: \"%s\" %s", s, s2)
|
||||
}
|
||||
func wrapInvalidConfigValue(s string, err error) error {
|
||||
return fmt.Errorf("invalid config value: \"%s\" %w", s, err)
|
||||
}
|
||||
|
||||
func updateVersion(cfg *Config, opts *ConfigOptions) error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Do nothing
|
||||
}
|
||||
}()
|
||||
|
||||
if cfg.Version != constants.Version {
|
||||
for _, f := range opts.OnVersionChange {
|
||||
f(cfg.Version, constants.Version)
|
||||
}
|
||||
cfg.Version = constants.Version
|
||||
}
|
||||
|
||||
viper.Set("version", constants.Version)
|
||||
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
func expandEnvironmentValues(cfg *Config) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Do nothing
|
||||
}
|
||||
}()
|
||||
cfg.Web.AssetDir = filepath.FromSlash(os.ExpandEnv(cfg.Web.AssetDir))
|
||||
cfg.Cache.Dir = filepath.FromSlash(os.ExpandEnv(cfg.Cache.Dir))
|
||||
cfg.Cache.TranscodeDir = filepath.FromSlash(os.ExpandEnv(cfg.Cache.TranscodeDir))
|
||||
cfg.Logs.Dir = filepath.FromSlash(os.ExpandEnv(cfg.Logs.Dir))
|
||||
cfg.Manga.DownloadDir = filepath.FromSlash(os.ExpandEnv(cfg.Manga.DownloadDir))
|
||||
cfg.Manga.LocalDir = filepath.FromSlash(os.ExpandEnv(cfg.Manga.LocalDir))
|
||||
cfg.Offline.Dir = filepath.FromSlash(os.ExpandEnv(cfg.Offline.Dir))
|
||||
cfg.Offline.AssetDir = filepath.FromSlash(os.ExpandEnv(cfg.Offline.AssetDir))
|
||||
cfg.Extensions.Dir = filepath.FromSlash(os.ExpandEnv(cfg.Extensions.Dir))
|
||||
}
|
||||
|
||||
// createConfigFile creates a default config file if it doesn't exist
|
||||
func createConfigFile(configPath string) error {
|
||||
_, err := os.Stat(configPath)
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initAppDataDir(definedDataDir string, logger *zerolog.Logger) (dataDir string, configPath string, err error) {
|
||||
|
||||
// User defined data directory
|
||||
if definedDataDir != "" {
|
||||
|
||||
// Expand environment variables
|
||||
definedDataDir = filepath.FromSlash(os.ExpandEnv(definedDataDir))
|
||||
|
||||
if !filepath.IsAbs(definedDataDir) {
|
||||
return "", "", errors.New("app: Data directory path must be absolute")
|
||||
}
|
||||
|
||||
// Replace the default data directory
|
||||
dataDir = definedDataDir
|
||||
|
||||
logger.Trace().Str("dataDir", dataDir).Msg("app: Overriding default data directory")
|
||||
} else {
|
||||
// Default OS data directory
|
||||
// windows: %APPDATA%
|
||||
// unix: $XDG_CONFIG_HOME or $HOME
|
||||
// darwin: $HOME/Library/Application Support
|
||||
dataDir, err = os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
// Get the app directory
|
||||
dataDir = filepath.Join(dataDir, "Seanime")
|
||||
}
|
||||
|
||||
// Create data dir if it doesn't exist
|
||||
if err := os.MkdirAll(dataDir, 0700); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// Get the config file path
|
||||
// Normalize the config file path
|
||||
configPath = filepath.FromSlash(filepath.Join(dataDir, constants.ConfigFileName))
|
||||
// Normalize the data directory path
|
||||
dataDir = filepath.FromSlash(dataDir)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func loadLogo(embeddedLogo []byte, dataDir string) (err error) {
|
||||
defer util.HandlePanicInModuleWithError("core/loadLogo", &err)
|
||||
|
||||
if len(embeddedLogo) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
logoPath := filepath.Join(dataDir, "logo.png")
|
||||
if _, err = os.Stat(logoPath); os.IsNotExist(err) {
|
||||
if err = os.WriteFile(logoPath, embeddedLogo, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user