733 lines
23 KiB
Go
733 lines
23 KiB
Go
// Файл: 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
|
||
}
|