Delete internal/plugin/old-plugin.go
This commit is contained in:
@@ -1,797 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Safronov Grigorii
|
|
||||||
*
|
|
||||||
* Licensed under the CDDL, Version 1.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
*
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* https://opensource.org/licenses/CDDL-1.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Файл: internal/plugin/plugin.go
|
|
||||||
// Назначение: Система плагинов на основе Lua для расширения функциональности СУБД.
|
|
||||||
// Позволяет загружать Lua-скрипты как плагины, выполнять их в изолированном окружении,
|
|
||||||
// взаимодействовать с данными СУБД и логировать действия плагинов в общий лог-файл.
|
|
||||||
|
|
||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"futriis/internal/log"
|
|
||||||
"futriis/internal/storage"
|
|
||||||
|
|
||||||
lua "github.com/yuin/gopher-lua"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PluginStatus представляет состояние плагина
|
|
||||||
type PluginStatus int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
StatusLoaded PluginStatus = iota
|
|
||||||
StatusRunning
|
|
||||||
StatusStopped
|
|
||||||
StatusError
|
|
||||||
)
|
|
||||||
|
|
||||||
// Plugin представляет загруженный Lua-плагин
|
|
||||||
type Plugin struct {
|
|
||||||
Name string
|
|
||||||
FilePath string
|
|
||||||
Status atomic.Int32
|
|
||||||
LState *lua.LState
|
|
||||||
logger *log.Logger
|
|
||||||
storage *storage.Storage
|
|
||||||
mu sync.RWMutex
|
|
||||||
loadedAt time.Time
|
|
||||||
version string
|
|
||||||
author string
|
|
||||||
description string
|
|
||||||
}
|
|
||||||
|
|
||||||
// PluginManager управляет всеми загруженными плагинами
|
|
||||||
type PluginManager struct {
|
|
||||||
plugins sync.Map // map[string]*Plugin
|
|
||||||
logger *log.Logger
|
|
||||||
storage *storage.Storage
|
|
||||||
pluginsDir string
|
|
||||||
eventBus chan PluginEvent
|
|
||||||
enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// PluginEvent представляет событие от плагина
|
|
||||||
type PluginEvent struct {
|
|
||||||
PluginName string
|
|
||||||
EventType string
|
|
||||||
Data interface{}
|
|
||||||
Timestamp int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPluginManager создаёт новый менеджер плагинов
|
|
||||||
func NewPluginManager(pluginsDir string, logger *log.Logger, store *storage.Storage, enabled bool) *PluginManager {
|
|
||||||
pm := &PluginManager{
|
|
||||||
logger: logger,
|
|
||||||
storage: store,
|
|
||||||
pluginsDir: pluginsDir,
|
|
||||||
eventBus: make(chan PluginEvent, 1000),
|
|
||||||
enabled: enabled,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !enabled {
|
|
||||||
if logger != nil {
|
|
||||||
logger.Info("Plugin system is disabled")
|
|
||||||
}
|
|
||||||
return pm
|
|
||||||
}
|
|
||||||
|
|
||||||
// Запускаем обработчик событий плагинов
|
|
||||||
go pm.eventLoop()
|
|
||||||
|
|
||||||
// Автоматически загружаем плагины из директории
|
|
||||||
go pm.autoLoadPlugins()
|
|
||||||
|
|
||||||
return pm
|
|
||||||
}
|
|
||||||
|
|
||||||
// autoLoadPlugins автоматически загружает все .lua файлы из директории плагинов
|
|
||||||
func (pm *PluginManager) autoLoadPlugins() {
|
|
||||||
if !pm.enabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ticker := time.NewTicker(10 * time.Second)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
<-ticker.C
|
|
||||||
entries, err := os.ReadDir(pm.pluginsDir)
|
|
||||||
if err != nil {
|
|
||||||
if pm.logger != nil {
|
|
||||||
pm.logger.Error(fmt.Sprintf("Failed to read plugins directory: %v", err))
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range entries {
|
|
||||||
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".lua") {
|
|
||||||
pluginName := strings.TrimSuffix(entry.Name(), ".lua")
|
|
||||||
if _, exists := pm.plugins.Load(pluginName); !exists {
|
|
||||||
pluginPath := filepath.Join(pm.pluginsDir, entry.Name())
|
|
||||||
if err := pm.LoadPlugin(pluginName, pluginPath); err != nil {
|
|
||||||
if pm.logger != nil {
|
|
||||||
pm.logger.Error(fmt.Sprintf("Failed to auto-load plugin %s: %v", pluginName, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadPlugin загружает Lua-плагин из файла
|
|
||||||
func (pm *PluginManager) LoadPlugin(name, filePath string) error {
|
|
||||||
if !pm.enabled {
|
|
||||||
return fmt.Errorf("plugin system is disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Читаем файл плагина
|
|
||||||
script, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read plugin file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаём новое Lua-состояние
|
|
||||||
L := lua.NewState()
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
L.Close()
|
|
||||||
if pm.logger != nil {
|
|
||||||
pm.logger.Error(fmt.Sprintf("Plugin %s panic: %v", name, r))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Открываем стандартные библиотеки Lua
|
|
||||||
lua.OpenBase(L)
|
|
||||||
lua.OpenString(L)
|
|
||||||
lua.OpenTable(L)
|
|
||||||
lua.OpenMath(L)
|
|
||||||
|
|
||||||
// Регистрируем функции СУБД для Lua
|
|
||||||
pm.registerDatabaseFunctions(L)
|
|
||||||
|
|
||||||
// Выполняем скрипт с защитой от паники
|
|
||||||
var execErr error
|
|
||||||
func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
execErr = fmt.Errorf("panic during script execution: %v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
execErr = L.DoString(string(script))
|
|
||||||
}()
|
|
||||||
|
|
||||||
if execErr != nil {
|
|
||||||
L.Close()
|
|
||||||
return fmt.Errorf("failed to execute plugin script: %v", execErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Извлекаем метаданные плагина
|
|
||||||
version := pm.getPluginMetadata(L, "version")
|
|
||||||
author := pm.getPluginMetadata(L, "author")
|
|
||||||
description := pm.getPluginMetadata(L, "description")
|
|
||||||
|
|
||||||
// Создаём объект плагина
|
|
||||||
plugin := &Plugin{
|
|
||||||
Name: name,
|
|
||||||
FilePath: filePath,
|
|
||||||
LState: L,
|
|
||||||
logger: pm.logger,
|
|
||||||
storage: pm.storage,
|
|
||||||
loadedAt: time.Now(),
|
|
||||||
version: version,
|
|
||||||
author: author,
|
|
||||||
description: description,
|
|
||||||
}
|
|
||||||
plugin.Status.Store(int32(StatusLoaded))
|
|
||||||
|
|
||||||
// Сохраняем плагин
|
|
||||||
pm.plugins.Store(name, plugin)
|
|
||||||
|
|
||||||
// Логируем загрузку
|
|
||||||
if pm.logger != nil {
|
|
||||||
pm.logger.Info(fmt.Sprintf("Plugin loaded: %s v%s by %s - %s", name, version, author, description))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вызываем функцию инициализации плагина, если она есть
|
|
||||||
if err := pm.callPluginFunction(plugin, "on_load"); err != nil {
|
|
||||||
if pm.logger != nil {
|
|
||||||
pm.logger.Warn(fmt.Sprintf("Plugin %s on_load error: %v", name, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerDatabaseFunctions регистрирует функции доступа к СУБД в Lua
|
|
||||||
func (pm *PluginManager) registerDatabaseFunctions(L *lua.LState) {
|
|
||||||
// Регистрируем функцию для получения базы данных
|
|
||||||
L.SetGlobal("get_database", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
dbName := L.CheckString(1)
|
|
||||||
db, err := pm.storage.GetDatabase(dbName)
|
|
||||||
if err != nil {
|
|
||||||
L.Push(lua.LNil)
|
|
||||||
L.Push(lua.LString(err.Error()))
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаём пользовательский тип для базы данных
|
|
||||||
ud := L.NewUserData()
|
|
||||||
ud.Value = db
|
|
||||||
L.SetMetatable(ud, L.GetTypeMetatable("database"))
|
|
||||||
L.Push(ud)
|
|
||||||
return 1
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Регистрируем функцию для получения коллекции
|
|
||||||
L.SetGlobal("get_collection", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
dbName := L.CheckString(1)
|
|
||||||
collName := L.CheckString(2)
|
|
||||||
|
|
||||||
db, err := pm.storage.GetDatabase(dbName)
|
|
||||||
if err != nil {
|
|
||||||
L.Push(lua.LNil)
|
|
||||||
L.Push(lua.LString(err.Error()))
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
coll, err := db.GetCollection(collName)
|
|
||||||
if err != nil {
|
|
||||||
L.Push(lua.LNil)
|
|
||||||
L.Push(lua.LString(err.Error()))
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
ud := L.NewUserData()
|
|
||||||
ud.Value = coll
|
|
||||||
L.SetMetatable(ud, L.GetTypeMetatable("collection"))
|
|
||||||
L.Push(ud)
|
|
||||||
return 1
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Регистрируем функцию логирования для плагинов
|
|
||||||
L.SetGlobal("plugin_log", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
level := L.CheckString(1)
|
|
||||||
message := L.CheckString(2)
|
|
||||||
|
|
||||||
if pm.logger != nil {
|
|
||||||
logMsg := fmt.Sprintf("[PLUGIN] %s: %s", level, message)
|
|
||||||
switch level {
|
|
||||||
case "debug":
|
|
||||||
pm.logger.Debug(logMsg)
|
|
||||||
case "info":
|
|
||||||
pm.logger.Info(logMsg)
|
|
||||||
case "warn":
|
|
||||||
pm.logger.Warn(logMsg)
|
|
||||||
case "error":
|
|
||||||
pm.logger.Error(logMsg)
|
|
||||||
default:
|
|
||||||
pm.logger.Info(logMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Регистрируем функцию для отправки событий
|
|
||||||
L.SetGlobal("emit_event", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
eventType := L.CheckString(1)
|
|
||||||
eventData := L.CheckAny(2)
|
|
||||||
|
|
||||||
// Получаем имя плагина из контекста (нужно передавать при вызове)
|
|
||||||
event := PluginEvent{
|
|
||||||
EventType: eventType,
|
|
||||||
Data: pm.luaValueToGo(eventData),
|
|
||||||
Timestamp: time.Now().UnixMilli(),
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case pm.eventBus <- event:
|
|
||||||
default:
|
|
||||||
if pm.logger != nil {
|
|
||||||
pm.logger.Warn("Plugin event bus full, event dropped")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Устанавливаем метатаблицы для методов баз данных и коллекций
|
|
||||||
pm.setupDatabaseMetatable(L)
|
|
||||||
pm.setupCollectionMetatable(L)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupDatabaseMetatable настраивает методы для объекта базы данных в Lua
|
|
||||||
func (pm *PluginManager) setupDatabaseMetatable(L *lua.LState) {
|
|
||||||
mt := L.NewTypeMetatable("database")
|
|
||||||
L.SetField(mt, "__index", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
db := L.CheckUserData(1).Value.(*storage.Database)
|
|
||||||
method := L.CheckString(2)
|
|
||||||
|
|
||||||
switch method {
|
|
||||||
case "create_collection":
|
|
||||||
L.Push(L.NewFunction(func(L *lua.LState) int {
|
|
||||||
name := L.CheckString(1)
|
|
||||||
err := db.CreateCollection(name)
|
|
||||||
if err != nil {
|
|
||||||
L.Push(lua.LString(err.Error()))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
L.Push(lua.LNil)
|
|
||||||
return 1
|
|
||||||
}))
|
|
||||||
case "get_collection":
|
|
||||||
L.Push(L.NewFunction(func(L *lua.LState) int {
|
|
||||||
name := L.CheckString(1)
|
|
||||||
coll, err := db.GetCollection(name)
|
|
||||||
if err != nil {
|
|
||||||
L.Push(lua.LNil)
|
|
||||||
L.Push(lua.LString(err.Error()))
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
ud := L.NewUserData()
|
|
||||||
ud.Value = coll
|
|
||||||
L.SetMetatable(ud, L.GetTypeMetatable("collection"))
|
|
||||||
L.Push(ud)
|
|
||||||
return 1
|
|
||||||
}))
|
|
||||||
case "name":
|
|
||||||
L.Push(lua.LString(db.Name()))
|
|
||||||
default:
|
|
||||||
L.Push(lua.LNil)
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupCollectionMetatable настраивает методы для объекта коллекции в Lua
|
|
||||||
func (pm *PluginManager) setupCollectionMetatable(L *lua.LState) {
|
|
||||||
mt := L.NewTypeMetatable("collection")
|
|
||||||
L.SetField(mt, "__index", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
coll := L.CheckUserData(1).Value.(*storage.Collection)
|
|
||||||
method := L.CheckString(2)
|
|
||||||
|
|
||||||
switch method {
|
|
||||||
case "insert":
|
|
||||||
L.Push(L.NewFunction(func(L *lua.LState) int {
|
|
||||||
doc := L.CheckTable(1)
|
|
||||||
// Конвертируем Lua table в map
|
|
||||||
fields := make(map[string]interface{})
|
|
||||||
doc.ForEach(func(key, value lua.LValue) {
|
|
||||||
if key.Type() == lua.LTString {
|
|
||||||
fields[key.String()] = pm.luaValueToGo(value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Вставляем документ
|
|
||||||
newDoc := storage.NewDocument()
|
|
||||||
for k, v := range fields {
|
|
||||||
newDoc.SetField(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := coll.Insert(newDoc)
|
|
||||||
if err != nil {
|
|
||||||
L.Push(lua.LNil)
|
|
||||||
L.Push(lua.LString(err.Error()))
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
// Возвращаем ID вставленного документа
|
|
||||||
L.Push(lua.LString(newDoc.ID))
|
|
||||||
L.Push(lua.LNil)
|
|
||||||
return 2
|
|
||||||
}))
|
|
||||||
case "find":
|
|
||||||
L.Push(L.NewFunction(func(L *lua.LState) int {
|
|
||||||
id := L.CheckString(1)
|
|
||||||
doc, err := coll.Find(id)
|
|
||||||
if err != nil {
|
|
||||||
L.Push(lua.LNil)
|
|
||||||
L.Push(lua.LString(err.Error()))
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
// Конвертируем документ в Lua table
|
|
||||||
table := L.NewTable()
|
|
||||||
table.RawSetString("_id", lua.LString(doc.ID))
|
|
||||||
for k, v := range doc.GetFields() {
|
|
||||||
table.RawSetString(k, pm.goValueToLua(L, v))
|
|
||||||
}
|
|
||||||
L.Push(table)
|
|
||||||
return 1
|
|
||||||
}))
|
|
||||||
case "update":
|
|
||||||
L.Push(L.NewFunction(func(L *lua.LState) int {
|
|
||||||
id := L.CheckString(1)
|
|
||||||
updates := L.CheckTable(2)
|
|
||||||
|
|
||||||
fields := make(map[string]interface{})
|
|
||||||
updates.ForEach(func(key, value lua.LValue) {
|
|
||||||
if key.Type() == lua.LTString {
|
|
||||||
fields[key.String()] = pm.luaValueToGo(value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
err := coll.Update(id, fields)
|
|
||||||
if err != nil {
|
|
||||||
L.Push(lua.LString(err.Error()))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
L.Push(lua.LNil)
|
|
||||||
return 1
|
|
||||||
}))
|
|
||||||
case "delete":
|
|
||||||
L.Push(L.NewFunction(func(L *lua.LState) int {
|
|
||||||
id := L.CheckString(1)
|
|
||||||
err := coll.Delete(id)
|
|
||||||
if err != nil {
|
|
||||||
L.Push(lua.LString(err.Error()))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
L.Push(lua.LNil)
|
|
||||||
return 1
|
|
||||||
}))
|
|
||||||
case "count":
|
|
||||||
L.Push(L.NewFunction(func(L *lua.LState) int {
|
|
||||||
count := coll.Count()
|
|
||||||
L.Push(lua.LNumber(count))
|
|
||||||
return 1
|
|
||||||
}))
|
|
||||||
default:
|
|
||||||
L.Push(lua.LNil)
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// luaValueToGo конвертирует Lua-значение в Go-значение
|
|
||||||
func (pm *PluginManager) luaValueToGo(val lua.LValue) interface{} {
|
|
||||||
if val == nil || val == lua.LNil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := val.(type) {
|
|
||||||
case lua.LString:
|
|
||||||
return string(v)
|
|
||||||
case lua.LNumber:
|
|
||||||
return float64(v)
|
|
||||||
case lua.LBool:
|
|
||||||
return bool(v)
|
|
||||||
case *lua.LTable:
|
|
||||||
result := make(map[string]interface{})
|
|
||||||
v.ForEach(func(key, value lua.LValue) {
|
|
||||||
keyStr := "unknown"
|
|
||||||
if key.Type() == lua.LTString {
|
|
||||||
keyStr = key.String()
|
|
||||||
} else if key.Type() == lua.LTNumber {
|
|
||||||
keyStr = fmt.Sprintf("%d", int64(key.(lua.LNumber)))
|
|
||||||
}
|
|
||||||
result[keyStr] = pm.luaValueToGo(value)
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
default:
|
|
||||||
return v.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// goValueToLua конвертирует Go-значение в Lua-значение
|
|
||||||
func (pm *PluginManager) goValueToLua(L *lua.LState, val interface{}) lua.LValue {
|
|
||||||
if val == nil {
|
|
||||||
return lua.LNil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := val.(type) {
|
|
||||||
case string:
|
|
||||||
return lua.LString(v)
|
|
||||||
case int:
|
|
||||||
return lua.LNumber(float64(v))
|
|
||||||
case int64:
|
|
||||||
return lua.LNumber(float64(v))
|
|
||||||
case float32:
|
|
||||||
return lua.LNumber(float64(v))
|
|
||||||
case float64:
|
|
||||||
return lua.LNumber(v)
|
|
||||||
case bool:
|
|
||||||
return lua.LBool(v)
|
|
||||||
case map[string]interface{}:
|
|
||||||
table := L.NewTable()
|
|
||||||
for k, val := range v {
|
|
||||||
table.RawSetString(k, pm.goValueToLua(L, val))
|
|
||||||
}
|
|
||||||
return table
|
|
||||||
case []interface{}:
|
|
||||||
table := L.NewTable()
|
|
||||||
for i, val := range v {
|
|
||||||
table.RawSetInt(i+1, pm.goValueToLua(L, val))
|
|
||||||
}
|
|
||||||
return table
|
|
||||||
default:
|
|
||||||
return lua.LString(fmt.Sprintf("%v", v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPluginMetadata извлекает метаданные из загруженного Lua-скрипта
|
|
||||||
func (pm *PluginManager) getPluginMetadata(L *lua.LState, field string) string {
|
|
||||||
// Пытаемся получить глобальную переменную с метаданными
|
|
||||||
val := L.GetGlobal(field)
|
|
||||||
if str, ok := val.(lua.LString); ok {
|
|
||||||
return string(str)
|
|
||||||
}
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
// callPluginFunction вызывает функцию плагина по имени с защитой от паники
|
|
||||||
func (pm *PluginManager) callPluginFunction(plugin *Plugin, funcName string) error {
|
|
||||||
plugin.mu.RLock()
|
|
||||||
defer plugin.mu.RUnlock()
|
|
||||||
|
|
||||||
L := plugin.LState
|
|
||||||
if L == nil {
|
|
||||||
return fmt.Errorf("plugin has no Lua state")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := L.GetGlobal(funcName)
|
|
||||||
if fn == lua.LNil {
|
|
||||||
return nil // Функция не определена
|
|
||||||
}
|
|
||||||
|
|
||||||
// Защита от паники при вызове Lua функции
|
|
||||||
var callErr error
|
|
||||||
func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
callErr = fmt.Errorf("panic during %s: %v", funcName, r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
callErr = L.CallByParam(lua.P{
|
|
||||||
Fn: fn,
|
|
||||||
NRet: 0,
|
|
||||||
Protect: true,
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
|
|
||||||
if callErr != nil {
|
|
||||||
return fmt.Errorf("failed to call %s: %v", funcName, callErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventLoop обрабатывает события от плагинов
|
|
||||||
func (pm *PluginManager) eventLoop() {
|
|
||||||
for event := range pm.eventBus {
|
|
||||||
if pm.logger != nil {
|
|
||||||
pm.logger.Debug(fmt.Sprintf("Plugin event [%s]: %+v", event.EventType, event.Data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Можно реализовать подписку плагинов на события
|
|
||||||
pm.plugins.Range(func(key, value interface{}) bool {
|
|
||||||
plugin := value.(*Plugin)
|
|
||||||
// Асинхронно уведомляем плагины о событии
|
|
||||||
go pm.notifyPlugin(plugin, event)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifyPlugin уведомляет конкретный плагин о событии
|
|
||||||
func (pm *PluginManager) notifyPlugin(plugin *Plugin, event PluginEvent) {
|
|
||||||
plugin.mu.RLock()
|
|
||||||
defer plugin.mu.RUnlock()
|
|
||||||
|
|
||||||
L := plugin.LState
|
|
||||||
if L == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := L.GetGlobal("on_event")
|
|
||||||
if fn == lua.LNil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Устанавливаем имя плагина в событие
|
|
||||||
event.PluginName = plugin.Name
|
|
||||||
|
|
||||||
// Создаём таблицу с данными события
|
|
||||||
eventTable := L.NewTable()
|
|
||||||
eventTable.RawSetString("type", lua.LString(event.EventType))
|
|
||||||
eventTable.RawSetString("plugin_name", lua.LString(event.PluginName))
|
|
||||||
eventTable.RawSetString("timestamp", lua.LNumber(event.Timestamp))
|
|
||||||
eventTable.RawSetString("data", pm.goValueToLua(L, event.Data))
|
|
||||||
|
|
||||||
// Не устанавливаем глобальную переменную, передаём как аргумент
|
|
||||||
var callErr error
|
|
||||||
func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
callErr = fmt.Errorf("panic during on_event: %v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
callErr = L.CallByParam(lua.P{
|
|
||||||
Fn: fn,
|
|
||||||
NRet: 0,
|
|
||||||
Protect: true,
|
|
||||||
}, eventTable)
|
|
||||||
}()
|
|
||||||
|
|
||||||
if callErr != nil && pm.logger != nil {
|
|
||||||
pm.logger.Error(fmt.Sprintf("Plugin %s on_event error: %v", plugin.Name, callErr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecutePlugin выполняет пользовательскую функцию плагина
|
|
||||||
func (pm *PluginManager) ExecutePlugin(pluginName, funcName string, args ...interface{}) (interface{}, error) {
|
|
||||||
if !pm.enabled {
|
|
||||||
return nil, fmt.Errorf("plugin system is disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
val, ok := pm.plugins.Load(pluginName)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("plugin not found: %s", pluginName)
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin := val.(*Plugin)
|
|
||||||
if PluginStatus(plugin.Status.Load()) != StatusRunning {
|
|
||||||
return nil, fmt.Errorf("plugin %s is not running", pluginName)
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin.mu.RLock()
|
|
||||||
defer plugin.mu.RUnlock()
|
|
||||||
|
|
||||||
L := plugin.LState
|
|
||||||
if L == nil {
|
|
||||||
return nil, fmt.Errorf("plugin %s has no Lua state", pluginName)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := L.GetGlobal(funcName)
|
|
||||||
if fn == lua.LNil {
|
|
||||||
return nil, fmt.Errorf("function %s not found in plugin %s", funcName, pluginName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Подготавливаем аргументы для вызова
|
|
||||||
luaArgs := make([]lua.LValue, len(args))
|
|
||||||
for i, arg := range args {
|
|
||||||
luaArgs[i] = pm.goValueToLua(L, arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вызываем функцию с защитой от паники
|
|
||||||
var ret lua.LValue
|
|
||||||
var callErr error
|
|
||||||
func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
callErr = fmt.Errorf("panic during execution: %v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
callErr = L.CallByParam(lua.P{
|
|
||||||
Fn: fn,
|
|
||||||
NRet: 1,
|
|
||||||
Protect: true,
|
|
||||||
}, luaArgs...)
|
|
||||||
if callErr == nil {
|
|
||||||
ret = L.Get(-1)
|
|
||||||
L.Pop(1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if callErr != nil {
|
|
||||||
return nil, fmt.Errorf("plugin execution failed: %v", callErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pm.luaValueToGo(ret), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnloadPlugin выгружает плагин
|
|
||||||
func (pm *PluginManager) UnloadPlugin(name string) error {
|
|
||||||
if !pm.enabled {
|
|
||||||
return fmt.Errorf("plugin system is disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
val, ok := pm.plugins.Load(name)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("plugin not found: %s", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin := val.(*Plugin)
|
|
||||||
|
|
||||||
// Вызываем функцию выгрузки
|
|
||||||
if err := pm.callPluginFunction(plugin, "on_unload"); err != nil {
|
|
||||||
if pm.logger != nil {
|
|
||||||
pm.logger.Warn(fmt.Sprintf("Plugin %s on_unload error: %v", name, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Закрываем Lua-состояние
|
|
||||||
if plugin.LState != nil {
|
|
||||||
plugin.LState.Close()
|
|
||||||
}
|
|
||||||
plugin.Status.Store(int32(StatusStopped))
|
|
||||||
|
|
||||||
pm.plugins.Delete(name)
|
|
||||||
if pm.logger != nil {
|
|
||||||
pm.logger.Info(fmt.Sprintf("Plugin unloaded: %s", name))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartPlugin запускает плагин
|
|
||||||
func (pm *PluginManager) StartPlugin(name string) error {
|
|
||||||
if !pm.enabled {
|
|
||||||
return fmt.Errorf("plugin system is disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
val, ok := pm.plugins.Load(name)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("plugin not found: %s", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin := val.(*Plugin)
|
|
||||||
plugin.Status.Store(int32(StatusRunning))
|
|
||||||
|
|
||||||
if err := pm.callPluginFunction(plugin, "on_start"); err != nil {
|
|
||||||
plugin.Status.Store(int32(StatusError))
|
|
||||||
return fmt.Errorf("failed to start plugin: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pm.logger != nil {
|
|
||||||
pm.logger.Info(fmt.Sprintf("Plugin started: %s", name))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopPlugin останавливает плагин
|
|
||||||
func (pm *PluginManager) StopPlugin(name string) error {
|
|
||||||
if !pm.enabled {
|
|
||||||
return fmt.Errorf("plugin system is disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
val, ok := pm.plugins.Load(name)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("plugin not found: %s", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin := val.(*Plugin)
|
|
||||||
|
|
||||||
if err := pm.callPluginFunction(plugin, "on_stop"); err != nil {
|
|
||||||
if pm.logger != nil {
|
|
||||||
pm.logger.Warn(fmt.Sprintf("Plugin %s on_stop error: %v", name, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin.Status.Store(int32(StatusStopped))
|
|
||||||
if pm.logger != nil {
|
|
||||||
pm.logger.Info(fmt.Sprintf("Plugin stopped: %s", name))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPlugins возвращает список всех загруженных плагинов
|
|
||||||
func (pm *PluginManager) ListPlugins() []*Plugin {
|
|
||||||
plugins := make([]*Plugin, 0)
|
|
||||||
pm.plugins.Range(func(key, value interface{}) bool {
|
|
||||||
plugins = append(plugins, value.(*Plugin))
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
return plugins
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEnabled возвращает статус системы плагинов
|
|
||||||
func (pm *PluginManager) IsEnabled() bool {
|
|
||||||
return pm.enabled
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user