Files
poimen-workflows/internal/plugins/loader.go
T

268 lines
6.4 KiB
Go
Raw Normal View History

package plugins
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"time"
)
// PluginLoader loads and manages plugin lifecycle
type PluginLoader struct {
mu sync.RWMutex
registry *PluginRegistry
pluginPath string
loadedPlugins map[string]*LoadedPlugin
loadTime map[string]time.Time
failedLoads map[string]error
}
// LoadedPlugin represents a loaded plugin with additional metadata
type LoadedPlugin struct {
Plugin SkillPlugin
LoadedAt time.Time
ReloadCount int
LastError error
}
// PluginConfig represents plugin configuration from file
type PluginConfig struct {
Name string `json:"name"`
Version string `json:"version"`
Author string `json:"author"`
Description string `json:"description"`
Path string `json:"path"`
Config map[string]interface{} `json:"config,omitempty"`
Enabled bool `json:"enabled"`
}
// NewPluginLoader creates a new plugin loader
func NewPluginLoader(registry *PluginRegistry, pluginPath string) *PluginLoader {
return &PluginLoader{
registry: registry,
pluginPath: pluginPath,
loadedPlugins: make(map[string]*LoadedPlugin),
loadTime: make(map[string]time.Time),
failedLoads: make(map[string]error),
}
}
// LoadPlugin loads a plugin from URL
func (pl *PluginLoader) LoadPlugin(url string) error {
pl.mu.Lock()
defer pl.mu.Unlock()
if IsPluginURL(url) {
// Already loaded - no need to load from file
return nil
}
// Try to load from file
return pl.loadFromFileLocked(url)
}
// loadFromFileLocked loads a plugin from a file (must be called with lock held)
func (pl *PluginLoader) loadFromFileLocked(path string) error {
// Read config file
data, err := os.ReadFile(path)
if err != nil {
pl.failedLoads[path] = err
return fmt.Errorf("failed to read plugin config: %w", err)
}
var config PluginConfig
if err := json.Unmarshal(data, &config); err != nil {
pl.failedLoads[path] = err
return fmt.Errorf("failed to parse plugin config: %w", err)
}
// For now, return a placeholder plugin load
// In a real implementation, this would use reflection or plugin packages
// to dynamically load compiled plugins
pl.loadTime[config.Name] = time.Now()
return nil
}
// LoadPluginDirectory loads all plugins from a directory
func (pl *PluginLoader) LoadPluginDirectory(directory string) error {
entries, err := os.ReadDir(directory)
if err != nil {
return fmt.Errorf("failed to read plugin directory: %w", err)
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
if filepath.Ext(entry.Name()) == ".json" {
pluginPath := filepath.Join(directory, entry.Name())
if err := pl.LoadPlugin(pluginPath); err != nil {
// Log error but continue loading other plugins
pl.mu.Lock()
pl.failedLoads[pluginPath] = err
pl.mu.Unlock()
}
}
}
return nil
}
// RegisterLoadedPlugin registers a loaded plugin with the registry
func (pl *PluginLoader) RegisterLoadedPlugin(plugin SkillPlugin, author string, config map[string]interface{}) error {
pl.mu.Lock()
defer pl.mu.Unlock()
if err := pl.registry.Register(plugin, author, config); err != nil {
pl.failedLoads[plugin.Name()] = err
return err
}
now := time.Now()
pl.loadedPlugins[plugin.Name()] = &LoadedPlugin{
Plugin: plugin,
LoadedAt: now,
LastError: nil,
}
pl.loadTime[plugin.Name()] = now
return nil
}
// UnloadPlugin unloads a plugin
func (pl *PluginLoader) UnloadPlugin(name string) error {
pl.mu.Lock()
defer pl.mu.Unlock()
if _, exists := pl.loadedPlugins[name]; !exists {
return fmt.Errorf("plugin not loaded: %s", name)
}
err := pl.registry.Unregister(name)
if err == nil {
delete(pl.loadedPlugins, name)
delete(pl.loadTime, name)
}
return err
}
// ReloadPlugin reloads a plugin
func (pl *PluginLoader) ReloadPlugin(name string) error {
pl.mu.Lock()
defer pl.mu.Unlock()
loadedPlugin, exists := pl.loadedPlugins[name]
if !exists {
return fmt.Errorf("plugin not loaded: %s", name)
}
// Re-validate plugin
if err := loadedPlugin.Plugin.Validate(); err != nil {
pl.failedLoads[name] = err
loadedPlugin.LastError = err
return fmt.Errorf("plugin validation failed: %w", err)
}
loadedPlugin.ReloadCount++
loadedPlugin.LastError = nil
pl.loadTime[name] = time.Now()
return nil
}
// GetLoadedPlugins returns all loaded plugins
func (pl *PluginLoader) GetLoadedPlugins() map[string]*LoadedPlugin {
pl.mu.RLock()
defer pl.mu.RUnlock()
result := make(map[string]*LoadedPlugin)
for name, plugin := range pl.loadedPlugins {
result[name] = plugin
}
return result
}
// GetFailedLoads returns all failed plugin loads
func (pl *PluginLoader) GetFailedLoads() map[string]error {
pl.mu.RLock()
defer pl.mu.RUnlock()
result := make(map[string]error)
for path, err := range pl.failedLoads {
result[path] = err
}
return result
}
// GetLoadTime returns when a plugin was loaded
func (pl *PluginLoader) GetLoadTime(name string) (time.Time, bool) {
pl.mu.RLock()
defer pl.mu.RUnlock()
t, exists := pl.loadTime[name]
return t, exists
}
// IsPluginLoaded checks if a plugin is loaded
func (pl *PluginLoader) IsPluginLoaded(name string) bool {
pl.mu.RLock()
defer pl.mu.RUnlock()
_, exists := pl.loadedPlugins[name]
return exists
}
// ExecutePlugin executes a loaded plugin
func (pl *PluginLoader) ExecutePlugin(name string, input map[string]interface{}) (map[string]interface{}, error) {
pl.mu.RLock()
if _, exists := pl.loadedPlugins[name]; !exists {
pl.mu.RUnlock()
return nil, fmt.Errorf("plugin not loaded: %s", name)
}
pl.mu.RUnlock()
return pl.registry.Execute(name, input)
}
// GetPluginStats returns stats for a loaded plugin
func (pl *PluginLoader) GetPluginStats(name string) (*PluginStats, error) {
pl.mu.RLock()
defer pl.mu.RUnlock()
if _, exists := pl.loadedPlugins[name]; !exists {
return nil, fmt.Errorf("plugin not loaded: %s", name)
}
return pl.registry.GetStats(), nil
}
// Close closes the plugin loader and unloads all plugins
func (pl *PluginLoader) Close() error {
pl.mu.Lock()
defer pl.mu.Unlock()
var lastErr error
for name := range pl.loadedPlugins {
if err := pl.registry.Unregister(name); err != nil {
lastErr = err
}
}
pl.loadedPlugins = make(map[string]*LoadedPlugin)
pl.loadTime = make(map[string]time.Time)
return lastErr
}
// GetPluginRegistry returns the underlying registry
func (pl *PluginLoader) GetPluginRegistry() *PluginRegistry {
return pl.registry
}