first commit
This commit is contained in:
732
internal/plugin/plugin.go
Normal file
732
internal/plugin/plugin.go
Normal file
@@ -0,0 +1,732 @@
|
||||
// Файл: 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 {
|
||||
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)
|
||||
|
||||
// Выполняем скрипт
|
||||
if err := L.DoString(string(script)); err != nil {
|
||||
L.Close()
|
||||
return fmt.Errorf("failed to execute plugin script: %v", err)
|
||||
}
|
||||
|
||||
// Извлекаем метаданные плагина
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
// Вставляем документ
|
||||
err := coll.InsertFromMap(fields)
|
||||
if err != nil {
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 1
|
||||
}
|
||||
L.Push(lua.LNil)
|
||||
return 1
|
||||
}))
|
||||
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()
|
||||
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
|
||||
fn := L.GetGlobal(funcName)
|
||||
if fn == lua.LNil {
|
||||
return nil // Функция не определена
|
||||
}
|
||||
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: fn,
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to call %s: %v", funcName, err)
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
L.SetGlobal("event", eventTable)
|
||||
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: fn,
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
}); err != nil {
|
||||
if pm.logger != nil {
|
||||
pm.logger.Error(fmt.Sprintf("Plugin %s on_event error: %v", plugin.Name, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Вызываем функцию
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: fn,
|
||||
NRet: 1,
|
||||
Protect: true,
|
||||
}, luaArgs...); err != nil {
|
||||
return nil, fmt.Errorf("plugin execution failed: %v", err)
|
||||
}
|
||||
|
||||
ret := L.Get(-1)
|
||||
L.Pop(1)
|
||||
|
||||
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