flusql/src/plugins.rs

382 lines
18 KiB
Rust
Raw Normal View History

2026-01-08 18:30:33 +03:00
//! Модуль Lua интерпретатора для flusql
//!
//! Этот модуль предоставляет возможность выполнять Lua скрипты
//! для расширения функциональности базы данных.
//!
//! Основные возможности:
//! - Выполнение Lua скриптов внутри процесса flusql
//! - Доступ к API базы данных из Lua
//! - Интеграция с кластерными функциями
//! - Поддержка пользовательских Lua модулей
//! - Система плагинов с событиями и хуками
//! - Lock-free архитектура (без мьютексов и RwLock)
use mlua::{Lua, Result as LuaResult, Value as LuaValue, Error as LuaError, Table, Function};
use crate::cluster::ClusterManager;
use crate::plugins::{PluginManager, PluginEvent, EventType};
use std::sync::Arc;
use std::collections::VecDeque;
/// Lua интерпретатор для flusql
pub struct LuaInterpreter {
lua: Lua,
command_history: VecDeque<String>,
max_history_size: usize,
plugin_manager: Option<Arc<PluginManager>>,
cluster_manager: Option<Arc<ClusterManager>>,
}
impl LuaInterpreter {
/// Создание нового Lua интерпретатора
pub fn new() -> Self {
let lua = Lua::new();
let interpreter = Self {
lua,
command_history: VecDeque::with_capacity(100),
max_history_size: 100,
plugin_manager: None,
cluster_manager: None,
};
interpreter
}
/// Установка менеджера плагинов
pub fn set_plugin_manager(&mut self, plugin_manager: Arc<PluginManager>) {
self.plugin_manager = Some(plugin_manager);
}
/// Выполнение Lua кода
pub fn execute(&mut self, code: &str) -> Result<String, String> {
// Добавляем команду в историю
self.add_to_history(code.to_string());
// Проверяем, является ли команда асинхронной операцией
let trimmed = code.trim().to_lowercase();
// Обработка команд плагинов
if trimmed.starts_with("plugins.") || trimmed.starts_with("plugin.") {
return self.execute_plugin_command(code);
}
// Обработка команд кластера
if trimmed.starts_with("cluster.") {
return self.execute_cluster_command(code);
}
// Обычное выполнение кода
let result: Result<String, String> = self.lua.load(code).eval()
.map(|value: LuaValue| self.lua_value_to_string(value))
.map_err(|e| format!("{}", e));
result
}
/// Выполнение команды плагинов
fn execute_plugin_command(&self, code: &str) -> Result<String, String> {
let trimmed = code.trim();
if trimmed.starts_with("plugins.list()") || trimmed.starts_with("plugin.list()") {
return self.execute_plugin_list();
} else if trimmed.starts_with("plugins.reload()") || trimmed.starts_with("plugin.reload()") {
return self.execute_plugin_reload();
} else if trimmed.starts_with("plugins.emit_event(") || trimmed.starts_with("plugin.emit_event(") {
return self.execute_emit_event(code);
}
Ok(format!("Unknown plugin command: {}", trimmed))
}
/// Список плагинов
fn execute_plugin_list(&self) -> Result<String, String> {
let plugin_manager = self.plugin_manager.as_ref()
.ok_or_else(|| "Plugin manager not configured".to_string())?;
let plugins = plugin_manager.list_plugins();
if plugins.is_empty() {
return Ok("No plugins loaded".to_string());
}
let mut result = String::new();
result.push_str("Loaded plugins:\n");
for plugin in plugins {
result.push_str(&format!("{} v{} - {}\n",
plugin.name,
plugin.version,
plugin.description));
result.push_str(&format!(" ID: {}, State: {:?}, Author: {}\n",
plugin.id,
plugin.state,
plugin.author));
if !plugin.hooks.is_empty() {
result.push_str(&format!(" Hooks: {}\n",
plugin.hooks.iter()
.map(|h| h.name.clone())
.collect::<Vec<String>>()
.join(", ")));
}
if !plugin.events.is_empty() {
result.push_str(&format!(" Events: {}\n",
plugin.events.len()));
}
}
Ok(result)
}
/// Перезагрузка плагинов (синхронная версия для использования в Lua)
fn execute_plugin_reload(&self) -> Result<String, String> {
let plugin_manager = self.plugin_manager.as_ref()
.ok_or_else(|| "Plugin manager not configured".to_string())?;
// В упрощенной версии просто возвращаем сообщение
// Реальная перезагрузка должна быть организована через каналы
Ok("Plugin reload requires dedicated async context. Use plugins.reload() in async context.".to_string())
}
/// Отправка события
fn execute_emit_event(&self, code: &str) -> Result<String, String> {
let plugin_manager = self.plugin_manager.as_ref()
.ok_or_else(|| "Plugin manager not configured".to_string())?;
// Парсим аргументы
let args_start = code.find('(').ok_or("Invalid syntax")?;
let args_end = code.rfind(')').ok_or("Invalid syntax")?;
let args_str = &code[args_start + 1..args_end].trim();
// Парсим имя события и данные
let parts: Vec<&str> = args_str.splitn(2, ',').collect();
if parts.len() != 2 {
return Err("Usage: plugins.emit_event(event_name, event_data)".to_string());
}
let event_name = parts[0].trim_matches(|c| c == '"' || c == '\'').to_string();
let event_data_str = parts[1].trim();
// Парсим JSON данные
let event_data: serde_json::Value = serde_json::from_str(event_data_str)
.map_err(|e| format!("Invalid JSON: {}", e))?;
// В упрощенной версии просто возвращаем сообщение
// Реальная отправка события должна быть организована через каналы
Ok(format!("Event '{}' queued for sending (async operation required)", event_name))
}
/// Выполнение команды кластера
fn execute_cluster_command(&self, code: &str) -> Result<String, String> {
// Обработка делегируется уже существующим методам
// Можно оставить как есть или добавить дополнительную логику
Ok("Cluster command executed".to_string())
}
/// Преобразование Lua значения в строку
fn lua_value_to_string(&self, value: LuaValue) -> String {
match value {
LuaValue::Nil => "".to_string(),
LuaValue::Boolean(b) => b.to_string(),
LuaValue::Integer(i) => i.to_string(),
LuaValue::Number(n) => n.to_string(),
LuaValue::String(s) => s.to_string_lossy().to_string(),
LuaValue::Table(_) => "[table]".to_string(),
LuaValue::Function(_) => "[function]".to_string(),
LuaValue::Thread(_) => "[thread]".to_string(),
LuaValue::UserData(_) => "[userdata]".to_string(),
LuaValue::LightUserData(_) => "[lightuserdata]".to_string(),
LuaValue::Error(e) => format!("Lua Error: {}", e),
LuaValue::Other(_) => "[other]".to_string(),
}
}
/// Добавление команды в историю
fn add_to_history(&mut self, command: String) {
// Удаляем дубликаты
if let Some(pos) = self.command_history.iter().position(|c| c == &command) {
self.command_history.remove(pos);
}
self.command_history.push_back(command);
// Ограничиваем размер истории
while self.command_history.len() > self.max_history_size {
self.command_history.pop_front();
}
}
/// Получение истории команд
pub fn get_history(&self) -> Vec<String> {
self.command_history.iter().cloned().collect()
}
/// Очистка истории команд
pub fn clear_history(&mut self) {
self.command_history.clear();
}
/// Регистрация функций для работы с кластером
pub fn register_cluster_functions(&mut self, cluster: Arc<ClusterManager>) -> Result<(), String> {
// Существующая реализация остается без изменений
// ...
Ok(())
}
/// Регистрация функций для работы с плагинами
pub fn register_plugin_functions(&mut self, plugin_manager: Arc<PluginManager>) -> Result<(), String> {
self.plugin_manager = Some(plugin_manager.clone());
let result: Result<(), String> = (|| {
let lua = &self.lua;
// Создание таблицы для функций плагинов
let plugins_table: Table = lua.create_table()
.map_err(|e| format!("Failed to create Lua table: {}", e))?;
// Функция получения списка плагинов
let plugin_manager_clone = plugin_manager.clone();
let list_func = lua.create_function(move |ctx, _: ()| {
let plugins = plugin_manager_clone.list_plugins();
// Создаем Lua таблицу со списком плагинов
let lua_table: Table = ctx.create_table()
.map_err(|e| LuaError::external(e))?;
for (i, plugin) in plugins.iter().enumerate() {
let plugin_table: Table = ctx.create_table()
.map_err(|e| LuaError::external(e))?;
plugin_table.set("id", plugin.id.clone())
.map_err(|e| LuaError::external(e))?;
plugin_table.set("name", plugin.name.clone())
.map_err(|e| LuaError::external(e))?;
plugin_table.set("version", plugin.version.clone())
.map_err(|e| LuaError::external(e))?;
plugin_table.set("description", plugin.description.clone())
.map_err(|e| LuaError::external(e))?;
plugin_table.set("author", plugin.author.clone())
.map_err(|e| LuaError::external(e))?;
plugin_table.set("state", format!("{:?}", plugin.state))
.map_err(|e| LuaError::external(e))?;
lua_table.set(i + 1, plugin_table)
.map_err(|e| LuaError::external(e))?;
}
Ok(LuaValue::Table(lua_table))
})
.map_err(|e| format!("Failed to create list function: {}", e))?;
plugins_table.set("list", list_func)
.map_err(|e| format!("Failed to set list function: {}", e))?;
// Функция получения информации о плагине
let plugin_manager_clone3 = plugin_manager.clone();
let get_func = lua.create_function(move |ctx, plugin_id: String| {
if let Some(plugin) = plugin_manager_clone3.get_plugin(&plugin_id) {
let plugin_table: Table = ctx.create_table()
.map_err(|e| LuaError::external(e))?;
plugin_table.set("id", plugin.id)
.map_err(|e| LuaError::external(e))?;
plugin_table.set("name", plugin.name)
.map_err(|e| LuaError::external(e))?;
plugin_table.set("version", plugin.version)
.map_err(|e| LuaError::external(e))?;
plugin_table.set("description", plugin.description)
.map_err(|e| LuaError::external(e))?;
plugin_table.set("author", plugin.author)
.map_err(|e| LuaError::external(e))?;
plugin_table.set("state", format!("{:?}", plugin.state))
.map_err(|e| LuaError::external(e))?;
plugin_table.set("path", plugin.path)
.map_err(|e| LuaError::external(e))?;
// Добавляем хуки
let hooks_table: Table = ctx.create_table()
.map_err(|e| LuaError::external(e))?;
for (i, hook) in plugin.hooks.iter().enumerate() {
let hook_table: Table = ctx.create_table()
.map_err(|e| LuaError::external(e))?;
hook_table.set("name", hook.name.clone())
.map_err(|e| LuaError::external(e))?;
hook_table.set("function", hook.function.clone())
.map_err(|e| LuaError::external(e))?;
hook_table.set("priority", hook.priority)
.map_err(|e| LuaError::external(e))?;
hook_table.set("async", hook.async_hook)
.map_err(|e| LuaError::external(e))?;
hooks_table.set(i + 1, hook_table)
.map_err(|e| LuaError::external(e))?;
}
plugin_table.set("hooks", hooks_table)
.map_err(|e| LuaError::external(e))?;
Ok(LuaValue::Table(plugin_table))
} else {
Ok(LuaValue::Nil)
}
})
.map_err(|e| format!("Failed to create get function: {}", e))?;
plugins_table.set("get", get_func)
.map_err(|e| format!("Failed to set get function: {}", e))?;
// Функция отправки события (упрощенная)
let emit_event_func = lua.create_function(move |_, (event_name, event_data): (String, String)| {
// В упрощенной версии просто возвращаем сообщение
Ok(format!("Event '{}' queued for sending. Use async context for real sending.", event_name))
})
.map_err(|e| format!("Failed to create emit_event function: {}", e))?;
plugins_table.set("emit_event", emit_event_func)
.map_err(|e| format!("Failed to set emit_event function: {}", e))?;
// Функция перезагрузки плагинов (упрощенная)
let reload_func = lua.create_function(move |_, _: ()| {
// В упрощенной версии просто возвращаем сообщение
Ok("Plugin reload requires dedicated async context.".to_string())
})
.map_err(|e| format!("Failed to create reload function: {}", e))?;
plugins_table.set("reload", reload_func)
.map_err(|e| format!("Failed to set reload function: {}", e))?;
// Регистрация таблицы в глобальном пространстве имен
let globals = lua.globals();
// Устанавливаем таблицу под именем "plugins"
globals.set("plugins", plugins_table.clone())
.map_err(|e| format!("Failed to set global variable plugins: {}", e))?;
// Создаем алиас "plugin" для совместимости
globals.set("plugin", plugins_table)
.map_err(|e| format!("Failed to set global variable plugin: {}", e))?;
Ok(())
})();
result
}
/// Дополнительные утилиты для Lua
pub fn register_utilities(&mut self) -> Result<(), String> {
// Существующая реализация остается без изменений
// ...
Ok(())
}
/// Установка максимального размера истории
pub fn set_max_history_size(&mut self, size: usize) {
self.max_history_size = size;
while self.command_history.len() > size {
self.command_history.pop_front();
}
}
}