Files
owlry/crates/owlry-core/src/plugins/loader.rs

213 lines
6.3 KiB
Rust

//! Lua plugin loading and initialization
use std::path::PathBuf;
use mlua::Lua;
use super::api;
use super::error::{PluginError, PluginResult};
use super::manifest::PluginManifest;
use super::runtime::{SandboxConfig, create_lua_runtime, load_file};
/// A loaded plugin instance
#[derive(Debug)]
pub struct LoadedPlugin {
/// Plugin manifest
pub manifest: PluginManifest,
/// Path to plugin directory
pub path: PathBuf,
/// Whether plugin is enabled
pub enabled: bool,
/// Lua runtime (None if not yet initialized)
lua: Option<Lua>,
}
impl LoadedPlugin {
/// Create a new loaded plugin (not yet initialized)
pub fn new(manifest: PluginManifest, path: PathBuf) -> Self {
Self {
manifest,
path,
enabled: true,
lua: None,
}
}
/// Get the plugin ID
pub fn id(&self) -> &str {
&self.manifest.plugin.id
}
/// Get the plugin name
#[allow(dead_code)]
pub fn name(&self) -> &str {
&self.manifest.plugin.name
}
/// Initialize the Lua runtime and load the entry point
pub fn initialize(&mut self) -> PluginResult<()> {
if self.lua.is_some() {
return Ok(()); // Already initialized
}
let sandbox = SandboxConfig::from_permissions(&self.manifest.permissions);
let lua = create_lua_runtime(&sandbox).map_err(|e| PluginError::LuaError {
plugin: self.id().to_string(),
message: e.to_string(),
})?;
// Register owlry APIs before loading entry point
api::register_apis(&lua, &self.path, self.id()).map_err(|e| PluginError::LuaError {
plugin: self.id().to_string(),
message: format!("Failed to register APIs: {}", e),
})?;
// Load the entry point file
let entry_path = self.path.join(&self.manifest.plugin.entry);
if !entry_path.exists() {
return Err(PluginError::InvalidManifest {
plugin: self.id().to_string(),
message: format!("Entry point '{}' not found", self.manifest.plugin.entry),
});
}
load_file(&lua, &entry_path).map_err(|e| PluginError::LuaError {
plugin: self.id().to_string(),
message: e.to_string(),
})?;
self.lua = Some(lua);
Ok(())
}
/// Get provider registrations from this plugin
pub fn get_provider_registrations(&self) -> PluginResult<Vec<super::ProviderRegistration>> {
let lua = self.lua.as_ref().ok_or_else(|| PluginError::LuaError {
plugin: self.id().to_string(),
message: "Plugin not initialized".to_string(),
})?;
api::get_provider_registrations(lua).map_err(|e| PluginError::LuaError {
plugin: self.id().to_string(),
message: e.to_string(),
})
}
/// Call a provider's refresh function
pub fn call_provider_refresh(
&self,
provider_name: &str,
) -> PluginResult<Vec<super::PluginItem>> {
let lua = self.lua.as_ref().ok_or_else(|| PluginError::LuaError {
plugin: self.id().to_string(),
message: "Plugin not initialized".to_string(),
})?;
api::provider::call_refresh(lua, provider_name).map_err(|e| PluginError::LuaError {
plugin: self.id().to_string(),
message: e.to_string(),
})
}
/// Call a provider's query function
#[allow(dead_code)] // Will be used for dynamic query providers
pub fn call_provider_query(
&self,
provider_name: &str,
query: &str,
) -> PluginResult<Vec<super::PluginItem>> {
let lua = self.lua.as_ref().ok_or_else(|| PluginError::LuaError {
plugin: self.id().to_string(),
message: "Plugin not initialized".to_string(),
})?;
api::provider::call_query(lua, provider_name, query).map_err(|e| PluginError::LuaError {
plugin: self.id().to_string(),
message: e.to_string(),
})
}
/// Get a reference to the Lua runtime (if initialized)
#[allow(dead_code)]
pub fn lua(&self) -> Option<&Lua> {
self.lua.as_ref()
}
/// Get a mutable reference to the Lua runtime (if initialized)
#[allow(dead_code)]
pub fn lua_mut(&mut self) -> Option<&mut Lua> {
self.lua.as_mut()
}
}
// Note: discover_plugins and check_compatibility are in manifest.rs
// to avoid Lua dependency for plugin discovery.
#[cfg(test)]
mod tests {
use super::super::manifest::{check_compatibility, discover_plugins};
use super::*;
use std::fs;
use std::path::Path;
use tempfile::TempDir;
fn create_test_plugin(dir: &Path, id: &str, name: &str) {
let plugin_dir = dir.join(id);
fs::create_dir_all(&plugin_dir).unwrap();
let manifest = format!(
r#"
[plugin]
id = "{}"
name = "{}"
version = "1.0.0"
"#,
id, name
);
fs::write(plugin_dir.join("plugin.toml"), manifest).unwrap();
fs::write(plugin_dir.join("init.lua"), "-- empty plugin").unwrap();
}
#[test]
fn test_discover_plugins() {
let temp = TempDir::new().unwrap();
let plugins_dir = temp.path();
create_test_plugin(plugins_dir, "test-plugin", "Test Plugin");
create_test_plugin(plugins_dir, "another-plugin", "Another Plugin");
let plugins = discover_plugins(plugins_dir).unwrap();
assert_eq!(plugins.len(), 2);
assert!(plugins.contains_key("test-plugin"));
assert!(plugins.contains_key("another-plugin"));
}
#[test]
fn test_discover_plugins_empty_dir() {
let temp = TempDir::new().unwrap();
let plugins = discover_plugins(temp.path()).unwrap();
assert!(plugins.is_empty());
}
#[test]
fn test_discover_plugins_nonexistent_dir() {
let plugins = discover_plugins(Path::new("/nonexistent/path")).unwrap();
assert!(plugins.is_empty());
}
#[test]
fn test_check_compatibility() {
let toml_str = r#"
[plugin]
id = "test"
name = "Test"
version = "1.0.0"
owlry_version = ">=0.3.0"
"#;
let manifest: PluginManifest = toml::from_str(toml_str).unwrap();
assert!(check_compatibility(&manifest, "0.3.5").is_ok());
assert!(check_compatibility(&manifest, "0.2.0").is_err());
}
}