From 7c6c49c7a1fc3ab882e664be308c1f7e7a5b459c Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 28 Dec 2025 12:13:36 -0500 Subject: [PATCH] refactor(plugins): update GetManager to accept DataStore parameter Signed-off-by: Deluan --- cmd/root.go | 2 +- cmd/wire_gen.go | 15 ++-- cmd/wire_injectors.go | 1 - core/metrics/insights.go | 2 +- plugins/host_artwork_test.go | 2 +- plugins/host_cache_test.go | 2 +- plugins/host_scheduler_test.go | 2 +- plugins/host_subsonicapi_test.go | 2 +- plugins/host_websocket_test.go | 2 +- plugins/manager.go | 143 +++++++++++++++---------------- plugins/plugins_suite_test.go | 2 +- plugins/watcher_test.go | 2 +- 12 files changed, 86 insertions(+), 91 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index c27934640..28bf5ef1d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -330,12 +330,12 @@ func startPlaybackServer(ctx context.Context) func() error { // startPluginManager starts the plugin manager, if configured. func startPluginManager(ctx context.Context) func() error { return func() error { + manager := GetPluginManager(ctx) if !conf.Server.Plugins.Enabled { log.Debug("Plugin system is DISABLED") return nil } log.Info(ctx, "Starting plugin manager") - manager := GetPluginManager(ctx) return manager.Start(ctx) } } diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index f4760fa81..d9450738f 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -60,7 +60,7 @@ func CreateNativeAPIRouter(ctx context.Context) *nativeapi.Router { insights := metrics.GetInstance(dataStore) fileCache := artwork.GetImageCache() fFmpeg := ffmpeg.New() - manager := plugins.GetManager() + manager := plugins.GetManager(dataStore) agentsAgents := agents.GetAgents(dataStore, manager) provider := external.NewProvider(dataStore, agentsAgents) artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, provider) @@ -80,7 +80,7 @@ func CreateSubsonicAPIRouter(ctx context.Context) *subsonic.Router { dataStore := persistence.New(sqlDB) fileCache := artwork.GetImageCache() fFmpeg := ffmpeg.New() - manager := plugins.GetManager() + manager := plugins.GetManager(dataStore) agentsAgents := agents.GetAgents(dataStore, manager) provider := external.NewProvider(dataStore, agentsAgents) artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, provider) @@ -105,7 +105,7 @@ func CreatePublicRouter() *public.Router { dataStore := persistence.New(sqlDB) fileCache := artwork.GetImageCache() fFmpeg := ffmpeg.New() - manager := plugins.GetManager() + manager := plugins.GetManager(dataStore) agentsAgents := agents.GetAgents(dataStore, manager) provider := external.NewProvider(dataStore, agentsAgents) artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, provider) @@ -150,7 +150,7 @@ func CreateScanner(ctx context.Context) model.Scanner { dataStore := persistence.New(sqlDB) fileCache := artwork.GetImageCache() fFmpeg := ffmpeg.New() - manager := plugins.GetManager() + manager := plugins.GetManager(dataStore) agentsAgents := agents.GetAgents(dataStore, manager) provider := external.NewProvider(dataStore, agentsAgents) artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, provider) @@ -167,7 +167,7 @@ func CreateScanWatcher(ctx context.Context) scanner.Watcher { dataStore := persistence.New(sqlDB) fileCache := artwork.GetImageCache() fFmpeg := ffmpeg.New() - manager := plugins.GetManager() + manager := plugins.GetManager(dataStore) agentsAgents := agents.GetAgents(dataStore, manager) provider := external.NewProvider(dataStore, agentsAgents) artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, provider) @@ -188,7 +188,9 @@ func GetPlaybackServer() playback.PlaybackServer { } func getPluginManager() *plugins.Manager { - manager := plugins.GetManager() + sqlDB := db.Db() + dataStore := persistence.New(sqlDB) + manager := plugins.GetManager(dataStore) return manager } @@ -199,6 +201,5 @@ var allProviders = wire.NewSet(core.Set, artwork.Set, server.New, subsonic.New, func GetPluginManager(ctx context.Context) *plugins.Manager { manager := getPluginManager() manager.SetSubsonicRouter(CreateSubsonicAPIRouter(ctx)) - manager.SetDataStore(CreateDataStore()) return manager } diff --git a/cmd/wire_injectors.go b/cmd/wire_injectors.go index 15973c497..782f2277c 100644 --- a/cmd/wire_injectors.go +++ b/cmd/wire_injectors.go @@ -129,6 +129,5 @@ func getPluginManager() *plugins.Manager { func GetPluginManager(ctx context.Context) *plugins.Manager { manager := getPluginManager() manager.SetSubsonicRouter(CreateSubsonicAPIRouter(ctx)) - manager.SetDataStore(CreateDataStore()) return manager } diff --git a/core/metrics/insights.go b/core/metrics/insights.go index a341bf217..a7e06f51c 100644 --- a/core/metrics/insights.go +++ b/core/metrics/insights.go @@ -313,7 +313,7 @@ func (c *insightsCollector) hasSmartPlaylists(ctx context.Context) (bool, error) // collectPlugins collects information about installed plugins func (c *insightsCollector) collectPlugins(_ context.Context) map[string]insights.PluginInfo { - manager := plugins.GetManager() + manager := plugins.GetManager(c.ds) info := manager.GetPluginInfo() result := make(map[string]insights.PluginInfo, len(info)) diff --git a/plugins/host_artwork_test.go b/plugins/host_artwork_test.go index 4422cdd47..b1b55bf15 100644 --- a/plugins/host_artwork_test.go +++ b/plugins/host_artwork_test.go @@ -71,8 +71,8 @@ var _ = Describe("ArtworkService", Ordered, func() { // Create and start manager manager = &Manager{ plugins: make(map[string]*plugin), + ds: dataStore, } - manager.SetDataStore(dataStore) err = manager.Start(GinkgoT().Context()) Expect(err).ToNot(HaveOccurred()) diff --git a/plugins/host_cache_test.go b/plugins/host_cache_test.go index 4568254d9..863a4ad46 100644 --- a/plugins/host_cache_test.go +++ b/plugins/host_cache_test.go @@ -360,8 +360,8 @@ var _ = Describe("CacheService Integration", Ordered, func() { // Create and start manager manager = &Manager{ plugins: make(map[string]*plugin), + ds: dataStore, } - manager.SetDataStore(dataStore) err = manager.Start(GinkgoT().Context()) Expect(err).ToNot(HaveOccurred()) diff --git a/plugins/host_scheduler_test.go b/plugins/host_scheduler_test.go index 65be9fecb..e56ef31ef 100644 --- a/plugins/host_scheduler_test.go +++ b/plugins/host_scheduler_test.go @@ -76,8 +76,8 @@ var _ = Describe("SchedulerService", Ordered, func() { // Create and start manager manager = &Manager{ plugins: make(map[string]*plugin), + ds: dataStore, } - manager.SetDataStore(dataStore) err = manager.Start(GinkgoT().Context()) Expect(err).ToNot(HaveOccurred()) diff --git a/plugins/host_subsonicapi_test.go b/plugins/host_subsonicapi_test.go index f570f78b9..30d5117be 100644 --- a/plugins/host_subsonicapi_test.go +++ b/plugins/host_subsonicapi_test.go @@ -67,9 +67,9 @@ var _ = Describe("SubsonicAPI Host Function", Ordered, func() { // Create and configure manager manager = &Manager{ plugins: make(map[string]*plugin), + ds: dataStore, } manager.SetSubsonicRouter(router) - manager.SetDataStore(dataStore) // Pre-enable the plugin in the mock repo so it loads on startup // Compute SHA256 of the plugin file to match what SyncPlugins will compute diff --git a/plugins/host_websocket_test.go b/plugins/host_websocket_test.go index fe435f81a..7a3248efc 100644 --- a/plugins/host_websocket_test.go +++ b/plugins/host_websocket_test.go @@ -69,8 +69,8 @@ var _ = Describe("WebSocketService", Ordered, func() { // Create and start manager manager = &Manager{ plugins: make(map[string]*plugin), + ds: dataStore, } - manager.SetDataStore(dataStore) err = manager.Start(GinkgoT().Context()) Expect(err).ToNot(HaveOccurred()) diff --git a/plugins/manager.go b/plugins/manager.go index 8912ec69a..f0f0528b1 100644 --- a/plugins/manager.go +++ b/plugins/manager.go @@ -109,81 +109,15 @@ func (p *plugin) Close() error { // GetManager returns a singleton instance of the plugin manager. // The manager is not started automatically; call Start() to begin loading plugins. -func GetManager() *Manager { +func GetManager(ds model.DataStore) *Manager { return singleton.GetInstance(func() *Manager { return &Manager{ + ds: ds, plugins: make(map[string]*plugin), } }) } -// adminContext returns a context with admin privileges for DB operations. -func adminContext(ctx context.Context) context.Context { - return request.WithUser(ctx, model.User{IsAdmin: true}) -} - -// marshalManifest marshals a manifest to JSON string, returning empty string on error. -func marshalManifest(m *Manifest) string { - b, _ := json.Marshal(m) - return string(b) -} - -// addPluginToDB adds a new plugin to the database as disabled. -func (m *Manager) addPluginToDB(ctx context.Context, repo model.PluginRepository, name, path string, metadata *PluginMetadata) error { - now := time.Now() - newPlugin := &model.Plugin{ - ID: name, - Path: path, - Manifest: marshalManifest(metadata.Manifest), - SHA256: metadata.SHA256, - Enabled: false, - CreatedAt: now, - UpdatedAt: now, - } - if err := repo.Put(newPlugin); err != nil { - return fmt.Errorf("adding plugin to DB: %w", err) - } - log.Info(ctx, "Discovered new plugin", "plugin", name) - return nil -} - -// updatePluginInDB updates an existing plugin in the database after a file change. -// If the plugin was enabled, it will be unloaded and disabled. -func (m *Manager) updatePluginInDB(ctx context.Context, repo model.PluginRepository, dbPlugin *model.Plugin, path string, metadata *PluginMetadata) error { - wasEnabled := dbPlugin.Enabled - if wasEnabled { - if err := m.UnloadPlugin(dbPlugin.ID); err != nil { - log.Debug(ctx, "Plugin not loaded during change", "plugin", dbPlugin.ID) - } - } - dbPlugin.Path = path - dbPlugin.Manifest = marshalManifest(metadata.Manifest) - dbPlugin.SHA256 = metadata.SHA256 - dbPlugin.Enabled = false - dbPlugin.LastError = "" - dbPlugin.UpdatedAt = time.Now() - if err := repo.Put(dbPlugin); err != nil { - return fmt.Errorf("updating plugin in DB: %w", err) - } - log.Info(ctx, "Plugin file changed", "plugin", dbPlugin.ID, "wasEnabled", wasEnabled) - return nil -} - -// removePluginFromDB removes a plugin from the database. -// If the plugin was enabled, it will be unloaded first. -func (m *Manager) removePluginFromDB(ctx context.Context, repo model.PluginRepository, dbPlugin *model.Plugin) error { - if dbPlugin.Enabled { - if err := m.UnloadPlugin(dbPlugin.ID); err != nil { - log.Debug(ctx, "Plugin not loaded during removal", "plugin", dbPlugin.ID) - } - } - if err := repo.Delete(dbPlugin.ID); err != nil { - return fmt.Errorf("deleting plugin from DB: %w", err) - } - log.Info(ctx, "Plugin removed", "plugin", dbPlugin.ID) - return nil -} - // SetSubsonicRouter sets the Subsonic router for SubsonicAPI host functions. // This should be called after the subsonic router is created but before plugins // that require SubsonicAPI access are loaded. @@ -191,12 +125,6 @@ func (m *Manager) SetSubsonicRouter(router SubsonicRouter) { m.subsonicRouter = router } -// SetDataStore sets the data store for plugins that need database access. -// This should be called before plugins are loaded. -func (m *Manager) SetDataStore(ds model.DataStore) { - m.ds = ds -} - // Start initializes the plugin manager and loads plugins from the configured folder. // It should be called once during application startup when plugins are enabled. // The startup flow is: @@ -384,6 +312,73 @@ func (m *Manager) GetPluginInfo() map[string]PluginInfo { return info } +// adminContext returns a context with admin privileges for DB operations. +func adminContext(ctx context.Context) context.Context { + return request.WithUser(ctx, model.User{IsAdmin: true}) +} + +// marshalManifest marshals a manifest to JSON string, returning empty string on error. +func marshalManifest(m *Manifest) string { + b, _ := json.Marshal(m) + return string(b) +} + +// addPluginToDB adds a new plugin to the database as disabled. +func (m *Manager) addPluginToDB(ctx context.Context, repo model.PluginRepository, name, path string, metadata *PluginMetadata) error { + now := time.Now() + newPlugin := &model.Plugin{ + ID: name, + Path: path, + Manifest: marshalManifest(metadata.Manifest), + SHA256: metadata.SHA256, + Enabled: false, + CreatedAt: now, + UpdatedAt: now, + } + if err := repo.Put(newPlugin); err != nil { + return fmt.Errorf("adding plugin to DB: %w", err) + } + log.Info(ctx, "Discovered new plugin", "plugin", name) + return nil +} + +// updatePluginInDB updates an existing plugin in the database after a file change. +// If the plugin was enabled, it will be unloaded and disabled. +func (m *Manager) updatePluginInDB(ctx context.Context, repo model.PluginRepository, dbPlugin *model.Plugin, path string, metadata *PluginMetadata) error { + wasEnabled := dbPlugin.Enabled + if wasEnabled { + if err := m.UnloadPlugin(dbPlugin.ID); err != nil { + log.Debug(ctx, "Plugin not loaded during change", "plugin", dbPlugin.ID) + } + } + dbPlugin.Path = path + dbPlugin.Manifest = marshalManifest(metadata.Manifest) + dbPlugin.SHA256 = metadata.SHA256 + dbPlugin.Enabled = false + dbPlugin.LastError = "" + dbPlugin.UpdatedAt = time.Now() + if err := repo.Put(dbPlugin); err != nil { + return fmt.Errorf("updating plugin in DB: %w", err) + } + log.Info(ctx, "Plugin file changed", "plugin", dbPlugin.ID, "wasEnabled", wasEnabled) + return nil +} + +// removePluginFromDB removes a plugin from the database. +// If the plugin was enabled, it will be unloaded first. +func (m *Manager) removePluginFromDB(ctx context.Context, repo model.PluginRepository, dbPlugin *model.Plugin) error { + if dbPlugin.Enabled { + if err := m.UnloadPlugin(dbPlugin.ID); err != nil { + log.Debug(ctx, "Plugin not loaded during removal", "plugin", dbPlugin.ID) + } + } + if err := repo.Delete(dbPlugin.ID); err != nil { + return fmt.Errorf("deleting plugin from DB: %w", err) + } + log.Info(ctx, "Plugin removed", "plugin", dbPlugin.ID) + return nil +} + // PluginMetadata holds the extracted information from a plugin file // without fully initializing the plugin. type PluginMetadata struct { diff --git a/plugins/plugins_suite_test.go b/plugins/plugins_suite_test.go index ff495e358..ae6a2bc94 100644 --- a/plugins/plugins_suite_test.go +++ b/plugins/plugins_suite_test.go @@ -114,8 +114,8 @@ func createTestManagerWithPlugins(pluginConfig map[string]map[string]string, plu // Create and start manager manager := &Manager{ plugins: make(map[string]*plugin), + ds: dataStore, } - manager.SetDataStore(dataStore) err = manager.Start(GinkgoT().Context()) Expect(err).ToNot(HaveOccurred()) diff --git a/plugins/watcher_test.go b/plugins/watcher_test.go index 7af3e00a2..3c6b1b382 100644 --- a/plugins/watcher_test.go +++ b/plugins/watcher_test.go @@ -136,8 +136,8 @@ var _ = Describe("Plugin Watcher", func() { autoReloadManager := &Manager{ plugins: make(map[string]*plugin), + ds: dataStore, } - autoReloadManager.SetDataStore(dataStore) err := autoReloadManager.Start(ctx) Expect(err).ToNot(HaveOccurred()) DeferCleanup(autoReloadManager.Stop)