fix(plugins): silence plugin warnings and folder creation when plugins disabled (#4297)
* fix(plugins): silence repeated “Plugin not found” spam for inactive Spotify/Last.fm plugins
Navidrome was emitting a warning when the optional Spotify or
Last.fm agents weren’t enabled, filling the journal with entries like:
level=warning msg="Plugin not found" capability=MetadataAgent name=spotify
Fixed by completely disable the plugin system when Plugins.Enabled = false.
Signed-off-by: Deluan <deluan@navidrome.org>
* style: update test description for clarity
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: ensure plugin folder is created only if plugins are enabled
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
+3
-3
@@ -175,7 +175,7 @@ func GetPlaybackServer() playback.PlaybackServer {
|
|||||||
return playbackServer
|
return playbackServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPluginManager() *plugins.Manager {
|
func getPluginManager() plugins.Manager {
|
||||||
sqlDB := db.Db()
|
sqlDB := db.Db()
|
||||||
dataStore := persistence.New(sqlDB)
|
dataStore := persistence.New(sqlDB)
|
||||||
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
||||||
@@ -185,9 +185,9 @@ func getPluginManager() *plugins.Manager {
|
|||||||
|
|
||||||
// wire_injectors.go:
|
// wire_injectors.go:
|
||||||
|
|
||||||
var allProviders = wire.NewSet(core.Set, artwork.Set, server.New, subsonic.New, nativeapi.New, public.New, persistence.New, lastfm.NewRouter, listenbrainz.NewRouter, events.GetBroker, scanner.New, scanner.NewWatcher, plugins.GetManager, metrics.GetPrometheusInstance, db.Db, wire.Bind(new(agents.PluginLoader), new(*plugins.Manager)), wire.Bind(new(scrobbler.PluginLoader), new(*plugins.Manager)))
|
var allProviders = wire.NewSet(core.Set, artwork.Set, server.New, subsonic.New, nativeapi.New, public.New, persistence.New, lastfm.NewRouter, listenbrainz.NewRouter, events.GetBroker, scanner.New, scanner.NewWatcher, plugins.GetManager, metrics.GetPrometheusInstance, db.Db, wire.Bind(new(agents.PluginLoader), new(plugins.Manager)), wire.Bind(new(scrobbler.PluginLoader), new(plugins.Manager)))
|
||||||
|
|
||||||
func GetPluginManager(ctx context.Context) *plugins.Manager {
|
func GetPluginManager(ctx context.Context) plugins.Manager {
|
||||||
manager := getPluginManager()
|
manager := getPluginManager()
|
||||||
manager.SetSubsonicRouter(CreateSubsonicAPIRouter(ctx))
|
manager.SetSubsonicRouter(CreateSubsonicAPIRouter(ctx))
|
||||||
return manager
|
return manager
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ var allProviders = wire.NewSet(
|
|||||||
plugins.GetManager,
|
plugins.GetManager,
|
||||||
metrics.GetPrometheusInstance,
|
metrics.GetPrometheusInstance,
|
||||||
db.Db,
|
db.Db,
|
||||||
wire.Bind(new(agents.PluginLoader), new(*plugins.Manager)),
|
wire.Bind(new(agents.PluginLoader), new(plugins.Manager)),
|
||||||
wire.Bind(new(scrobbler.PluginLoader), new(*plugins.Manager)),
|
wire.Bind(new(scrobbler.PluginLoader), new(plugins.Manager)),
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateDataStore() model.DataStore {
|
func CreateDataStore() model.DataStore {
|
||||||
@@ -118,13 +118,13 @@ func GetPlaybackServer() playback.PlaybackServer {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPluginManager() *plugins.Manager {
|
func getPluginManager() plugins.Manager {
|
||||||
panic(wire.Build(
|
panic(wire.Build(
|
||||||
allProviders,
|
allProviders,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPluginManager(ctx context.Context) *plugins.Manager {
|
func GetPluginManager(ctx context.Context) plugins.Manager {
|
||||||
manager := getPluginManager()
|
manager := getPluginManager()
|
||||||
manager.SetSubsonicRouter(CreateSubsonicAPIRouter(ctx))
|
manager.SetSubsonicRouter(CreateSubsonicAPIRouter(ctx))
|
||||||
return manager
|
return manager
|
||||||
|
|||||||
@@ -264,13 +264,15 @@ func Load(noConfigDump bool) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if Server.Plugins.Folder == "" {
|
if Server.Plugins.Enabled {
|
||||||
Server.Plugins.Folder = filepath.Join(Server.DataFolder, "plugins")
|
if Server.Plugins.Folder == "" {
|
||||||
}
|
Server.Plugins.Folder = filepath.Join(Server.DataFolder, "plugins")
|
||||||
err = os.MkdirAll(Server.Plugins.Folder, 0700)
|
}
|
||||||
if err != nil {
|
err = os.MkdirAll(Server.Plugins.Folder, 0700)
|
||||||
_, _ = fmt.Fprintln(os.Stderr, "FATAL: Error creating plugins path:", err)
|
if err != nil {
|
||||||
os.Exit(1)
|
_, _ = fmt.Fprintln(os.Stderr, "FATAL: Error creating plugins path:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Server.ConfigFile = viper.GetViper().ConfigFileUsed()
|
Server.ConfigFile = viper.GetViper().ConfigFileUsed()
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewWasmMediaAgent creates a new adapter for a MetadataAgent plugin
|
// NewWasmMediaAgent creates a new adapter for a MetadataAgent plugin
|
||||||
func newWasmMediaAgent(wasmPath, pluginID string, m *Manager, runtime api.WazeroNewRuntime, mc wazero.ModuleConfig) WasmPlugin {
|
func newWasmMediaAgent(wasmPath, pluginID string, m *managerImpl, runtime api.WazeroNewRuntime, mc wazero.ModuleConfig) WasmPlugin {
|
||||||
loader, err := api.NewMetadataAgentPlugin(context.Background(), api.WazeroRuntime(runtime), api.WazeroModuleConfig(mc))
|
loader, err := api.NewMetadataAgentPlugin(context.Background(), api.WazeroRuntime(runtime), api.WazeroModuleConfig(mc))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error creating media metadata service plugin", "plugin", pluginID, "path", wasmPath, err)
|
log.Error("Error creating media metadata service plugin", "plugin", pluginID, "path", wasmPath, err)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
var _ = Describe("Adapter Media Agent", func() {
|
var _ = Describe("Adapter Media Agent", func() {
|
||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
var mgr *Manager
|
var mgr *managerImpl
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
ctx = GinkgoT().Context()
|
ctx = GinkgoT().Context()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// newWasmSchedulerCallback creates a new adapter for a SchedulerCallback plugin
|
// newWasmSchedulerCallback creates a new adapter for a SchedulerCallback plugin
|
||||||
func newWasmSchedulerCallback(wasmPath, pluginID string, m *Manager, runtime api.WazeroNewRuntime, mc wazero.ModuleConfig) WasmPlugin {
|
func newWasmSchedulerCallback(wasmPath, pluginID string, m *managerImpl, runtime api.WazeroNewRuntime, mc wazero.ModuleConfig) WasmPlugin {
|
||||||
loader, err := api.NewSchedulerCallbackPlugin(context.Background(), api.WazeroRuntime(runtime), api.WazeroModuleConfig(mc))
|
loader, err := api.NewSchedulerCallbackPlugin(context.Background(), api.WazeroRuntime(runtime), api.WazeroModuleConfig(mc))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error creating scheduler callback plugin", "plugin", pluginID, "path", wasmPath, err)
|
log.Error("Error creating scheduler callback plugin", "plugin", pluginID, "path", wasmPath, err)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/tetratelabs/wazero"
|
"github.com/tetratelabs/wazero"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newWasmScrobblerPlugin(wasmPath, pluginID string, m *Manager, runtime api.WazeroNewRuntime, mc wazero.ModuleConfig) WasmPlugin {
|
func newWasmScrobblerPlugin(wasmPath, pluginID string, m *managerImpl, runtime api.WazeroNewRuntime, mc wazero.ModuleConfig) WasmPlugin {
|
||||||
loader, err := api.NewScrobblerPlugin(context.Background(), api.WazeroRuntime(runtime), api.WazeroModuleConfig(mc))
|
loader, err := api.NewScrobblerPlugin(context.Background(), api.WazeroRuntime(runtime), api.WazeroModuleConfig(mc))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error creating scrobbler service plugin", "plugin", pluginID, "path", wasmPath, err)
|
log.Error("Error creating scrobbler service plugin", "plugin", pluginID, "path", wasmPath, err)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// newWasmWebSocketCallback creates a new adapter for a WebSocketCallback plugin
|
// newWasmWebSocketCallback creates a new adapter for a WebSocketCallback plugin
|
||||||
func newWasmWebSocketCallback(wasmPath, pluginID string, m *Manager, runtime api.WazeroNewRuntime, mc wazero.ModuleConfig) WasmPlugin {
|
func newWasmWebSocketCallback(wasmPath, pluginID string, m *managerImpl, runtime api.WazeroNewRuntime, mc wazero.ModuleConfig) WasmPlugin {
|
||||||
loader, err := api.NewWebSocketCallbackPlugin(context.Background(), api.WazeroRuntime(runtime), api.WazeroModuleConfig(mc))
|
loader, err := api.NewWebSocketCallbackPlugin(context.Background(), api.WazeroRuntime(runtime), api.WazeroModuleConfig(mc))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error creating WebSocket callback plugin", "plugin", pluginID, "path", wasmPath, err)
|
log.Error("Error creating WebSocket callback plugin", "plugin", pluginID, "path", wasmPath, err)
|
||||||
|
|||||||
@@ -49,13 +49,13 @@ func (s SchedulerHostFunctions) CancelSchedule(ctx context.Context, req *schedul
|
|||||||
type schedulerService struct {
|
type schedulerService struct {
|
||||||
// Map of schedule IDs to their callback info
|
// Map of schedule IDs to their callback info
|
||||||
schedules map[string]*ScheduledCallback
|
schedules map[string]*ScheduledCallback
|
||||||
manager *Manager
|
manager *managerImpl
|
||||||
navidSched navidsched.Scheduler // Navidrome scheduler for recurring jobs
|
navidSched navidsched.Scheduler // Navidrome scheduler for recurring jobs
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// newSchedulerService creates a new schedulerService instance
|
// newSchedulerService creates a new schedulerService instance
|
||||||
func newSchedulerService(manager *Manager) *schedulerService {
|
func newSchedulerService(manager *managerImpl) *schedulerService {
|
||||||
return &schedulerService{
|
return &schedulerService{
|
||||||
schedules: make(map[string]*ScheduledCallback),
|
schedules: make(map[string]*ScheduledCallback),
|
||||||
manager: manager,
|
manager: manager,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
var _ = Describe("SchedulerService", func() {
|
var _ = Describe("SchedulerService", func() {
|
||||||
var (
|
var (
|
||||||
ss *schedulerService
|
ss *schedulerService
|
||||||
manager *Manager
|
manager *managerImpl
|
||||||
pluginName = "test_plugin"
|
pluginName = "test_plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -50,12 +50,12 @@ func (s WebSocketHostFunctions) Close(ctx context.Context, req *websocket.CloseR
|
|||||||
// websocketService implements the WebSocket service functionality
|
// websocketService implements the WebSocket service functionality
|
||||||
type websocketService struct {
|
type websocketService struct {
|
||||||
connections map[string]*WebSocketConnection
|
connections map[string]*WebSocketConnection
|
||||||
manager *Manager
|
manager *managerImpl
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// newWebsocketService creates a new websocketService instance
|
// newWebsocketService creates a new websocketService instance
|
||||||
func newWebsocketService(manager *Manager) *websocketService {
|
func newWebsocketService(manager *managerImpl) *websocketService {
|
||||||
return &websocketService{
|
return &websocketService{
|
||||||
connections: make(map[string]*WebSocketConnection),
|
connections: make(map[string]*WebSocketConnection),
|
||||||
manager: manager,
|
manager: manager,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
var _ = Describe("WebSocket Host Service", func() {
|
var _ = Describe("WebSocket Host Service", func() {
|
||||||
var (
|
var (
|
||||||
wsService *websocketService
|
wsService *websocketService
|
||||||
manager *Manager
|
manager *managerImpl
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
server *httptest.Server
|
server *httptest.Server
|
||||||
upgrader gorillaws.Upgrader
|
upgrader gorillaws.Upgrader
|
||||||
|
|||||||
+67
-31
@@ -40,7 +40,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// pluginCreators maps capability types to their respective creator functions
|
// pluginCreators maps capability types to their respective creator functions
|
||||||
type pluginConstructor func(wasmPath, pluginID string, m *Manager, runtime api.WazeroNewRuntime, mc wazero.ModuleConfig) WasmPlugin
|
type pluginConstructor func(wasmPath, pluginID string, m *managerImpl, runtime api.WazeroNewRuntime, mc wazero.ModuleConfig) WasmPlugin
|
||||||
|
|
||||||
var pluginCreators = map[string]pluginConstructor{
|
var pluginCreators = map[string]pluginConstructor{
|
||||||
CapabilityMetadataAgent: newWasmMediaAgent,
|
CapabilityMetadataAgent: newWasmMediaAgent,
|
||||||
@@ -86,8 +86,21 @@ func (p *plugin) waitForCompilation() error {
|
|||||||
|
|
||||||
type SubsonicRouter http.Handler
|
type SubsonicRouter http.Handler
|
||||||
|
|
||||||
// Manager is a singleton that manages plugins
|
type Manager interface {
|
||||||
type Manager struct {
|
SetSubsonicRouter(router SubsonicRouter)
|
||||||
|
EnsureCompiled(name string) error
|
||||||
|
PluginNames(serviceName string) []string
|
||||||
|
LoadPlugin(name string, capability string) WasmPlugin
|
||||||
|
LoadAllPlugins(capability string) []WasmPlugin
|
||||||
|
LoadMediaAgent(name string) (agents.Interface, bool)
|
||||||
|
LoadAllMediaAgents() []agents.Interface
|
||||||
|
LoadScrobbler(name string) (scrobbler.Scrobbler, bool)
|
||||||
|
LoadAllScrobblers() []scrobbler.Scrobbler
|
||||||
|
ScanPlugins()
|
||||||
|
}
|
||||||
|
|
||||||
|
// managerImpl is a singleton that manages plugins
|
||||||
|
type managerImpl struct {
|
||||||
plugins map[string]*plugin // Map of plugin folder name to plugin info
|
plugins map[string]*plugin // Map of plugin folder name to plugin info
|
||||||
mu sync.RWMutex // Protects plugins map
|
mu sync.RWMutex // Protects plugins map
|
||||||
subsonicRouter atomic.Pointer[SubsonicRouter] // Subsonic API router
|
subsonicRouter atomic.Pointer[SubsonicRouter] // Subsonic API router
|
||||||
@@ -99,16 +112,19 @@ type Manager struct {
|
|||||||
metrics metrics.Metrics
|
metrics metrics.Metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetManager returns the singleton instance of Manager
|
// GetManager returns the singleton instance of managerImpl
|
||||||
func GetManager(ds model.DataStore, metrics metrics.Metrics) *Manager {
|
func GetManager(ds model.DataStore, metrics metrics.Metrics) Manager {
|
||||||
return singleton.GetInstance(func() *Manager {
|
if !conf.Server.Plugins.Enabled {
|
||||||
|
return &noopManager{}
|
||||||
|
}
|
||||||
|
return singleton.GetInstance(func() *managerImpl {
|
||||||
return createManager(ds, metrics)
|
return createManager(ds, metrics)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// createManager creates a new Manager instance. Used in tests
|
// createManager creates a new managerImpl instance. Used in tests
|
||||||
func createManager(ds model.DataStore, metrics metrics.Metrics) *Manager {
|
func createManager(ds model.DataStore, metrics metrics.Metrics) *managerImpl {
|
||||||
m := &Manager{
|
m := &managerImpl{
|
||||||
plugins: make(map[string]*plugin),
|
plugins: make(map[string]*plugin),
|
||||||
lifecycle: newPluginLifecycleManager(),
|
lifecycle: newPluginLifecycleManager(),
|
||||||
ds: ds,
|
ds: ds,
|
||||||
@@ -122,14 +138,14 @@ func createManager(ds model.DataStore, metrics metrics.Metrics) *Manager {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSubsonicRouter sets the SubsonicRouter after Manager initialization
|
// SetSubsonicRouter sets the SubsonicRouter after managerImpl initialization
|
||||||
func (m *Manager) SetSubsonicRouter(router SubsonicRouter) {
|
func (m *managerImpl) SetSubsonicRouter(router SubsonicRouter) {
|
||||||
m.subsonicRouter.Store(&router)
|
m.subsonicRouter.Store(&router)
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerPlugin adds a plugin to the registry with the given parameters
|
// registerPlugin adds a plugin to the registry with the given parameters
|
||||||
// Used internally by ScanPlugins to register plugins
|
// Used internally by ScanPlugins to register plugins
|
||||||
func (m *Manager) registerPlugin(pluginID, pluginDir, wasmPath string, manifest *schema.PluginManifest) *plugin {
|
func (m *managerImpl) registerPlugin(pluginID, pluginDir, wasmPath string, manifest *schema.PluginManifest) *plugin {
|
||||||
// Create custom runtime function
|
// Create custom runtime function
|
||||||
customRuntime := m.createRuntime(pluginID, manifest.Permissions)
|
customRuntime := m.createRuntime(pluginID, manifest.Permissions)
|
||||||
|
|
||||||
@@ -190,7 +206,7 @@ func (m *Manager) registerPlugin(pluginID, pluginDir, wasmPath string, manifest
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initializePluginIfNeeded calls OnInit on plugins that implement LifecycleManagement
|
// initializePluginIfNeeded calls OnInit on plugins that implement LifecycleManagement
|
||||||
func (m *Manager) initializePluginIfNeeded(plugin *plugin) {
|
func (m *managerImpl) initializePluginIfNeeded(plugin *plugin) {
|
||||||
// Skip if already initialized
|
// Skip if already initialized
|
||||||
if m.lifecycle.isInitialized(plugin) {
|
if m.lifecycle.isInitialized(plugin) {
|
||||||
return
|
return
|
||||||
@@ -207,7 +223,7 @@ func (m *Manager) initializePluginIfNeeded(plugin *plugin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ScanPlugins scans the plugins directory, discovers all valid plugins, and registers them for use.
|
// ScanPlugins scans the plugins directory, discovers all valid plugins, and registers them for use.
|
||||||
func (m *Manager) ScanPlugins() {
|
func (m *managerImpl) ScanPlugins() {
|
||||||
// Clear existing plugins
|
// Clear existing plugins
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
m.plugins = make(map[string]*plugin)
|
m.plugins = make(map[string]*plugin)
|
||||||
@@ -259,7 +275,7 @@ func (m *Manager) ScanPlugins() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PluginNames returns the folder names of all plugins that implement the specified capability
|
// PluginNames returns the folder names of all plugins that implement the specified capability
|
||||||
func (m *Manager) PluginNames(capability string) []string {
|
func (m *managerImpl) PluginNames(capability string) []string {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
@@ -275,28 +291,26 @@ func (m *Manager) PluginNames(capability string) []string {
|
|||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) getPlugin(name string, capability string) (*plugin, WasmPlugin) {
|
func (m *managerImpl) getPlugin(name string, capability string) (*plugin, WasmPlugin, error) {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
info, infoOk := m.plugins[name]
|
info, infoOk := m.plugins[name]
|
||||||
adapter, adapterOk := m.adapters[name+"_"+capability]
|
adapter, adapterOk := m.adapters[name+"_"+capability]
|
||||||
|
|
||||||
if !infoOk {
|
if !infoOk {
|
||||||
log.Warn("Plugin not found", "name", name)
|
return nil, nil, fmt.Errorf("plugin not registered: %s", name)
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
if !adapterOk {
|
if !adapterOk {
|
||||||
log.Warn("Plugin adapter not found", "name", name, "capability", capability)
|
return nil, nil, fmt.Errorf("plugin adapter not registered: %s, capability: %s", name, capability)
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
return info, adapter
|
return info, adapter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadPlugin instantiates and returns a plugin by folder name
|
// LoadPlugin instantiates and returns a plugin by folder name
|
||||||
func (m *Manager) LoadPlugin(name string, capability string) WasmPlugin {
|
func (m *managerImpl) LoadPlugin(name string, capability string) WasmPlugin {
|
||||||
info, adapter := m.getPlugin(name, capability)
|
info, adapter, err := m.getPlugin(name, capability)
|
||||||
if info == nil {
|
if err != nil {
|
||||||
log.Warn("Plugin not found", "name", name, "capability", capability)
|
log.Warn("Error loading plugin", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +332,7 @@ func (m *Manager) LoadPlugin(name string, capability string) WasmPlugin {
|
|||||||
// EnsureCompiled waits for a plugin to finish compilation and returns any compilation error.
|
// EnsureCompiled waits for a plugin to finish compilation and returns any compilation error.
|
||||||
// This is useful when you need to wait for compilation without loading a specific capability,
|
// This is useful when you need to wait for compilation without loading a specific capability,
|
||||||
// such as during plugin refresh operations or health checks.
|
// such as during plugin refresh operations or health checks.
|
||||||
func (m *Manager) EnsureCompiled(name string) error {
|
func (m *managerImpl) EnsureCompiled(name string) error {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
plugin, ok := m.plugins[name]
|
plugin, ok := m.plugins[name]
|
||||||
m.mu.RUnlock()
|
m.mu.RUnlock()
|
||||||
@@ -331,7 +345,7 @@ func (m *Manager) EnsureCompiled(name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadAllPlugins instantiates and returns all plugins that implement the specified capability
|
// LoadAllPlugins instantiates and returns all plugins that implement the specified capability
|
||||||
func (m *Manager) LoadAllPlugins(capability string) []WasmPlugin {
|
func (m *managerImpl) LoadAllPlugins(capability string) []WasmPlugin {
|
||||||
names := m.PluginNames(capability)
|
names := m.PluginNames(capability)
|
||||||
if len(names) == 0 {
|
if len(names) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -348,7 +362,7 @@ func (m *Manager) LoadAllPlugins(capability string) []WasmPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadMediaAgent instantiates and returns a media agent plugin by folder name
|
// LoadMediaAgent instantiates and returns a media agent plugin by folder name
|
||||||
func (m *Manager) LoadMediaAgent(name string) (agents.Interface, bool) {
|
func (m *managerImpl) LoadMediaAgent(name string) (agents.Interface, bool) {
|
||||||
plugin := m.LoadPlugin(name, CapabilityMetadataAgent)
|
plugin := m.LoadPlugin(name, CapabilityMetadataAgent)
|
||||||
if plugin == nil {
|
if plugin == nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -358,7 +372,7 @@ func (m *Manager) LoadMediaAgent(name string) (agents.Interface, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadAllMediaAgents instantiates and returns all media agent plugins
|
// LoadAllMediaAgents instantiates and returns all media agent plugins
|
||||||
func (m *Manager) LoadAllMediaAgents() []agents.Interface {
|
func (m *managerImpl) LoadAllMediaAgents() []agents.Interface {
|
||||||
plugins := m.LoadAllPlugins(CapabilityMetadataAgent)
|
plugins := m.LoadAllPlugins(CapabilityMetadataAgent)
|
||||||
|
|
||||||
return slice.Map(plugins, func(p WasmPlugin) agents.Interface {
|
return slice.Map(plugins, func(p WasmPlugin) agents.Interface {
|
||||||
@@ -367,7 +381,7 @@ func (m *Manager) LoadAllMediaAgents() []agents.Interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadScrobbler instantiates and returns a scrobbler plugin by folder name
|
// LoadScrobbler instantiates and returns a scrobbler plugin by folder name
|
||||||
func (m *Manager) LoadScrobbler(name string) (scrobbler.Scrobbler, bool) {
|
func (m *managerImpl) LoadScrobbler(name string) (scrobbler.Scrobbler, bool) {
|
||||||
plugin := m.LoadPlugin(name, CapabilityScrobbler)
|
plugin := m.LoadPlugin(name, CapabilityScrobbler)
|
||||||
if plugin == nil {
|
if plugin == nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -377,10 +391,32 @@ func (m *Manager) LoadScrobbler(name string) (scrobbler.Scrobbler, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadAllScrobblers instantiates and returns all scrobbler plugins
|
// LoadAllScrobblers instantiates and returns all scrobbler plugins
|
||||||
func (m *Manager) LoadAllScrobblers() []scrobbler.Scrobbler {
|
func (m *managerImpl) LoadAllScrobblers() []scrobbler.Scrobbler {
|
||||||
plugins := m.LoadAllPlugins(CapabilityScrobbler)
|
plugins := m.LoadAllPlugins(CapabilityScrobbler)
|
||||||
|
|
||||||
return slice.Map(plugins, func(p WasmPlugin) scrobbler.Scrobbler {
|
return slice.Map(plugins, func(p WasmPlugin) scrobbler.Scrobbler {
|
||||||
return p.(scrobbler.Scrobbler)
|
return p.(scrobbler.Scrobbler)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type noopManager struct{}
|
||||||
|
|
||||||
|
func (n noopManager) SetSubsonicRouter(router SubsonicRouter) {}
|
||||||
|
|
||||||
|
func (n noopManager) EnsureCompiled(name string) error { return nil }
|
||||||
|
|
||||||
|
func (n noopManager) PluginNames(serviceName string) []string { return nil }
|
||||||
|
|
||||||
|
func (n noopManager) LoadPlugin(name string, capability string) WasmPlugin { return nil }
|
||||||
|
|
||||||
|
func (n noopManager) LoadAllPlugins(capability string) []WasmPlugin { return nil }
|
||||||
|
|
||||||
|
func (n noopManager) LoadMediaAgent(name string) (agents.Interface, bool) { return nil, false }
|
||||||
|
|
||||||
|
func (n noopManager) LoadAllMediaAgents() []agents.Interface { return nil }
|
||||||
|
|
||||||
|
func (n noopManager) LoadScrobbler(name string) (scrobbler.Scrobbler, bool) { return nil, false }
|
||||||
|
|
||||||
|
func (n noopManager) LoadAllScrobblers() []scrobbler.Scrobbler { return nil }
|
||||||
|
|
||||||
|
func (n noopManager) ScanPlugins() {}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Plugin Manager", func() {
|
var _ = Describe("Plugin Manager", func() {
|
||||||
var mgr *Manager
|
var mgr *managerImpl
|
||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
@@ -76,7 +76,7 @@ var _ = Describe("Plugin Manager", func() {
|
|||||||
|
|
||||||
Describe("ScanPlugins", func() {
|
Describe("ScanPlugins", func() {
|
||||||
var tempPluginsDir string
|
var tempPluginsDir string
|
||||||
var m *Manager
|
var m *managerImpl
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
tempPluginsDir, _ = os.MkdirTemp("", "navidrome-plugins-test-*")
|
tempPluginsDir, _ = os.MkdirTemp("", "navidrome-plugins-test-*")
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func createTestPlugin(tempDir, name string, permissions schema.PluginManifestPer
|
|||||||
|
|
||||||
var _ = Describe("Plugin Permissions", func() {
|
var _ = Describe("Plugin Permissions", func() {
|
||||||
var (
|
var (
|
||||||
mgr *Manager
|
mgr *managerImpl
|
||||||
tempDir string
|
tempDir string
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func hasInitService(info *plugin) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ = Describe("LifecycleManagement", func() {
|
var _ = Describe("LifecycleManagement", func() {
|
||||||
Describe("Plugin Lifecycle Manager", func() {
|
Describe("Plugin Lifecycle managerImpl", func() {
|
||||||
var lifecycleManager *pluginLifecycleManager
|
var lifecycleManager *pluginLifecycleManager
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
|
|||||||
+3
-3
@@ -41,7 +41,7 @@ var (
|
|||||||
|
|
||||||
// createRuntime returns a function that creates a new wazero runtime and instantiates the required host functions
|
// createRuntime returns a function that creates a new wazero runtime and instantiates the required host functions
|
||||||
// based on the given plugin permissions
|
// based on the given plugin permissions
|
||||||
func (m *Manager) createRuntime(pluginID string, permissions schema.PluginManifestPermissions) api.WazeroNewRuntime {
|
func (m *managerImpl) createRuntime(pluginID string, permissions schema.PluginManifestPermissions) api.WazeroNewRuntime {
|
||||||
return func(ctx context.Context) (wazero.Runtime, error) {
|
return func(ctx context.Context) (wazero.Runtime, error) {
|
||||||
// Check if runtime already exists
|
// Check if runtime already exists
|
||||||
if rt, ok := runtimePool.Load(pluginID); ok {
|
if rt, ok := runtimePool.Load(pluginID); ok {
|
||||||
@@ -70,7 +70,7 @@ func (m *Manager) createRuntime(pluginID string, permissions schema.PluginManife
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createCachingRuntime handles the complex logic of setting up a new cachingRuntime
|
// createCachingRuntime handles the complex logic of setting up a new cachingRuntime
|
||||||
func (m *Manager) createCachingRuntime(ctx context.Context, pluginID string, permissions schema.PluginManifestPermissions) (*cachingRuntime, error) {
|
func (m *managerImpl) createCachingRuntime(ctx context.Context, pluginID string, permissions schema.PluginManifestPermissions) (*cachingRuntime, error) {
|
||||||
// Get compilation cache
|
// Get compilation cache
|
||||||
compCache, err := getCompilationCache()
|
compCache, err := getCompilationCache()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -94,7 +94,7 @@ func (m *Manager) createCachingRuntime(ctx context.Context, pluginID string, per
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setupHostServices configures all the permitted host services for a plugin
|
// setupHostServices configures all the permitted host services for a plugin
|
||||||
func (m *Manager) setupHostServices(ctx context.Context, r wazero.Runtime, pluginID string, permissions schema.PluginManifestPermissions) error {
|
func (m *managerImpl) setupHostServices(ctx context.Context, r wazero.Runtime, pluginID string, permissions schema.PluginManifestPermissions) error {
|
||||||
// Define all available host services
|
// Define all available host services
|
||||||
type hostService struct {
|
type hostService struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ var _ = Describe("Runtime", func() {
|
|||||||
var _ = Describe("CachingRuntime", func() {
|
var _ = Describe("CachingRuntime", func() {
|
||||||
var (
|
var (
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
mgr *Manager
|
mgr *managerImpl
|
||||||
plugin *wasmScrobblerPlugin
|
plugin *wasmScrobblerPlugin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user