Better plugin development experience (#118)

This commit is contained in:
Gregory Schier
2024-09-29 10:41:07 -07:00
committed by GitHub
parent 1c5e62a468
commit 917adcfb2e
33 changed files with 172753 additions and 66 deletions

View File

@@ -74,6 +74,7 @@ pub enum InternalEventPayload {
#[ts(export, export_to="events.ts")]
pub struct BootRequest {
pub dir: String,
pub watch: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]

View File

@@ -12,6 +12,7 @@ use crate::server::plugin_runtime::plugin_runtime_server::PluginRuntimeServer;
use crate::server::PluginRuntimeServerImpl;
use log::{info, warn};
use std::collections::HashMap;
use std::env;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
@@ -32,6 +33,12 @@ pub struct PluginManager {
server: Arc<PluginRuntimeServerImpl>,
}
#[derive(Clone)]
struct PluginCandidate {
dir: String,
watch: bool,
}
impl PluginManager {
pub fn new<R: Runtime>(app_handle: AppHandle<R>) -> PluginManager {
let (events_tx, mut events_rx) = mpsc::channel(128);
@@ -123,24 +130,45 @@ impl PluginManager {
plugin_manager
}
pub async fn list_plugin_dirs<R: Runtime>(&self, app_handle: &AppHandle<R>) -> Vec<String> {
let plugins_dir = app_handle
async fn list_plugin_dirs<R: Runtime>(
&self,
app_handle: &AppHandle<R>,
) -> Vec<PluginCandidate> {
let bundled_plugins_dir = &app_handle
.path()
.resolve("vendored/plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory resource");
let bundled_plugin_dirs = read_plugins_dir(&plugins_dir)
let plugins_dir = match env::var("YAAK_PLUGINS_DIR") {
Ok(d) => &PathBuf::from(d),
Err(_) => bundled_plugins_dir,
};
info!("Loading bundled plugins from {plugins_dir:?}");
let bundled_plugin_dirs: Vec<PluginCandidate> = read_plugins_dir(&plugins_dir)
.await
.expect(format!("Failed to read plugins dir: {:?}", plugins_dir).as_str());
.expect(format!("Failed to read plugins dir: {:?}", plugins_dir).as_str())
.iter()
.map(|d| {
let is_vendored = plugins_dir.starts_with(bundled_plugins_dir);
PluginCandidate {
dir: d.into(),
watch: !is_vendored,
}
})
.collect();
let plugins = list_plugins(app_handle).await.unwrap_or_default();
let installed_plugin_dirs = plugins
let installed_plugin_dirs: Vec<PluginCandidate> = plugins
.iter()
.map(|p| p.directory.to_owned())
.collect::<Vec<String>>();
.map(|p| PluginCandidate {
dir: p.directory.to_owned(),
watch: true,
})
.collect();
let plugin_dirs = [bundled_plugin_dirs, installed_plugin_dirs].concat();
plugin_dirs
[bundled_plugin_dirs, installed_plugin_dirs].concat()
}
pub async fn uninstall(&self, dir: &str) -> Result<()> {
@@ -152,12 +180,11 @@ impl PluginManager {
}
async fn remove_plugin(&self, plugin: &PluginHandle) -> Result<()> {
let mut plugins = self.plugins.lock().await;
// Terminate the plugin
plugin.terminate().await?;
// Remove the plugin from the list
let mut plugins = self.plugins.lock().await;
let pos = plugins.iter().position(|p| p.ref_id == plugin.ref_id);
if let Some(pos) = pos {
plugins.remove(pos);
@@ -166,26 +193,22 @@ impl PluginManager {
Ok(())
}
pub async fn add_plugin_by_dir(&self, dir: &str) -> Result<()> {
pub async fn add_plugin_by_dir(&self, dir: &str, watch: bool) -> Result<()> {
info!("Adding plugin by dir {dir}");
let maybe_tx = self.server.app_to_plugin_events_tx.lock().await;
let tx = match &*maybe_tx {
None => return Err(ClientNotInitializedErr),
Some(tx) => tx,
};
let ph = PluginHandle::new(dir, tx.clone());
self.plugins.lock().await.push(ph.clone());
let plugin = self
.get_plugin_by_dir(dir)
.await
.ok_or(PluginNotFoundErr(dir.to_string()))?;
let plugin_handle = PluginHandle::new(dir, tx.clone());
// Boot the plugin
let event = self
.send_to_plugin_and_wait(
&plugin,
&plugin_handle,
&InternalEventPayload::BootRequest(BootRequest {
dir: dir.to_string(),
watch,
}),
)
.await?;
@@ -195,7 +218,10 @@ impl PluginManager {
_ => return Err(UnknownEventErr),
};
plugin.set_boot_response(&resp).await;
plugin_handle.set_boot_response(&resp).await;
// Add the new plugin after it boots
self.plugins.lock().await.push(plugin_handle.clone());
Ok(())
}
@@ -204,18 +230,30 @@ impl PluginManager {
&self,
app_handle: &AppHandle<R>,
) -> Result<()> {
for dir in self.list_plugin_dirs(app_handle).await {
let dirs = self.list_plugin_dirs(app_handle).await;
for d in dirs.clone() {
// First remove the plugin if it exists
if let Some(plugin) = self.get_plugin_by_dir(dir.as_str()).await {
if let Some(plugin) = self.get_plugin_by_dir(d.dir.as_str()).await {
if let Err(e) = self.remove_plugin(&plugin).await {
warn!("Failed to remove plugin {dir} {e:?}");
warn!("Failed to remove plugin {} {e:?}", d.dir);
}
}
if let Err(e) = self.add_plugin_by_dir(dir.as_str()).await {
warn!("Failed to add plugin {dir} {e:?}");
if let Err(e) = self.add_plugin_by_dir(d.dir.as_str(), d.watch).await {
warn!("Failed to add plugin {} {e:?}", d.dir);
}
}
info!(
"Initialized all plugins:\n - {}",
self.plugins
.lock()
.await
.iter()
.map(|p| p.dir.to_string())
.collect::<Vec<String>>()
.join("\n - "),
);
Ok(())
}
@@ -271,7 +309,7 @@ impl PluginManager {
pub async fn get_plugin_by_name(&self, name: &str) -> Option<PluginHandle> {
for plugin in self.plugins.lock().await.iter().cloned() {
let info = plugin.info().await?;
let info = plugin.info().await;
if info.name == name {
return Some(plugin);
}
@@ -446,8 +484,7 @@ impl PluginManager {
.get_plugin_by_ref_id(ref_id.as_str())
.await
.ok_or(PluginNotFoundErr(ref_id))?;
let info = plugin.info().await.unwrap();
Ok((resp, info.name))
Ok((resp, plugin.info().await.name))
}
}
}

View File

@@ -11,7 +11,7 @@ pub struct PluginHandle {
pub ref_id: String,
pub dir: String,
pub(crate) to_plugin_tx: Arc<Mutex<mpsc::Sender<tonic::Result<EventStreamEvent>>>>,
pub(crate) boot_resp: Arc<Mutex<Option<BootResponse>>>,
pub(crate) boot_resp: Arc<Mutex<BootResponse>>,
}
impl PluginHandle {
@@ -22,11 +22,11 @@ impl PluginHandle {
ref_id: ref_id.clone(),
dir: dir.to_string(),
to_plugin_tx: Arc::new(Mutex::new(tx)),
boot_resp: Arc::new(Mutex::new(None)),
boot_resp: Arc::new(Mutex::new(BootResponse::default())),
}
}
pub async fn info(&self) -> Option<BootResponse> {
pub async fn info(&self) -> BootResponse {
let resp = &*self.boot_resp.lock().await;
resp.clone()
}
@@ -72,6 +72,6 @@ impl PluginHandle {
pub async fn set_boot_response(&self, resp: &BootResponse) {
let mut boot_resp = self.boot_resp.lock().await;
*boot_resp = Some(resp.clone());
*boot_resp = resp.clone();
}
}