use std::collections::HashMap;
use std::fmt::Debug;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex;
use anyhow::Context;
use mcvm_core::Paths;
use mcvm_shared::output::MCVMOutput;
use serde::{Deserialize, Deserializer};
use crate::hook_call::HookCallArg;
use crate::hooks::Hook;
use crate::HookHandle;
pub const NEWEST_PROTOCOL_VERSION: u16 = 2;
pub const DEFAULT_PROTOCOL_VERSION: u16 = 1;
#[derive(Debug)]
pub struct Plugin {
id: String,
manifest: PluginManifest,
custom_config: Option<String>,
working_dir: Option<PathBuf>,
state: Arc<Mutex<serde_json::Value>>,
}
impl Plugin {
pub fn new(id: String, manifest: PluginManifest) -> Self {
Self {
id,
manifest,
custom_config: None,
working_dir: None,
state: Arc::new(Mutex::new(serde_json::Value::Null)),
}
}
pub fn get_id(&self) -> &String {
&self.id
}
pub fn get_manifest(&self) -> &PluginManifest {
&self.manifest
}
pub fn call_hook<H: Hook>(
&self,
hook: &H,
arg: &H::Arg,
paths: &Paths,
mcvm_version: Option<&str>,
plugin_list: &[String],
o: &mut impl MCVMOutput,
) -> anyhow::Result<Option<HookHandle<H>>> {
let Some(handler) = self.manifest.hooks.get(hook.get_name()) else {
return Ok(None);
};
match handler {
HookHandler::Execute { executable, args } => {
let arg = HookCallArg {
cmd: &executable,
arg,
additional_args: args,
working_dir: self.working_dir.as_deref(),
use_base64: !self.manifest.raw_transfer,
custom_config: self.custom_config.clone(),
state: self.state.clone(),
paths,
mcvm_version,
plugin_id: &self.id,
plugin_list,
protocol_version: self
.manifest
.protocol_version
.unwrap_or(DEFAULT_PROTOCOL_VERSION),
};
hook.call(arg, o).map(Some)
}
HookHandler::Constant { constant } => Ok(Some(HookHandle::constant(
serde_json::from_value(constant.clone())?,
self.id.clone(),
))),
HookHandler::Native { function } => {
let arg = serde_json::to_string(arg)
.context("Failed to serialize native hook argument")?;
let result = function(arg).context("Native hook handler failed")?;
let result = serde_json::from_str(&result)
.context("Failed to deserialize native hook result")?;
Ok(Some(HookHandle::constant(result, self.id.clone())))
}
}
}
pub fn set_custom_config(&mut self, config: serde_json::Value) -> anyhow::Result<()> {
let serialized =
serde_json::to_string(&config).context("Failed to serialize custom plugin config")?;
self.custom_config = Some(serialized);
Ok(())
}
pub fn set_working_dir(&mut self, dir: PathBuf) {
self.working_dir = Some(dir);
}
}
#[derive(Deserialize, Debug, Default)]
#[serde(default)]
pub struct PluginManifest {
pub name: Option<String>,
pub description: Option<String>,
pub mcvm_version: Option<String>,
pub hooks: HashMap<String, HookHandler>,
pub subcommands: HashMap<String, String>,
pub dependencies: Vec<String>,
pub protocol_version: Option<u16>,
pub raw_transfer: bool,
}
impl PluginManifest {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Deserialize)]
#[serde(untagged)]
#[serde(rename_all = "snake_case")]
pub enum HookHandler {
Execute {
executable: String,
#[serde(default)]
args: Vec<String>,
},
Constant {
constant: serde_json::Value,
},
Native {
#[serde(deserialize_with = "deserialize_native_function")]
function: NativeHookHandler,
},
}
impl Debug for HookHandler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "HookHandler")
}
}
fn deserialize_native_function<'de, D>(_: D) -> Result<NativeHookHandler, D::Error>
where
D: Deserializer<'de>,
{
Ok(Arc::new(|_| Ok(String::new())))
}
pub type NativeHookHandler = Arc<dyn Fn(String) -> anyhow::Result<String> + Send + Sync + 'static>;