Files
navidrome/plugins/manager_plugin.go
Jorge Pardo Pardo 85e9982b43 feat(plugins): add path to Scrobbler and Lyrics plugin TrackInfo (#5339)
* feat: add Path to TrackInfo struct

* refactor: improve naming to follow the rest of the code

* test: add tests

* fix: actually check for filesystem permission

* refactor: remove library logic from specific plugins

* refactor: move hasFilesystemPermission to a Manifest method

* test(plugins): add unit tests for hasLibraryFilesystemAccess method

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): remove hasFilesystemPerm field and use manifest for filesystem permission checks

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): streamline library filesystem access checks in lyrics and scrobbler adapters

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan <deluan@navidrome.org>
2026-04-12 10:27:58 -04:00

78 lines
2.2 KiB
Go

package plugins
import (
"context"
"crypto/rand"
"errors"
"io"
extism "github.com/extism/go-sdk"
"github.com/tetratelabs/wazero"
)
// plugin represents a loaded plugin
type plugin struct {
name string // Plugin name (from filename)
path string // Path to the wasm file
manifest *Manifest
compiled *extism.CompiledPlugin
capabilities []Capability // Auto-detected capabilities based on exported functions
closers []io.Closer // Cleanup functions to call on unload
metrics PluginMetricsRecorder
allowedUserIDs []string // User IDs this plugin can access (from DB configuration)
allUsers bool // If true, plugin can access all users
libraries libraryAccess
}
// instance creates a new plugin instance for the given context.
// The context is used for cancellation - if cancelled during a call,
// the module will be terminated and the instance becomes unusable.
func (p *plugin) instance(ctx context.Context) (*extism.Plugin, error) {
instance, err := p.compiled.Instance(ctx, extism.PluginInstanceConfig{
ModuleConfig: wazero.NewModuleConfig().WithSysWalltime().WithRandSource(rand.Reader),
})
if err != nil {
return nil, err
}
instance.SetLogger(extismLogger(p.name))
return instance, nil
}
func (p *plugin) Close() error {
var errs []error
for _, f := range p.closers {
err := f.Close()
if err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
func (p *plugin) hasLibraryFilesystemAccess(libID int) bool {
return p.manifest.HasLibraryFilesystemPermission() && p.libraries.contains(libID)
}
// libraryAccess captures the set of libraries a plugin is permitted to see,
// precomputed at load time for O(1) lookup.
type libraryAccess struct {
allLibraries bool
libraryIDSet map[int]struct{}
}
func newLibraryAccess(allowedLibraryIDs []int, allLibraries bool) libraryAccess {
set := make(map[int]struct{}, len(allowedLibraryIDs))
for _, id := range allowedLibraryIDs {
set[id] = struct{}{}
}
return libraryAccess{allLibraries: allLibraries, libraryIDSet: set}
}
func (a libraryAccess) contains(libID int) bool {
if a.allLibraries {
return true
}
_, ok := a.libraryIDSet[libID]
return ok
}