From 8e1506f6decfa0ea14d05cad583ea43c87f51b77 Mon Sep 17 00:00:00 2001 From: Chip Senkbeil Date: Sun, 5 Nov 2023 00:26:10 -0500 Subject: [PATCH] Add plugin trait and plugin registry to hold handlers --- distant-plugin/src/client.rs | 6 +- distant-plugin/src/lib.rs | 107 +++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/distant-plugin/src/client.rs b/distant-plugin/src/client.rs index b2a6f55..eb49ba3 100644 --- a/distant-plugin/src/client.rs +++ b/distant-plugin/src/client.rs @@ -24,7 +24,7 @@ pub trait Client { .await? .next() .await - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Channel has closed")) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Stream has closed")) } /// Sends a request without waiting for any response; this method is able to be used even @@ -55,7 +55,6 @@ impl ClientBridge { #[async_trait] impl Client for ClientBridge { async fn send(&mut self, request: Request) -> io::Result>> { - #[derive(Clone, Debug)] struct __Ctx(u32, mpsc::UnboundedSender); impl Ctx for __Ctx { @@ -77,6 +76,9 @@ impl Client for ClientBridge { let (tx, rx) = mpsc::unbounded_channel(); let ctx = Box::new(__Ctx(rand::random(), tx)); + // Spawn a task that will perform the request using the api. We use a task to allow the + // async engine to not get blocked awaiting immediately for the response to arrive before + // returning the stream of responses. tokio::task::spawn({ let api = Arc::clone(&self.api); async move { diff --git a/distant-plugin/src/lib.rs b/distant-plugin/src/lib.rs index 0771cd9..fa2083a 100644 --- a/distant-plugin/src/lib.rs +++ b/distant-plugin/src/lib.rs @@ -11,3 +11,110 @@ pub mod handlers; pub use distant_core_auth as auth; pub use distant_core_protocol as protocol; + +/// Interface to a plugin that can register new handlers for launching and connecting to +/// distant-compatible servers. +pub trait Plugin { + /// Returns a unique name associated with the plugin. + fn name(&self) -> &'static str; + + /// Invoked immediately after the plugin is loaded. Used for initialization. + #[allow(unused_variables)] + fn on_load(&self, registry: &mut PluginRegistry) {} + + /// Invoked immediately before the plugin is unloaded. Used for deallocation of resources. + fn on_unload(&self) {} +} + +/// Registry that contains various handlers and other information tied to plugins. +#[derive(Default)] +pub struct PluginRegistry { + /// Names of loaded plugins. + loaded: Vec<&'static str>, + + /// Launch handlers registered by plugins, keyed by scheme. + launch_handlers: std::collections::HashMap>, + + /// Connect handlers registered by plugins, keyed by scheme. + connect_handlers: std::collections::HashMap>, +} + +impl PluginRegistry { + pub fn new() -> Self { + Self::default() + } + + /// Returns a list of plugin names associated with this registry. + pub fn plugin_names(&self) -> &[&'static str] { + &self.loaded + } + + /// Inserts the name of the plugin into the registry. If it already exists, nothing happens. + pub fn insert_plugin_name(&mut self, name: &'static str) { + if !self.loaded.contains(&name) { + self.loaded.push(name); + } + } + + /// Returns a reference to the launch handler associated with the `scheme` if one exists. + pub fn launch_handler(&self, scheme: impl AsRef) -> Option<&dyn handlers::LaunchHandler> { + self.launch_handlers + .get(scheme.as_ref()) + .map(|x| x.as_ref()) + } + + /// Inserts a new `handler` for `scheme`. Returns true if successfully inserted, otherwise + /// false if the scheme is already taken. + pub fn insert_launch_handler( + &mut self, + scheme: impl Into, + handler: impl handlers::LaunchHandler + 'static, + ) -> bool { + use std::collections::hash_map::Entry; + + let scheme = scheme.into(); + if let Entry::Vacant(e) = self.launch_handlers.entry(scheme) { + e.insert(Box::new(handler)); + true + } else { + false + } + } + + /// Returns a reference to the connect handler associated with the `scheme` if one exists. + pub fn connect_handler( + &self, + scheme: impl AsRef, + ) -> Option<&dyn handlers::ConnectHandler> { + self.connect_handlers + .get(scheme.as_ref()) + .map(|x| x.as_ref()) + } + + /// Inserts a new `handler` for `scheme`. Returns true if successfully inserted, otherwise + /// false if the scheme is already taken. + pub fn insert_connect_handler( + &mut self, + scheme: impl Into, + handler: impl handlers::ConnectHandler + 'static, + ) -> bool { + use std::collections::hash_map::Entry; + + let scheme = scheme.into(); + if let Entry::Vacant(e) = self.connect_handlers.entry(scheme) { + e.insert(Box::new(handler)); + true + } else { + false + } + } +} + +impl std::fmt::Debug for PluginRegistry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PluginHandlerRegistry") + .field("launch_handlers", &self.launch_handlers.keys()) + .field("connect_handlers", &self.connect_handlers.keys()) + .finish() + } +}