verstak/internal/core/plugins/manager_lifecycle.go

110 lines
2.7 KiB
Go

package plugins
import (
"fmt"
"log"
"os"
"path/filepath"
lua "github.com/yuin/gopher-lua"
)
// ActivatePlugin fully activates a plugin: creates Lua VM, loads main.lua, starts scheduler.
// Only works if plugin is installed and enabled but not yet active.
// Returns error if VM creation, script loading, scheduler setup, or on_init hook fails.
func (m *Manager) ActivatePlugin(name string) error {
for i := range m.plugins {
p := &m.plugins[i]
if p.Meta.Name != name || p.Active {
continue
}
if !p.Enabled {
return fmt.Errorf("plugin %q is not enabled", name)
}
if p.HasInstall && !p.Installed {
return fmt.Errorf("plugin %q is not installed", name)
}
vm, err := NewLuaVM(p)
if err != nil {
return fmt.Errorf("create VM for %q: %w", name, err)
}
p.vm = vm
if m.Services != nil {
vm.SetServices(m.Services)
}
mainPath := filepath.Join(p.Dir, "main.lua")
if _, err := os.Stat(mainPath); err == nil {
if err := vm.LoadScript("main.lua"); err != nil {
p.vm.Close()
p.vm = nil
return fmt.Errorf("load main.lua for %q: %w", name, err)
}
}
p.scheduler = NewScheduler(p, vm)
for _, bg := range p.Meta.Background {
if err := p.scheduler.AddTask(bg); err != nil {
p.vm.Close()
p.vm = nil
p.scheduler = nil
return fmt.Errorf("add task %s for %q: %w", bg.ID, name, err)
}
}
p.scheduler.Start()
if hookName, ok := p.Meta.Hooks["on_init"]; ok {
if err := vm.CallHook(hookName); err != nil {
// on_init failure is non-fatal for activation — log but continue
log.Printf("[plugins] %s: on_init error: %v", name, err)
}
}
p.Active = true
log.Printf("[plugins] %s: activated", name)
return nil
}
return fmt.Errorf("plugin %q not found", name)
}
// DeactivatePlugin stops a plugin's runtime without removing it from enabled list.
func (m *Manager) DeactivatePlugin(name string) {
for i := range m.plugins {
p := &m.plugins[i]
if p.Meta.Name != name || !p.Active {
continue
}
if p.scheduler != nil {
p.scheduler.Stop()
p.scheduler = nil
}
if hookName, ok := p.Meta.Hooks["on_shutdown"]; ok && p.vm != nil {
_ = p.vm.CallHook(hookName)
}
if p.vm != nil {
p.vm.Close()
p.vm = nil
}
p.Active = false
log.Printf("[plugins] %s: deactivated", name)
return
}
}
// CallPluginHook calls a named Lua function on a specific plugin.
func (m *Manager) CallPluginHook(name, hookName string, args ...lua.LValue) (lua.LValue, error) {
for i := range m.plugins {
if m.plugins[i].Meta.Name == name && m.plugins[i].Active && m.plugins[i].vm != nil {
if fn, ok := m.plugins[i].Meta.Hooks[hookName]; ok {
return m.plugins[i].vm.CallHookWithResult(fn, args...)
}
return m.plugins[i].vm.CallHookWithResult(hookName, args...)
}
}
return lua.LNil, nil
}