node build fixed
This commit is contained in:
440
seanime-2.9.10/internal/core/app.go
Normal file
440
seanime-2.9.10/internal/core/app.go
Normal file
@@ -0,0 +1,440 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"seanime/internal/api/anilist"
|
||||
"seanime/internal/api/metadata"
|
||||
"seanime/internal/constants"
|
||||
"seanime/internal/continuity"
|
||||
"seanime/internal/database/db"
|
||||
"seanime/internal/database/models"
|
||||
debrid_client "seanime/internal/debrid/client"
|
||||
"seanime/internal/directstream"
|
||||
discordrpc_presence "seanime/internal/discordrpc/presence"
|
||||
"seanime/internal/doh"
|
||||
"seanime/internal/events"
|
||||
"seanime/internal/extension_playground"
|
||||
"seanime/internal/extension_repo"
|
||||
"seanime/internal/hook"
|
||||
"seanime/internal/library/autodownloader"
|
||||
"seanime/internal/library/autoscanner"
|
||||
"seanime/internal/library/fillermanager"
|
||||
"seanime/internal/library/playbackmanager"
|
||||
"seanime/internal/library/scanner"
|
||||
"seanime/internal/local"
|
||||
"seanime/internal/manga"
|
||||
"seanime/internal/mediaplayers/iina"
|
||||
"seanime/internal/mediaplayers/mediaplayer"
|
||||
"seanime/internal/mediaplayers/mpchc"
|
||||
"seanime/internal/mediaplayers/mpv"
|
||||
"seanime/internal/mediaplayers/vlc"
|
||||
"seanime/internal/mediastream"
|
||||
"seanime/internal/nakama"
|
||||
"seanime/internal/nativeplayer"
|
||||
"seanime/internal/onlinestream"
|
||||
"seanime/internal/platforms/anilist_platform"
|
||||
"seanime/internal/platforms/offline_platform"
|
||||
"seanime/internal/platforms/platform"
|
||||
"seanime/internal/platforms/simulated_platform"
|
||||
"seanime/internal/plugin"
|
||||
"seanime/internal/report"
|
||||
"seanime/internal/torrent_clients/torrent_client"
|
||||
"seanime/internal/torrents/torrent"
|
||||
"seanime/internal/torrentstream"
|
||||
"seanime/internal/updater"
|
||||
"seanime/internal/user"
|
||||
"seanime/internal/util"
|
||||
"seanime/internal/util/filecache"
|
||||
"seanime/internal/util/result"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type (
|
||||
App struct {
|
||||
Config *Config
|
||||
Database *db.Database
|
||||
Logger *zerolog.Logger
|
||||
TorrentClientRepository *torrent_client.Repository
|
||||
TorrentRepository *torrent.Repository
|
||||
DebridClientRepository *debrid_client.Repository
|
||||
Watcher *scanner.Watcher
|
||||
AnilistClient anilist.AnilistClient
|
||||
AnilistPlatform platform.Platform
|
||||
OfflinePlatform platform.Platform
|
||||
LocalManager local.Manager
|
||||
FillerManager *fillermanager.FillerManager
|
||||
WSEventManager *events.WSEventManager
|
||||
AutoDownloader *autodownloader.AutoDownloader
|
||||
ExtensionRepository *extension_repo.Repository
|
||||
ExtensionPlaygroundRepository *extension_playground.PlaygroundRepository
|
||||
DirectStreamManager *directstream.Manager
|
||||
NativePlayer *nativeplayer.NativePlayer
|
||||
MediaPlayer struct {
|
||||
VLC *vlc.VLC
|
||||
MpcHc *mpchc.MpcHc
|
||||
Mpv *mpv.Mpv
|
||||
Iina *iina.Iina
|
||||
}
|
||||
MediaPlayerRepository *mediaplayer.Repository
|
||||
Version string
|
||||
Updater *updater.Updater
|
||||
AutoScanner *autoscanner.AutoScanner
|
||||
PlaybackManager *playbackmanager.PlaybackManager
|
||||
FileCacher *filecache.Cacher
|
||||
OnlinestreamRepository *onlinestream.Repository
|
||||
MangaRepository *manga.Repository
|
||||
MetadataProvider metadata.Provider
|
||||
DiscordPresence *discordrpc_presence.Presence
|
||||
MangaDownloader *manga.Downloader
|
||||
ContinuityManager *continuity.Manager
|
||||
Cleanups []func()
|
||||
OnRefreshAnilistCollectionFuncs *result.Map[string, func()]
|
||||
OnFlushLogs func()
|
||||
MediastreamRepository *mediastream.Repository
|
||||
TorrentstreamRepository *torrentstream.Repository
|
||||
FeatureFlags FeatureFlags
|
||||
Settings *models.Settings
|
||||
SecondarySettings struct {
|
||||
Mediastream *models.MediastreamSettings
|
||||
Torrentstream *models.TorrentstreamSettings
|
||||
Debrid *models.DebridSettings
|
||||
} // Struct for other settings sent to clientN
|
||||
SelfUpdater *updater.SelfUpdater
|
||||
ReportRepository *report.Repository
|
||||
TotalLibrarySize uint64 // Initialized in modules.go
|
||||
LibraryDir string
|
||||
IsDesktopSidecar bool
|
||||
animeCollection *anilist.AnimeCollection
|
||||
rawAnimeCollection *anilist.AnimeCollection // (retains custom lists)
|
||||
mangaCollection *anilist.MangaCollection
|
||||
rawMangaCollection *anilist.MangaCollection // (retains custom lists)
|
||||
user *user.User
|
||||
previousVersion string
|
||||
moduleMu sync.Mutex
|
||||
HookManager hook.Manager
|
||||
ServerReady bool // Whether the Anilist data from the first request has been fetched
|
||||
isOffline *bool
|
||||
NakamaManager *nakama.Manager
|
||||
ServerPasswordHash string // SHA-256 hash of the server password
|
||||
}
|
||||
)
|
||||
|
||||
// NewApp creates a new server instance
|
||||
func NewApp(configOpts *ConfigOptions, selfupdater *updater.SelfUpdater) *App {
|
||||
|
||||
// Initialize logger with predefined format
|
||||
logger := util.NewLogger()
|
||||
|
||||
// Log application version, OS, architecture and system info
|
||||
logger.Info().Msgf("app: Seanime %s-%s", constants.Version, constants.VersionName)
|
||||
logger.Info().Msgf("app: OS: %s", runtime.GOOS)
|
||||
logger.Info().Msgf("app: Arch: %s", runtime.GOARCH)
|
||||
logger.Info().Msgf("app: Processor count: %d", runtime.NumCPU())
|
||||
|
||||
// Initialize hook manager for plugin event system
|
||||
hookManager := hook.NewHookManager(hook.NewHookManagerOptions{Logger: logger})
|
||||
hook.SetGlobalHookManager(hookManager)
|
||||
plugin.GlobalAppContext.SetLogger(logger)
|
||||
|
||||
// Store current version to detect version changes
|
||||
previousVersion := constants.Version
|
||||
|
||||
// Add callback to track version changes
|
||||
configOpts.OnVersionChange = append(configOpts.OnVersionChange, func(oldVersion string, newVersion string) {
|
||||
logger.Info().Str("prev", oldVersion).Str("current", newVersion).Msg("app: Version change detected")
|
||||
previousVersion = oldVersion
|
||||
})
|
||||
|
||||
// Initialize configuration with provided options
|
||||
// Creates config directory if it doesn't exist
|
||||
cfg, err := NewConfig(configOpts, logger)
|
||||
if err != nil {
|
||||
logger.Fatal().Err(err).Msgf("app: Failed to initialize config")
|
||||
}
|
||||
|
||||
// Compute SHA-256 hash of the server password
|
||||
serverPasswordHash := ""
|
||||
if cfg.Server.Password != "" {
|
||||
serverPasswordHash = util.HashSHA256Hex(cfg.Server.Password)
|
||||
}
|
||||
|
||||
// Create logs directory if it doesn't exist
|
||||
_ = os.MkdirAll(cfg.Logs.Dir, 0755)
|
||||
|
||||
// Start background process to trim log files
|
||||
go TrimLogEntries(cfg.Logs.Dir, logger)
|
||||
|
||||
logger.Info().Msgf("app: Data directory: %s", cfg.Data.AppDataDir)
|
||||
logger.Info().Msgf("app: Working directory: %s", cfg.Data.WorkingDir)
|
||||
|
||||
// Log if running in desktop sidecar mode
|
||||
if configOpts.IsDesktopSidecar {
|
||||
logger.Info().Msg("app: Desktop sidecar mode enabled")
|
||||
}
|
||||
|
||||
// Initialize database connection
|
||||
database, err := db.NewDatabase(cfg.Data.AppDataDir, cfg.Database.Name, logger)
|
||||
if err != nil {
|
||||
logger.Fatal().Err(err).Msgf("app: Failed to initialize database")
|
||||
}
|
||||
|
||||
HandleNewDatabaseEntries(database, logger)
|
||||
|
||||
// Clean up old database entries in background goroutines
|
||||
database.TrimLocalFileEntries() // Remove old local file entries
|
||||
database.TrimScanSummaryEntries() // Remove old scan summaries
|
||||
database.TrimTorrentstreamHistory() // Remove old torrent stream history
|
||||
|
||||
// Get anime library paths for plugin context
|
||||
animeLibraryPaths, _ := database.GetAllLibraryPathsFromSettings()
|
||||
plugin.GlobalAppContext.SetModulesPartial(plugin.AppContextModules{
|
||||
Database: database,
|
||||
AnimeLibraryPaths: &animeLibraryPaths,
|
||||
})
|
||||
|
||||
// Get Anilist token from database if available
|
||||
anilistToken := database.GetAnilistToken()
|
||||
|
||||
// Initialize Anilist API client with the token
|
||||
// If the token is empty, the client will not be authenticated
|
||||
anilistCW := anilist.NewAnilistClient(anilistToken)
|
||||
|
||||
// Initialize WebSocket event manager for real-time communication
|
||||
wsEventManager := events.NewWSEventManager(logger)
|
||||
|
||||
// Exit if no WebSocket connections in desktop sidecar mode
|
||||
if configOpts.IsDesktopSidecar {
|
||||
wsEventManager.ExitIfNoConnsAsDesktopSidecar()
|
||||
}
|
||||
|
||||
// Initialize DNS-over-HTTPS service in background
|
||||
go doh.HandleDoH(cfg.Server.DoHUrl, logger)
|
||||
|
||||
// Initialize file cache system for media and metadata
|
||||
fileCacher, err := filecache.NewCacher(cfg.Cache.Dir)
|
||||
if err != nil {
|
||||
logger.Fatal().Err(err).Msgf("app: Failed to initialize file cacher")
|
||||
}
|
||||
|
||||
// Initialize extension repository
|
||||
extensionRepository := extension_repo.NewRepository(&extension_repo.NewRepositoryOptions{
|
||||
Logger: logger,
|
||||
ExtensionDir: cfg.Extensions.Dir,
|
||||
WSEventManager: wsEventManager,
|
||||
FileCacher: fileCacher,
|
||||
HookManager: hookManager,
|
||||
})
|
||||
// Load extensions in background
|
||||
go LoadExtensions(extensionRepository, logger, cfg)
|
||||
|
||||
// Initialize metadata provider for media information
|
||||
metadataProvider := metadata.NewProvider(&metadata.NewProviderImplOptions{
|
||||
Logger: logger,
|
||||
FileCacher: fileCacher,
|
||||
})
|
||||
|
||||
// Set initial metadata provider (will change if offline mode is enabled)
|
||||
activeMetadataProvider := metadataProvider
|
||||
|
||||
// Initialize manga repository
|
||||
mangaRepository := manga.NewRepository(&manga.NewRepositoryOptions{
|
||||
Logger: logger,
|
||||
FileCacher: fileCacher,
|
||||
CacheDir: cfg.Cache.Dir,
|
||||
ServerURI: cfg.GetServerURI(),
|
||||
WsEventManager: wsEventManager,
|
||||
DownloadDir: cfg.Manga.DownloadDir,
|
||||
Database: database,
|
||||
})
|
||||
|
||||
// Initialize Anilist platform
|
||||
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistCW, logger)
|
||||
|
||||
// Update plugin context with new modules
|
||||
plugin.GlobalAppContext.SetModulesPartial(plugin.AppContextModules{
|
||||
AnilistPlatform: anilistPlatform,
|
||||
WSEventManager: wsEventManager,
|
||||
MetadataProvider: metadataProvider,
|
||||
})
|
||||
|
||||
// Initialize sync manager for offline/online synchronization
|
||||
localManager, err := local.NewManager(&local.NewManagerOptions{
|
||||
LocalDir: cfg.Offline.Dir,
|
||||
AssetDir: cfg.Offline.AssetDir,
|
||||
Logger: logger,
|
||||
MetadataProvider: metadataProvider,
|
||||
MangaRepository: mangaRepository,
|
||||
Database: database,
|
||||
WSEventManager: wsEventManager,
|
||||
IsOffline: cfg.Server.Offline,
|
||||
AnilistPlatform: anilistPlatform,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Fatal().Err(err).Msgf("app: Failed to initialize sync manager")
|
||||
}
|
||||
|
||||
// Use local metadata provider if in offline mode
|
||||
if cfg.Server.Offline {
|
||||
activeMetadataProvider = localManager.GetOfflineMetadataProvider()
|
||||
}
|
||||
|
||||
// Initialize local platform for offline operations
|
||||
offlinePlatform, err := offline_platform.NewOfflinePlatform(localManager, anilistCW, logger)
|
||||
if err != nil {
|
||||
logger.Fatal().Err(err).Msgf("app: Failed to initialize local platform")
|
||||
}
|
||||
|
||||
// Initialize simulated platform for unauthenticated operations
|
||||
simulatedPlatform, err := simulated_platform.NewSimulatedPlatform(localManager, anilistCW, logger)
|
||||
if err != nil {
|
||||
logger.Fatal().Err(err).Msgf("app: Failed to initialize simulated platform")
|
||||
}
|
||||
|
||||
// Change active platform if offline mode is enabled
|
||||
activePlatform := anilistPlatform
|
||||
if cfg.Server.Offline {
|
||||
activePlatform = offlinePlatform
|
||||
} else if !anilistCW.IsAuthenticated() {
|
||||
logger.Warn().Msg("app: Anilist client is not authenticated, using simulated platform")
|
||||
activePlatform = simulatedPlatform
|
||||
}
|
||||
|
||||
// Initialize online streaming repository
|
||||
onlinestreamRepository := onlinestream.NewRepository(&onlinestream.NewRepositoryOptions{
|
||||
Logger: logger,
|
||||
FileCacher: fileCacher,
|
||||
MetadataProvider: activeMetadataProvider,
|
||||
Platform: activePlatform,
|
||||
Database: database,
|
||||
})
|
||||
|
||||
// Initialize extension playground for testing extensions
|
||||
extensionPlaygroundRepository := extension_playground.NewPlaygroundRepository(logger, activePlatform, activeMetadataProvider)
|
||||
|
||||
isOffline := cfg.Server.Offline
|
||||
|
||||
// Create the main app instance with initialized components
|
||||
app := &App{
|
||||
Config: cfg,
|
||||
Database: database,
|
||||
AnilistClient: anilistCW,
|
||||
AnilistPlatform: activePlatform,
|
||||
OfflinePlatform: offlinePlatform,
|
||||
LocalManager: localManager,
|
||||
WSEventManager: wsEventManager,
|
||||
Logger: logger,
|
||||
Version: constants.Version,
|
||||
Updater: updater.New(constants.Version, logger, wsEventManager),
|
||||
FileCacher: fileCacher,
|
||||
OnlinestreamRepository: onlinestreamRepository,
|
||||
MetadataProvider: activeMetadataProvider,
|
||||
MangaRepository: mangaRepository,
|
||||
ExtensionRepository: extensionRepository,
|
||||
ExtensionPlaygroundRepository: extensionPlaygroundRepository,
|
||||
ReportRepository: report.NewRepository(logger),
|
||||
TorrentRepository: nil, // Initialized in App.initModulesOnce
|
||||
FillerManager: nil, // Initialized in App.initModulesOnce
|
||||
MangaDownloader: nil, // Initialized in App.initModulesOnce
|
||||
PlaybackManager: nil, // Initialized in App.initModulesOnce
|
||||
AutoDownloader: nil, // Initialized in App.initModulesOnce
|
||||
AutoScanner: nil, // Initialized in App.initModulesOnce
|
||||
MediastreamRepository: nil, // Initialized in App.initModulesOnce
|
||||
TorrentstreamRepository: nil, // Initialized in App.initModulesOnce
|
||||
ContinuityManager: nil, // Initialized in App.initModulesOnce
|
||||
DebridClientRepository: nil, // Initialized in App.initModulesOnce
|
||||
DirectStreamManager: nil, // Initialized in App.initModulesOnce
|
||||
NativePlayer: nil, // Initialized in App.initModulesOnce
|
||||
NakamaManager: nil, // Initialized in App.initModulesOnce
|
||||
TorrentClientRepository: nil, // Initialized in App.InitOrRefreshModules
|
||||
MediaPlayerRepository: nil, // Initialized in App.InitOrRefreshModules
|
||||
DiscordPresence: nil, // Initialized in App.InitOrRefreshModules
|
||||
previousVersion: previousVersion,
|
||||
FeatureFlags: NewFeatureFlags(cfg, logger),
|
||||
IsDesktopSidecar: configOpts.IsDesktopSidecar,
|
||||
SecondarySettings: struct {
|
||||
Mediastream *models.MediastreamSettings
|
||||
Torrentstream *models.TorrentstreamSettings
|
||||
Debrid *models.DebridSettings
|
||||
}{Mediastream: nil, Torrentstream: nil},
|
||||
SelfUpdater: selfupdater,
|
||||
moduleMu: sync.Mutex{},
|
||||
OnRefreshAnilistCollectionFuncs: result.NewResultMap[string, func()](),
|
||||
HookManager: hookManager,
|
||||
isOffline: &isOffline,
|
||||
ServerPasswordHash: serverPasswordHash,
|
||||
}
|
||||
|
||||
// Run database migrations if version has changed
|
||||
app.runMigrations()
|
||||
|
||||
// Initialize modules that only need to be initialized once
|
||||
app.initModulesOnce()
|
||||
|
||||
plugin.GlobalAppContext.SetModulesPartial(plugin.AppContextModules{
|
||||
IsOffline: app.IsOffline(),
|
||||
ContinuityManager: app.ContinuityManager,
|
||||
AutoScanner: app.AutoScanner,
|
||||
AutoDownloader: app.AutoDownloader,
|
||||
FileCacher: app.FileCacher,
|
||||
OnlinestreamRepository: app.OnlinestreamRepository,
|
||||
MediastreamRepository: app.MediastreamRepository,
|
||||
TorrentstreamRepository: app.TorrentstreamRepository,
|
||||
})
|
||||
|
||||
if !*app.IsOffline() {
|
||||
go app.Updater.FetchAnnouncements()
|
||||
}
|
||||
|
||||
// Initialize all modules that depend on settings
|
||||
app.InitOrRefreshModules()
|
||||
|
||||
// Load built-in extensions into extension consumers
|
||||
app.AddExtensionBankToConsumers()
|
||||
|
||||
// Initialize Anilist data if not in offline mode
|
||||
if !*app.IsOffline() {
|
||||
app.InitOrRefreshAnilistData()
|
||||
} else {
|
||||
app.ServerReady = true
|
||||
}
|
||||
|
||||
// Initialize mediastream settings (for streaming media)
|
||||
app.InitOrRefreshMediastreamSettings()
|
||||
|
||||
// Initialize torrentstream settings (for torrent streaming)
|
||||
app.InitOrRefreshTorrentstreamSettings()
|
||||
|
||||
// Initialize debrid settings (for debrid services)
|
||||
app.InitOrRefreshDebridSettings()
|
||||
|
||||
// Register Nakama manager cleanup
|
||||
app.AddCleanupFunction(app.NakamaManager.Cleanup)
|
||||
|
||||
// Run one-time initialization actions
|
||||
app.performActionsOnce()
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func (a *App) IsOffline() *bool {
|
||||
return a.isOffline
|
||||
}
|
||||
|
||||
func (a *App) AddCleanupFunction(f func()) {
|
||||
a.Cleanups = append(a.Cleanups, f)
|
||||
}
|
||||
func (a *App) AddOnRefreshAnilistCollectionFunc(key string, f func()) {
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
a.OnRefreshAnilistCollectionFuncs.Set(key, f)
|
||||
}
|
||||
|
||||
func (a *App) Cleanup() {
|
||||
for _, f := range a.Cleanups {
|
||||
f()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user