Files
owlry/crates/owlry-core/src/providers/lua_provider.rs

143 lines
4.3 KiB
Rust

//! LuaProvider - Bridge between Lua plugins and the Provider trait
//!
//! This module provides a `LuaProvider` struct that implements the `Provider` trait
//! by delegating to a Lua plugin's registered provider functions.
use std::cell::RefCell;
use std::rc::Rc;
use crate::plugins::{LoadedPlugin, PluginItem, ProviderRegistration};
use super::{LaunchItem, Provider, ProviderType};
/// A provider backed by a Lua plugin
///
/// This struct implements the `Provider` trait by calling into a Lua plugin's
/// `refresh` or `query` functions.
pub struct LuaProvider {
/// Provider registration info
registration: ProviderRegistration,
/// Reference to the loaded plugin (shared with other providers from same plugin)
plugin: Rc<RefCell<LoadedPlugin>>,
/// Cached items from last refresh
items: Vec<LaunchItem>,
}
impl LuaProvider {
/// Create a new LuaProvider
pub fn new(registration: ProviderRegistration, plugin: Rc<RefCell<LoadedPlugin>>) -> Self {
Self {
registration,
plugin,
items: Vec::new(),
}
}
/// Convert a PluginItem to a LaunchItem
fn convert_item(&self, item: PluginItem) -> LaunchItem {
LaunchItem {
id: item.id,
name: item.name,
description: item.description,
icon: item.icon,
provider: ProviderType::Plugin(self.registration.type_id.clone()),
command: item.command.unwrap_or_default(),
terminal: item.terminal,
tags: item.tags,
}
}
}
impl Provider for LuaProvider {
fn name(&self) -> &str {
&self.registration.name
}
fn provider_type(&self) -> ProviderType {
ProviderType::Plugin(self.registration.type_id.clone())
}
fn refresh(&mut self) {
// Only refresh static providers
if !self.registration.is_static {
return;
}
let plugin = self.plugin.borrow();
match plugin.call_provider_refresh(&self.registration.name) {
Ok(items) => {
self.items = items.into_iter().map(|i| self.convert_item(i)).collect();
log::debug!(
"[LuaProvider] '{}' refreshed with {} items",
self.registration.name,
self.items.len()
);
}
Err(e) => {
log::error!(
"[LuaProvider] Failed to refresh '{}': {}",
self.registration.name,
e
);
self.items.clear();
}
}
}
fn items(&self) -> &[LaunchItem] {
&self.items
}
}
// LuaProvider needs to be Send + Sync for the Provider trait.
// Rc<RefCell<>> is !Send and !Sync, but the ProviderManager RwLock ensures
// Rc<RefCell<>> is only accessed during refresh() (write lock = exclusive access).
// Read-only operations (items(), search) only touch self.items (Vec<LaunchItem>).
unsafe impl Send for LuaProvider {}
unsafe impl Sync for LuaProvider {}
/// Create LuaProviders from all registered providers in a plugin
pub fn create_providers_from_plugin(plugin: Rc<RefCell<LoadedPlugin>>) -> Vec<Box<dyn Provider>> {
let registrations = {
let p = plugin.borrow();
match p.get_provider_registrations() {
Ok(regs) => regs,
Err(e) => {
log::error!("[LuaProvider] Failed to get registrations: {}", e);
return Vec::new();
}
}
};
registrations
.into_iter()
.map(|reg| {
let provider = LuaProvider::new(reg, plugin.clone());
Box::new(provider) as Box<dyn Provider>
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
// Note: Full integration tests require a complete plugin setup
// These tests verify the basic structure
#[test]
fn test_provider_type() {
let reg = ProviderRegistration {
name: "test".to_string(),
display_name: "Test".to_string(),
type_id: "test_provider".to_string(),
default_icon: "test-icon".to_string(),
is_static: true,
prefix: None,
};
// We can't easily create a mock LoadedPlugin, so just test the type
assert_eq!(reg.type_id, "test_provider");
}
}