package loader
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
log "github.com/sirupsen/logrus"
)
type PluginInfo struct {
Path string
Name string
Type PluginType
IsLoaded bool
}
func DiscoverPlugins(dir string) ([]PluginInfo, error) {
if dir == "" {
return []PluginInfo{}, nil
}
stat, err := os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
return []PluginInfo{}, nil
}
return nil, fmt.Errorf("failed to stat plugin directory: %w", err)
}
if !stat.IsDir() {
return nil, fmt.Errorf("plugin path is not a directory: %s", dir)
}
nativeExt := getPluginExtension()
wasmExt := ".wasm"
var plugins []PluginInfo
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Warnf("Error accessing path %s: %v", path, err)
return nil
}
if info.IsDir() {
return nil
}
var pluginType PluginType
var name string
if strings.HasSuffix(info.Name(), nativeExt) {
pluginType = PluginTypeNative
name = strings.TrimSuffix(info.Name(), nativeExt)
} else if strings.HasSuffix(info.Name(), wasmExt) {
pluginType = PluginTypeWASM
name = strings.TrimSuffix(info.Name(), wasmExt)
} else {
return nil
}
plugins = append(plugins, PluginInfo{
Path: path,
Name: name,
Type: pluginType,
IsLoaded: false,
})
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to walk plugin directory: %w", err)
}
log.Infof("Discovered %d plugin(s) in %s (%d native, %d WASM)",
len(plugins), dir, countPluginsByType(plugins, PluginTypeNative), countPluginsByType(plugins, PluginTypeWASM))
return plugins, nil
}
func countPluginsByType(plugins []PluginInfo, pluginType PluginType) int {
count := 0
for _, p := range plugins {
if p.Type == pluginType {
count++
}
}
return count
}
func getPluginExtension() string {
switch runtime.GOOS {
case "darwin":
return ".dylib"
case "linux":
return ".so"
case "windows":
return ".dll"
default:
return ".so"
}
}
func (pl *PluginLoader) LoadPluginsFromDirectory(dir string) ([]string, []error) {
plugins, err := DiscoverPlugins(dir)
if err != nil {
return nil, []error{err}
}
var loaded []string
var errors []error
for _, pluginInfo := range plugins {
_, err := pl.LoadPlugin(pluginInfo.Path)
if err != nil {
errors = append(errors, fmt.Errorf("failed to load %s: %w", pluginInfo.Name, err))
log.Errorf("Failed to load plugin %s: %v", pluginInfo.Path, err)
} else {
loaded = append(loaded, pluginInfo.Path)
log.Infof("Loaded plugin: %s", pluginInfo.Name)
}
}
return loaded, errors
}
func ValidatePluginPath(path string) error {
stat, err := os.Stat(path)
if err != nil {
return fmt.Errorf("plugin file not found: %w", err)
}
if stat.IsDir() {
return fmt.Errorf("plugin path is a directory, not a file")
}
nativeExt := getPluginExtension()
wasmExt := ".wasm"
if !strings.HasSuffix(path, nativeExt) && !strings.HasSuffix(path, wasmExt) {
return fmt.Errorf("invalid plugin file extension (expected %s or %s)", nativeExt, wasmExt)
}
file, err := os.Open(path)
if err != nil {
return fmt.Errorf("cannot open plugin file: %w", err)
}
file.Close()
return nil
}