use std::ffi::OsStr;
use std::process::Stdio;
use std::sync::Mutex;
use bifrostlink::{Rpc, Rtt, WeakRpc};
use tokio::process::{Child, Command};
use remowt_link_shared::plugin::{Error, PluginEndpoints, PluginHost};
use remowt_link_shared::port::child_port;
use remowt_link_shared::{Address, BifConfig};
pub fn serve(rpc: &mut Rpc<BifConfig>) {
let host = Host {
me: rpc.me(),
rpc: rpc.clone().downgrade(),
children: Mutex::new(Vec::new()),
};
PluginEndpoints(host).register_endpoints(rpc);
}
struct Host {
me: Address,
rpc: WeakRpc<BifConfig>,
children: Mutex<Vec<Child>>,
}
impl Host {
fn spawn(&self, id: u16, path: impl AsRef<OsStr>) -> Result<(), Error> {
let rpc = self.rpc.clone().upgrade().ok_or(Error::Gone)?;
let mut child = Command::new(path)
.arg(id.to_string())
.arg(serde_json::to_string(&self.me).expect("address serializes"))
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.kill_on_drop(true)
.spawn()
.map_err(|e| Error::Spawn(e.to_string()))?;
let stdin = child.stdin.take().expect("stdin piped");
let stdout = child.stdout.take().expect("stdout piped");
rpc.add_direct(Address::Plugin(id), child_port(stdout, stdin), Rtt(0));
self.children.lock().expect("not poisoned").push(child);
Ok(())
}
}
impl PluginHost for Host {
async fn load_plugin(&self, id: u16, name: String) -> Result<(), Error> {
if name.is_empty() || name == "." || name == ".." || name.contains(['/', '\0']) {
return Err(Error::BadName);
}
let exe = std::env::current_exe().map_err(|e| Error::Spawn(e.to_string()))?;
let dir = exe
.parent()
.ok_or_else(|| Error::Spawn("primary agent has no parent directory".to_owned()))?;
self.spawn(id, dir.join(&name))
}
async fn load_plugin_path(&self, id: u16, path: String) -> Result<(), Error> {
if path.is_empty() || path.contains('\0') {
return Err(Error::BadName);
}
self.spawn(id, path)
}
}