use std::collections::HashMap;
use std::collections::VecDeque;
use std::sync::Arc;
use crate::PluginPaths;
use crate::hook::Hook;
use crate::hook::call::HookHandle;
use crate::hook::call::HookHandles;
use crate::hook::hooks::OnLoad;
use crate::hook::hooks::StartWorker;
use crate::hook::wasm::loader::WASMLoader;
use crate::plugin::PluginProvidedSubcommand;
use crate::plugin::{DEFAULT_PROTOCOL_VERSION, HookPriority, NEWEST_PROTOCOL_VERSION, Plugin};
use anyhow::{Context, bail};
use itertools::Itertools;
use nitro_config::instance::InstanceConfig;
use nitro_config::template::TemplateConfig;
use nitro_shared::output::NitroOutput;
use tokio::sync::Mutex;
pub struct CorePluginManager {
plugins: Vec<Plugin>,
plugin_list: Vec<String>,
nitro_version: Option<&'static str>,
wasm_loader: Arc<Mutex<WASMLoader>>,
context: Option<Arc<dyn PluginContext>>,
}
impl CorePluginManager {
pub fn new(paths: &PluginPaths) -> Self {
Self {
plugins: Vec::new(),
plugin_list: Vec::new(),
nitro_version: None,
wasm_loader: Arc::new(Mutex::new(WASMLoader::new(&paths.data_dir))),
context: None,
}
}
pub fn set_nitro_version(&mut self, version: &'static str) {
self.nitro_version = Some(version);
}
pub fn set_wasm_loader(&mut self, loader: Arc<Mutex<WASMLoader>>) {
self.wasm_loader = loader;
}
pub async fn add_plugin(
&mut self,
mut plugin: Plugin,
paths: &PluginPaths,
o: &mut impl NitroOutput,
) -> anyhow::Result<()> {
if plugin
.get_manifest()
.protocol_version
.unwrap_or(DEFAULT_PROTOCOL_VERSION)
> NEWEST_PROTOCOL_VERSION
{
bail!("Plugin has a newer protocol version than Nitrolaunch");
}
self.plugin_list.push(plugin.get_id().clone());
let result = plugin
.call_hook(
&OnLoad,
&(),
paths,
self.nitro_version,
&self.plugin_list,
self.wasm_loader.clone(),
self.context.as_ref(),
o,
)
.await
.context("Failed to call on_load hook of plugin")?;
if let Some(result) = result {
result.result(o).await?;
}
let worker_handle = plugin
.call_hook(
&StartWorker,
&(),
paths,
self.nitro_version,
&self.plugin_list,
self.wasm_loader.clone(),
self.context.as_ref(),
o,
)
.await
.context("Failed to call start_worker hook of plugin")?;
if let Some(worker_handle) = worker_handle {
plugin
.set_worker(worker_handle)
.await
.context("Failed to set plugin worker")?;
}
self.plugins.push(plugin);
Ok(())
}
pub async fn call_hook<H: Hook>(
&self,
hook: H,
arg: &H::Arg,
paths: &PluginPaths,
o: &mut impl NitroOutput,
) -> anyhow::Result<HookHandles<H>> {
let mut out = VecDeque::new();
for plugin in self.plugins.iter().sorted_by_key(|x| PluginSort {
priority: x.get_hook_priority(&hook),
id: x.get_id().clone(),
}) {
let result = plugin
.call_hook(
&hook,
arg,
paths,
self.nitro_version,
&self.plugin_list,
self.wasm_loader.clone(),
self.context.as_ref(),
o,
)
.await
.with_context(|| format!("Hook failed for plugin {}", plugin.get_id()))?;
out.extend(result);
}
let handles = HookHandles::new(out, o)
.await
.context("Failed to start hook handles")?;
Ok(handles)
}
pub async fn call_hook_on_plugin<H: Hook>(
&self,
hook: H,
plugin_id: &str,
arg: &H::Arg,
paths: &PluginPaths,
o: &mut impl NitroOutput,
) -> anyhow::Result<Option<HookHandle<H>>> {
for plugin in &self.plugins {
if plugin.get_id() == plugin_id {
let result = plugin
.call_hook(
&hook,
arg,
paths,
self.nitro_version,
&self.plugin_list,
self.wasm_loader.clone(),
self.context.as_ref(),
o,
)
.await
.context("Plugin hook failed")?;
return Ok(result);
}
}
bail!("No plugin found that matched the given ID")
}
pub fn iter_plugins(&self) -> impl Iterator<Item = &Plugin> {
self.plugins.iter()
}
pub fn has_plugin(&self, plugin_id: &str) -> bool {
self.plugin_list.iter().any(|x| x == plugin_id)
}
pub fn get_subcommand(&self, subcommand: &str, supercommand: Option<&str>) -> Option<String> {
self.iter_plugins().find(|x| {
x.get_manifest()
.subcommands
.iter()
.any(|x| {
if x.0 != subcommand {
return false;
}
if let Some(supercommand2) = supercommand {
matches!(x.1, PluginProvidedSubcommand::Specific { supercommand, .. } if supercommand == supercommand2)
} else {
matches!(x.1, PluginProvidedSubcommand::Global(..))
}
})
})
.map(|x| x.get_id().clone())
}
pub fn set_context(&mut self, context: Arc<dyn PluginContext>) {
self.context = Some(context);
}
}
#[derive(PartialEq, PartialOrd, Eq, Ord)]
struct PluginSort {
priority: HookPriority,
id: String,
}
#[async_trait::async_trait]
pub trait PluginContext: Send + Sync + 'static {
fn get_instances(&self) -> Arc<HashMap<String, InstanceConfig>>;
fn get_templates(&self) -> Arc<HashMap<String, TemplateConfig>>;
async fn create_instance(&self, id: String, config: InstanceConfig) -> anyhow::Result<()>;
async fn create_template(&self, id: String, config: TemplateConfig) -> anyhow::Result<()>;
}