update
Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
POEditor import / update-translations (push) Has been cancelled
Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
POEditor import / update-translations (push) Has been cancelled
This commit is contained in:
145
plugins/discovery.go
Normal file
145
plugins/discovery.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/navidrome/navidrome/plugins/schema"
|
||||
)
|
||||
|
||||
// PluginDiscoveryEntry represents the result of plugin discovery
|
||||
type PluginDiscoveryEntry struct {
|
||||
ID string // Plugin ID (directory name)
|
||||
Path string // Resolved plugin directory path
|
||||
WasmPath string // Path to the WASM file
|
||||
Manifest *schema.PluginManifest // Loaded manifest (nil if failed)
|
||||
IsSymlink bool // Whether the plugin is a development symlink
|
||||
Error error // Error encountered during discovery
|
||||
}
|
||||
|
||||
// DiscoverPlugins scans the plugins directory and returns information about all discoverable plugins
|
||||
// This shared function eliminates duplication between ScanPlugins and plugin list commands
|
||||
func DiscoverPlugins(pluginsDir string) []PluginDiscoveryEntry {
|
||||
var discoveries []PluginDiscoveryEntry
|
||||
|
||||
entries, err := os.ReadDir(pluginsDir)
|
||||
if err != nil {
|
||||
// Return a single entry with the error
|
||||
return []PluginDiscoveryEntry{{
|
||||
Error: fmt.Errorf("failed to read plugins directory %s: %w", pluginsDir, err),
|
||||
}}
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
pluginPath := filepath.Join(pluginsDir, name)
|
||||
|
||||
// Skip hidden files
|
||||
if name[0] == '.' {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if it's a directory or symlink
|
||||
info, err := os.Lstat(pluginPath)
|
||||
if err != nil {
|
||||
discoveries = append(discoveries, PluginDiscoveryEntry{
|
||||
ID: name,
|
||||
Error: fmt.Errorf("failed to stat entry %s: %w", pluginPath, err),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
isSymlink := info.Mode()&os.ModeSymlink != 0
|
||||
isDir := info.IsDir()
|
||||
|
||||
// Skip if not a directory or symlink
|
||||
if !isDir && !isSymlink {
|
||||
continue
|
||||
}
|
||||
|
||||
// Resolve symlinks
|
||||
pluginDir := pluginPath
|
||||
if isSymlink {
|
||||
targetDir, err := os.Readlink(pluginPath)
|
||||
if err != nil {
|
||||
discoveries = append(discoveries, PluginDiscoveryEntry{
|
||||
ID: name,
|
||||
IsSymlink: true,
|
||||
Error: fmt.Errorf("failed to resolve symlink %s: %w", pluginPath, err),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// If target is a relative path, make it absolute
|
||||
if !filepath.IsAbs(targetDir) {
|
||||
targetDir = filepath.Join(filepath.Dir(pluginPath), targetDir)
|
||||
}
|
||||
|
||||
// Verify that the target is a directory
|
||||
targetInfo, err := os.Stat(targetDir)
|
||||
if err != nil {
|
||||
discoveries = append(discoveries, PluginDiscoveryEntry{
|
||||
ID: name,
|
||||
IsSymlink: true,
|
||||
Error: fmt.Errorf("failed to stat symlink target %s: %w", targetDir, err),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if !targetInfo.IsDir() {
|
||||
discoveries = append(discoveries, PluginDiscoveryEntry{
|
||||
ID: name,
|
||||
IsSymlink: true,
|
||||
Error: fmt.Errorf("symlink target is not a directory: %s", targetDir),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
pluginDir = targetDir
|
||||
}
|
||||
|
||||
// Check for WASM file
|
||||
wasmPath := filepath.Join(pluginDir, "plugin.wasm")
|
||||
if _, err := os.Stat(wasmPath); err != nil {
|
||||
discoveries = append(discoveries, PluginDiscoveryEntry{
|
||||
ID: name,
|
||||
Path: pluginDir,
|
||||
Error: fmt.Errorf("no plugin.wasm found: %w", err),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Load manifest
|
||||
manifest, err := LoadManifest(pluginDir)
|
||||
if err != nil {
|
||||
discoveries = append(discoveries, PluginDiscoveryEntry{
|
||||
ID: name,
|
||||
Path: pluginDir,
|
||||
Error: fmt.Errorf("failed to load manifest: %w", err),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for capabilities
|
||||
if len(manifest.Capabilities) == 0 {
|
||||
discoveries = append(discoveries, PluginDiscoveryEntry{
|
||||
ID: name,
|
||||
Path: pluginDir,
|
||||
Error: fmt.Errorf("no capabilities found in manifest"),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Success!
|
||||
discoveries = append(discoveries, PluginDiscoveryEntry{
|
||||
ID: name,
|
||||
Path: pluginDir,
|
||||
WasmPath: wasmPath,
|
||||
Manifest: manifest,
|
||||
IsSymlink: isSymlink,
|
||||
})
|
||||
}
|
||||
|
||||
return discoveries
|
||||
}
|
||||
Reference in New Issue
Block a user