flusql/src/plugins.rs
2026-01-08 18:30:33 +03:00

382 lines
18 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Модуль 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();
}
}
}