// Package service contains the business logic of MediaStationGo. Handlers // deserialize the HTTP request, call into a Service method, then serialize // the response. Services own all cross-cutting policy (auth, scanning, // transcoding, etc.) and never deal with HTTP types directly. package service import ( "context" "go.uber.org/zap" "github.com/ShukeBta/MediaStationGo/internal/config" "github.com/ShukeBta/MediaStationGo/internal/repository" ) // Container holds every service initialized at startup. Handlers receive a // pointer to it and pick the relevant fields. type Container struct { Cfg *config.Config Log *zap.Logger Repo *repository.Container WSHub *Hub Auth *AuthService Media *MediaService Scan *ScannerService Stream *StreamService Transcoder *TranscoderService FFprobe *FFprobeService TMDb *TMDbProvider Bangumi *BangumiProvider TheTVDB *TheTVDBProvider Fanart *FanartProvider Scraper *ScraperService Discover *DiscoverService Playback *PlaybackService ImageProxy *ImageProxy Watcher *WatcherService Downloads *DownloadService Subscription *SubscriptionService Subtitle *SubtitleService Stats *StatsService Profile *ProfileService Audit *AuditService NFO *NFOService AI *AIService APIConfig *APIConfigService Crypto *CryptoService Duplicate *DuplicateService FileManager *FileManagerService DLNA *DLNAService Scheduler *SchedulerService Storage *StorageService Emby *EmbyService Backup *BackupService Notifier *NotifierService NotifyChannels *NotifyChannelService PlayProfiles *PlayProfileService Permissions *PermissionService StorageCfg *StorageConfigService License *LicenseService DownloadClients *DownloadClientService Assistant *AssistantService Organizer *OrganizerService Douban *DoubanProvider Site *SiteService stopCtx context.Context stopCancel context.CancelFunc } // New builds the service container. func New(cfg *config.Config, log *zap.Logger, repos *repository.Container) *Container { hub := NewHub(log) go hub.Run() probe := NewFFprobeService(cfg, log) tmdb := NewTMDbProvider(cfg, log) bangumi := NewBangumiProvider(cfg, log) thetvdb := NewTheTVDBProvider(cfg, log) fanart := NewFanartProvider(cfg, log) scraper := NewScraperService(cfg, log, repos, tmdb, bangumi, thetvdb, fanart, hub) discover := NewDiscoverService(log, tmdb) transcoder := NewTranscoderService(cfg, log, repos, hub) scanner := NewScannerService(cfg, log, repos, hub, probe, scraper) downloads := NewDownloadService(log, repos, hub) subscription := NewSubscriptionService(log, repos, downloads, hub) watcher := NewWatcherService(log, repos, scanner) nfo := NewNFOService(log, repos) ai := NewAIService(cfg, log) crypto := NewCryptoService(cfg.Secrets.JWTSecret, log) apiConfig := NewAPIConfigService(log, repos, crypto) duplicate := NewDuplicateService(log, repos, hub) filemanager := NewFileManagerService(cfg, log, repos) dlna := NewDLNAService(log) storage := NewStorageService(log, repos) emby := NewEmbyService(cfg, log, repos) backup := NewBackupService(cfg, log, repos.DB) notifier := NewNotifierService(log, repos) notifyChannels := NewNotifyChannelService(log, repos) playProfiles := NewPlayProfileService(log, repos) permissions := NewPermissionService(log, repos) storageCfg := NewStorageConfigService(log, repos, crypto) licenseSvc := NewLicenseService(log, repos) downloadClients := NewDownloadClientService(log, repos) assistant := NewAssistantService(log, repos, ai) organizer := NewOrganizerService(cfg, log, repos) douban := NewDoubanProvider(cfg, log) siteService := NewSiteService(log, repos) scheduler := NewSchedulerService(log, repos, scanner, transcoder, hub, cfg.Cache.CacheDir) ctx, cancel := context.WithCancel(context.Background()) return &Container{ Cfg: cfg, Log: log, Repo: repos, WSHub: hub, Auth: NewAuthService(cfg, log, repos), Media: NewMediaService(cfg, log, repos), Scan: scanner, Stream: NewStreamService(cfg, log, repos, transcoder), Transcoder: transcoder, FFprobe: probe, TMDb: tmdb, Bangumi: bangumi, TheTVDB: thetvdb, Fanart: fanart, Scraper: scraper, Discover: discover, Playback: NewPlaybackService(log, repos), ImageProxy: NewImageProxy(cfg, log), Watcher: watcher, Downloads: downloads, Subscription: subscription, Subtitle: NewSubtitleService(log, repos), Stats: NewStatsService(log, repos), Profile: NewProfileService(log, repos), Audit: NewAuditService(log, repos), NFO: nfo, AI: ai, APIConfig: apiConfig, Crypto: crypto, Duplicate: duplicate, FileManager: filemanager, DLNA: dlna, Scheduler: scheduler, Storage: storage, Emby: emby, Backup: backup, Notifier: notifier, NotifyChannels: notifyChannels, PlayProfiles: playProfiles, Permissions: permissions, StorageCfg: storageCfg, License: licenseSvc, DownloadClients: downloadClients, Assistant: assistant, Organizer: organizer, Douban: douban, Site: siteService, stopCtx: ctx, stopCancel: cancel, } } // Boot kicks off background workers (watcher, downloads poller, // subscription scheduler). Called once after AutoMigrate. func (c *Container) Boot() { if err := c.Watcher.Start(c.stopCtx); err != nil { c.Log.Warn("watcher start failed", zap.Error(err)) } c.Downloads.Start(c.stopCtx) c.Subscription.Start(c.stopCtx) if err := c.APIConfig.SeedDefaults(c.stopCtx); err != nil { c.Log.Warn("api config seed failed", zap.Error(err)) } c.Scheduler.Start(c.stopCtx) } // Close releases any resources held by services (websocket hub, ffmpeg // transcodes, fsnotify, background pollers). func (c *Container) Close() { if c.stopCancel != nil { c.stopCancel() } if c.Scheduler != nil { c.Scheduler.Stop() } if c.Watcher != nil { c.Watcher.Stop() } if c.Subscription != nil { c.Subscription.Stop() } if c.Downloads != nil { c.Downloads.Stop() } if c.Transcoder != nil { c.Transcoder.StopAll() } if c.WSHub != nil { c.WSHub.Stop() } }