From d58a26a5e1c18ad5b3ed172a86259fa42eb140f7 Mon Sep 17 00:00:00 2001 From: Eduard Smet Date: Mon, 9 Mar 2026 02:20:23 +0100 Subject: [PATCH] feat(plugins): Improve cache checking logic Other notable changes: - Restructured and switched to snake_case in the registry metadata file. - Removed the unnecessary Box of the Config struct. - Started adopting Anyhow in plugin and HTTP registry modules. - Still needs further tweaking (adding/removing context), will happen with a later full anyhow adoption PR. - Added the plugin user ID to their Store - This will allow recognizing plugins during host calls and is the first step towards the new plugin API. --- Cargo.lock | 2 + Cargo.toml | 4 +- src/config.rs | 4 +- src/http.rs | 2 +- src/http/registry.rs | 72 ++--- src/main.rs | 8 +- src/plugins.rs | 6 +- src/plugins/registry.rs | 481 ++++++++++++++++++++------------ src/plugins/runtime.rs | 60 ++-- src/plugins/runtime/internal.rs | 3 + 10 files changed, 371 insertions(+), 271 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 001f667..a941dac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -818,6 +818,7 @@ dependencies = [ name = "discord-bot" version = "0.1.0" dependencies = [ + "anyhow", "bytes", "chrono", "clap", @@ -825,6 +826,7 @@ dependencies = [ "indexmap", "reqwest", "rustls 0.23.36", + "semver", "serde", "serde_yaml_ng", "sonic-rs", diff --git a/Cargo.toml b/Cargo.toml index 3c028f1..804d60f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2024" [features] [dependencies] +anyhow = "1" bytes = "1" chrono = "0.4" clap = { version = "4", features = ["derive"] } @@ -16,9 +17,10 @@ dotenvy = "0.15" indexmap = "2" reqwest = { version = "0.13", features = ["hickory-dns"] } rustls = "0.23" +semver = "1" serde = "1" -sonic-rs = "0.5" serde_yaml_ng = "0.10" # Should replace this with a better maintained YAML 1.2 supporting alternative +sonic-rs = "0.5" tokio = { version = "1", features = ["full"] } tokio-cron-scheduler = { version = "0.15", features = ["english"] } tokio-util = "0.7" diff --git a/src/config.rs b/src/config.rs index 327a8c1..bdf4854 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,7 +17,7 @@ pub struct Config { } impl Config { - pub fn new(file_path: &Path) -> Result, ()> { + pub fn new(file_path: &Path) -> Result { info!("Loading and parsing the config file"); let file_bytes = match fs::read(file_path) { @@ -29,7 +29,7 @@ impl Config { }; match serde_yaml_ng::from_slice::(&file_bytes) { - Ok(config) => Ok(Box::new(config)), // TODO: Env var interpolation, maybe via YAML 1.2's' `!val` + Ok(config) => Ok(config), // TODO: Env var interpolation Err(err) => { error!( "An error occurred while trying to deserialize the config file YAML to a struct: {err}" diff --git a/src/http.rs b/src/http.rs index 7cf3e8b..0aa2cd6 100644 --- a/src/http.rs +++ b/src/http.rs @@ -12,7 +12,7 @@ pub struct HttpClient { client: Client, } -const USER_AGENT: &str = "celarye/discord-bot"; +static USER_AGENT: &str = "celarye/discord-bot"; impl HttpClient { pub fn new(http_client_timeout_seconds: u64) -> Result { diff --git a/src/http/registry.rs b/src/http/registry.rs index 9556a09..41583a4 100644 --- a/src/http/registry.rs +++ b/src/http/registry.rs @@ -1,72 +1,38 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ /* Copyright © 2026 Eduard Smet */ -use std::{ - str::FromStr, - sync::{Arc, LazyLock}, -}; +use std::str::FromStr; +use anyhow::{Context, Error, Result}; use reqwest::StatusCode; -use tracing::{debug, error}; +use tracing::debug; use url::{ParseError, Url}; use crate::http::HttpClient; -static DEFAULT_REGISTRY_URL: LazyLock = LazyLock::new(|| { - Url::parse("https://raw.githubusercontent.com/celarye/discord-bot-plugins/refs/heads/master/") - .unwrap() -}); - impl HttpClient { - pub async fn get_file_from_registry( - &self, - registry: &Arc>, - path: &str, - ) -> Result, ()> { - let url = match Self::parse_url(registry, path) { - Ok(url) => url, - Err(err) => { - error!( - "An error occurred while trying to construct a valid URL from the provided registry and path: {err}" - ); - return Err(()); - } - }; + pub async fn get_file_from_registry(&self, registry: &str, path: &str) -> Result> { + let url = Self::parse_url(registry, path).context("An error occurred while trying to construct a valid URL from the provided registry and path")?; debug!("Requested registry file: {url}"); - match self.client.get(url).send().await { - Ok(raw_response) => { - if raw_response.status() != StatusCode::OK { - error!( - "The response was undesired, status code: {}", - raw_response.status(), - ); - return Err(()); - } + let response = self.client.get(url).send().await?; - match raw_response.bytes().await { - Ok(response) => Ok(response.to_vec()), - Err(err) => { - error!( - "Something went wrong while getting the raw bytes from the response, error: {err}" - ); - Err(()) - } - } - } - Err(err) => { - error!("Something went wrong while making the request, error: {err}"); - Err(()) - } + if response.status() != StatusCode::OK { + return Err(Error::msg(format!( + "The response was undesired, status code: {}", + response.status() + ))); } - } - fn parse_url(registry: &Arc>, path: &str) -> Result { - if let Some(registry) = registry.as_deref() { - return Url::from_str(registry)?.join(path); - } + Ok(response + .bytes() + .await + .context("Something went wrong while getting the raw bytes from the response")? + .to_vec()) + } - DEFAULT_REGISTRY_URL.join(path) + fn parse_url(registry: &str, path: &str) -> Result { + Url::from_str(&format!("https://{registry}/"))?.join(path) } } diff --git a/src/main.rs b/src/main.rs index eb8bc54..0f31578 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use std::os::unix::process::CommandExt; use std::{ - collections::VecDeque, + collections::{HashMap, VecDeque}, env, ffi::OsString, path::{Path, PathBuf}, @@ -139,10 +139,10 @@ fn initialization( async fn registry_get_plugins( http_client_timeout_seconds: u64, - config: Box, + config: Config, plugin_directory: PathBuf, cache: bool, -) -> Result, ()> { +) -> Result, ()> { let http_client = Arc::new(HttpClient::new(http_client_timeout_seconds)?); registry::get_plugins(http_client, config, plugin_directory, cache).await @@ -150,7 +150,7 @@ async fn registry_get_plugins( async fn plugin_initializations( runtime: Arc, - available_plugins: Vec, + available_plugins: HashMap, plugin_registrations: Arc>, config_directory: &Path, ) -> Result<(), ()> { diff --git a/src/plugins.rs b/src/plugins.rs index 82c4c71..dc7becd 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -7,6 +7,7 @@ pub mod runtime; use std::collections::{HashMap, HashSet}; +use semver::Version; use serde::{Deserialize, Deserializer}; use serde_yaml_ng::Value; use twilight_model::id::{Id, marker::CommandMarker}; @@ -15,7 +16,7 @@ use crate::plugins::discord_bot::plugin::plugin_types::SupportedRegistrations; wasmtime::component::bindgen!({ imports: { default: async }, exports: { default: async } }); -#[derive(Deserialize)] +#[derive(Clone, Deserialize)] pub struct ConfigPlugin { pub plugin: String, pub cache: Option, @@ -86,8 +87,9 @@ impl<'de> Deserialize<'de> for SupportedRegistrations { } pub struct AvailablePlugin { + pub registry_id: String, pub id: String, - pub version: String, + pub version: Version, pub permissions: SupportedRegistrations, pub environment: Option>, pub settings: Option, diff --git a/src/plugins/registry.rs b/src/plugins/registry.rs index cb2fbc6..26219be 100644 --- a/src/plugins/registry.rs +++ b/src/plugins/registry.rs @@ -1,22 +1,26 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ /* Copyright © 2026 Eduard Smet */ -// TODO: Support faster cache fallback and prevent the need for a registry fetch - use std::{ collections::{BTreeMap, HashMap}, - fs, + io::ErrorKind, path::{Path, PathBuf}, - sync::Arc, + sync::{Arc, LazyLock}, }; +use anyhow::{Error, Result}; +use semver::{Version, VersionReq}; use serde::Deserialize; -use tracing::{debug, error, info}; +use tokio::fs; +use tracing::{error, info, warn}; -use crate::{config::Config, http::HttpClient, plugins::AvailablePlugin}; +use crate::{ + config::Config, + http::HttpClient, + plugins::{AvailablePlugin, ConfigPlugin}, +}; #[derive(Deserialize)] -#[serde(rename_all = "kebab-case")] #[allow(unused)] pub struct Registry { pub name: String, @@ -26,131 +30,195 @@ pub struct Registry { } #[derive(Deserialize)] -#[serde(rename_all = "kebab-case")] #[allow(unused)] pub struct RegistryPlugin { pub versions: Vec, - pub deprecated: Option<(bool, String)>, pub description: String, - pub release_time: String, } #[derive(Deserialize)] -#[serde(rename_all = "kebab-case")] +#[allow(unused)] pub struct RegistryPluginVersion { pub version: String, - pub deprecated: Option<(bool, String)>, + pub release_time: String, pub compatible_program_version: String, + pub deprecated: Option, + pub deprecation_reason: Option, } -const DEFAULT_REGISTRY_ID: &str = "celarye/discord-bot-plugins"; +type RegistryTask = Vec>>>; + +static DEFAULT_REGISTRY_ID: &str = + "raw.githubusercontent.com/celarye/discord-bot-plugins/refs/heads/master"; + +static PROGRAM_VERSION: LazyLock = + LazyLock::new(|| Version::parse(env!("CARGO_PKG_VERSION")).unwrap()); pub async fn get_plugins( http_client: Arc, - config: Box, - base_plugin_directory: PathBuf, + config: Config, + base_plugin_directory_path: PathBuf, cache: bool, -) -> Result, ()> { +) -> Result, ()> { info!("Fetching and storing the plugins"); - let mut available_plugins = vec![]; + let mut available_plugins = HashMap::new(); + + let registries = get_cached_plugins( + &base_plugin_directory_path, + config, + cache, + &mut available_plugins, + ) + .await; + + fetch_non_cached_plugins( + http_client, + &base_plugin_directory_path, + registries, + &mut available_plugins, + ) + .await; + + if available_plugins.is_empty() { + warn!("No plugins are available for the runtime"); + return Err(()); + } + + Ok(available_plugins) +} +async fn get_cached_plugins( + base_plugin_directory_path: &Path, + config: Config, + cache: bool, + available_plugins: &mut HashMap, +) -> HashMap> { let mut registries = HashMap::new(); - registries.insert(DEFAULT_REGISTRY_ID.to_string(), vec![]); - - for (plugin_id, mut plugin_options) in config.plugins { - if let Some(registry) = parse_plugin_string_registry(&mut plugin_options.plugin) { - registries - .entry(registry) - .or_insert(vec![]) - .push((plugin_id, plugin_options)); - } else { - registries - .get_mut(DEFAULT_REGISTRY_ID) - .unwrap() - .push((plugin_id, plugin_options)); + for (plugin_uid, plugin_options) in config.plugins { + let (plugin_string, plugin_requested_version) = + parse_plugin_string_requested_version(&plugin_options.plugin); + let (registry_id, plugin_id) = parse_plugin_string_registry_id(plugin_string); + + if plugin_options.cache.unwrap_or(cache) { + match check_plugin_cache( + base_plugin_directory_path, + registry_id, + plugin_id, + plugin_requested_version, + ) + .await + { + Ok(cache_check) => { + if let Some(plugin_version) = cache_check { + available_plugins.insert( + plugin_uid, + AvailablePlugin { + registry_id: registry_id.to_string(), + id: plugin_id.to_string(), + version: plugin_version, + permissions: plugin_options.permissions, + environment: plugin_options.environment, + settings: plugin_options.settings, + }, + ); + + continue; + } + } + Err(err) => { + error!("An error occurred while checking if the {plugin_uid} is cached: {err}"); + } + } } + + registries + .entry(registry_id.to_string()) + .or_insert(vec![]) + .push((plugin_uid, plugin_options)); } - let mut registry_tasks = vec![]; + registries +} - for (registry_id, plugins) in registries { - let registry_id = match registry_id.as_str() { - DEFAULT_REGISTRY_ID => Arc::new(None), - &_ => Arc::new(Some(registry_id)), - }; +async fn fetch_non_cached_plugins( + http_client: Arc, + base_plugin_directory_path: &Path, + registries: HashMap>, + available_plugins: &mut HashMap, +) { + let mut registry_tasks: RegistryTask = vec![]; + for (registry_id, plugins) in registries { let http_client = http_client.clone(); - let base_plugin_directory = base_plugin_directory.clone(); - let registry_id = registry_id.clone(); + let registry_directory_path = base_plugin_directory_path.join(®istry_id); + let registry_id = Arc::new(registry_id); registry_tasks.push(tokio::spawn(async move { let mut available_registry_plugins = vec![]; - let registry = match fetch_registry(®istry_id, http_client.clone()).await { - Ok(registry) => Arc::new(registry), - Err(()) => return Err(()), - }; + let registry = Arc::new(fetch_registry(http_client.clone(), ®istry_id, ®istry_directory_path).await?); let mut plugin_tasks = vec![]; - for (plugin_id, mut plugin_options) in plugins { + for (plugin_uid, plugin_options) in plugins { let http_client = http_client.clone(); - let mut plugin_directory = base_plugin_directory.clone(); - let registry_id = registry_id.clone(); + let registry_directory_path = registry_directory_path.clone(); let registry = registry.clone(); plugin_tasks.push(tokio::spawn(async move { - let plugin_requested_version = parse_plugin_string_name_version(&mut plugin_options.plugin); + let (plugin_string, plugin_requested_version) = + parse_plugin_string_requested_version(&plugin_options.plugin); + let (registry_id, plugin_id) = parse_plugin_string_registry_id(plugin_string); - let Some(registry_plugin) = registry.plugins.get(&plugin_options.plugin) else { - error!("The {} registry has no {} plugin entry", registry_id.as_deref().unwrap_or(DEFAULT_REGISTRY_ID), plugin_options.plugin); - return Err(()); + let mut plugin_directory_path = registry_directory_path.join(plugin_id); + + let Some(registry_plugin) = registry.plugins.get(plugin_id) else { + return Err(Error::msg(format!("The {registry_id} registry has no {plugin_id} plugin entry", + ))); }; - let Some(version) = - find_plugin_version_match(&plugin_requested_version, ®istry_plugin.versions, &plugin_id) + let Some(plugin_version) = get_plugin_matching_version( + plugin_requested_version, + ®istry_plugin.versions, + )? else { - error!( - "The requested {plugin_requested_version} version of the {plugin_id} plugin is deprecated or can not be ran by this version of the program" - ); - return Err(()); + return Err(Error::msg(format!( + "The {plugin_uid} plugin has no version which isn't marked as deprecated and is compatible with this version of the program"))); }; - plugin_directory.push(&plugin_id); - plugin_directory.push(&version); - - let plugin_registry_path = plugin_options.plugin + "/" + &version + "/"; - - if plugin_options.cache.unwrap_or(cache) && fs::exists(plugin_directory.join("plugin.wasm")).unwrap_or(false) { - info!("Using the cached version of the {plugin_id} plugin"); - - return Ok(AvailablePlugin { - id: plugin_id, - version, + plugin_directory_path.push(plugin_version.to_string()); + + let plugin_url_segment = format!("{plugin_id}/{plugin_version}/"); + + fetch_plugin( + http_client, + registry_id, + plugin_id, + &plugin_url_segment, + &plugin_directory_path, + ) + .await?; + + Ok(( + plugin_uid, + AvailablePlugin { + registry_id: registry_id.to_string(), + id: plugin_id.to_string(), + version: plugin_version, permissions: plugin_options.permissions, environment: plugin_options.environment, settings: plugin_options.settings, - }); - } - - fetch_plugin(&http_client, ®istry_id, &plugin_registry_path, &plugin_directory, &plugin_id).await?; - - Ok(AvailablePlugin { - id: plugin_id, - version, - permissions: plugin_options.permissions, - environment: plugin_options.environment, - settings: plugin_options.settings, - }) - + }, + )) })); } - for plugin_task in plugin_tasks.drain(..) { - if let Ok(available_plugin) = plugin_task.await.unwrap() { - available_registry_plugins.push(available_plugin); + for plugin_task in plugin_tasks { + match plugin_task.await.unwrap() { + Ok(available_plugin) => available_registry_plugins.push(available_plugin), + Err(err) => error!("An error occurred while fetching a plugin from the {registry_id} registry: {err}") } } @@ -158,154 +226,207 @@ pub async fn get_plugins( })); } - for registry_task in registry_tasks.drain(..) { - if let Ok(mut available_registry_plugins) = registry_task.await.unwrap() { - available_plugins.append(&mut available_registry_plugins); + for registry_task in registry_tasks { + match registry_task.await.unwrap() { + Ok(available_registry_plugins) => { + for available_registry_plugin in available_registry_plugins { + available_plugins + .insert(available_registry_plugin.0, available_registry_plugin.1); + } + } + Err(err) => { + error!("An error occurred while fetching a registry: {err}"); + } } } +} - Ok(available_plugins) +fn parse_plugin_string_registry_id(value: &str) -> (&str, &str) { + match value.rsplit_once('/') { + Some((registry_id, plugin_string)) => (registry_id, plugin_string), + None => (DEFAULT_REGISTRY_ID, value), + } } -fn parse_plugin_string_registry(value: &mut String) -> Option { - let (registry, plugin_name_version) = value.rsplit_once('/')?; +fn parse_plugin_string_requested_version(value: &str) -> (&str, &str) { + match value.rsplit_once(':') { + Some((plugin_string, plugin_requested_version)) => { + (plugin_string, plugin_requested_version) + } + None => (value, "latest"), + } +} - let registry = registry.to_string(); +async fn check_plugin_cache( + base_plugin_directory: &Path, + registry_id: &str, + plugin_id: &str, + plugin_requested_version: &str, +) -> Result> { + let mut plugin_path = base_plugin_directory.join(registry_id); + plugin_path.push(plugin_id); + + let plugin_version = if plugin_requested_version == "latest" { + let Some(plugin_version) = get_plugin_latest_cached_version(&plugin_path).await? else { + return Ok(None); + }; - *value = plugin_name_version.to_string(); + plugin_path.push(plugin_version.to_string()); + plugin_path.push("plugin.wasm"); - Some(registry) -} + plugin_version + } else { + let plugin_version = Version::parse(plugin_requested_version)?; -async fn fetch_registry( - registry_id: &Arc>, - http_client: Arc, -) -> Result { - info!( - "Fetching the {} registry", - registry_id.as_deref().unwrap_or(DEFAULT_REGISTRY_ID) - ); + plugin_path.push(plugin_requested_version); + plugin_path.push("plugin.wasm"); - if let Ok(registry_bytes) = http_client - .get_file_from_registry(registry_id, "plugins.json") - .await - { - match sonic_rs::from_slice::(®istry_bytes) { - Ok(registry) => Ok(registry), - Err(err) => { - error!( - "Failed to deserialize the registry plugins file JSON to a struct, error: {err}" - ); - Err(()) - } - } - } else { - error!( - "The {} registry is invalid", - registry_id.as_deref().unwrap_or(DEFAULT_REGISTRY_ID) - ); - Err(()) + plugin_version + }; + + if fs::try_exists(plugin_path).await? { + return Ok(Some(plugin_version)); } + + Ok(None) } -fn parse_plugin_string_name_version(value: &mut String) -> String { - match value.rsplit_once(':') { - Some((plugin_registry_id, plugin_requested_version)) => { - let plugin_requested_version = plugin_requested_version.to_string(); +async fn get_plugin_latest_cached_version(plugin_path: &Path) -> Result> { + let mut plugin_latest_version = None; - *value = plugin_registry_id.to_string(); + let mut plugin_cached_dir = match fs::read_dir(plugin_path).await { + Ok(plugin_cached_dir) => plugin_cached_dir, + Err(err) => { + if err.kind() == ErrorKind::NotFound { + return Ok(None); + } - plugin_requested_version + return Err(Error::new(err)); + } + }; + + while let Some(plugin_cached_version) = plugin_cached_dir.next_entry().await? { + if plugin_cached_version.file_type().await?.is_dir() + && let Some(plugin_cached_version_file_name) = + plugin_cached_version.file_name().to_str() + { + let Ok(plugin_cached_version) = Version::parse(plugin_cached_version_file_name) else { + continue; + }; + + if &plugin_cached_version + > plugin_latest_version + .as_ref() + .unwrap_or(&Version::new(0, 0, 0)) + { + plugin_latest_version = Some(plugin_cached_version); + } } - None => String::from("latest"), } + + Ok(plugin_latest_version) } -fn find_plugin_version_match( +fn get_plugin_matching_version( requested_version: &str, plugin_versions: &[RegistryPluginVersion], - plugin_id: &str, -) -> Option { +) -> Result> { if requested_version == "latest" { - for plugin_version in plugin_versions.iter().rev() { - if check_plugin_version_usability(plugin_version, plugin_id) { - return Some(plugin_version.version.clone()); + let mut plugin_latest_version = None; + + for plugin_version in plugin_versions { + let plugin_version_version = Version::parse(&plugin_version.version)?; + + if check_plugin_version_usability(plugin_version) + && &plugin_version_version + > plugin_latest_version + .as_ref() + .unwrap_or(&Version::new(0, 0, 0)) + { + plugin_latest_version = Some(plugin_version_version); } } + + return Ok(plugin_latest_version); } else if let Some(plugin_version) = plugin_versions .iter() .find(|v| v.version == requested_version) - && check_plugin_version_usability(plugin_version, plugin_id) + && check_plugin_version_usability(plugin_version) { - return Some(plugin_version.version.clone()); + return Ok(Some(Version::parse(&plugin_version.version)?)); } - None + Ok(None) } -fn check_plugin_version_usability(plugin_version: &RegistryPluginVersion, plugin_id: &str) -> bool { - if let Some(deprecated) = plugin_version.deprecated.as_ref() - && deprecated.0 +fn check_plugin_version_usability(plugin_version: &RegistryPluginVersion) -> bool { + if let Some(deprecated) = plugin_version.deprecated + && deprecated { - debug!( - "The {plugin_id} plugin version {} is marked as deprecated: {}", - plugin_version.version, deprecated.1 - ); return false; - } else if plugin_version.compatible_program_version - != env!("CARGO_PKG_VERSION")[..plugin_version.compatible_program_version.len()] - { - debug!( - "The {plugin_id} plugin version {} is not compatible with this version of the program: {} != {}", - plugin_version.version, - plugin_version.compatible_program_version, - &env!("CARGO_PKG_VERSION")[..plugin_version.compatible_program_version.len()] - ); + } + + let Ok(plugin_compatible_program_version) = + VersionReq::parse(&plugin_version.compatible_program_version) + else { + return false; + }; + + if !plugin_compatible_program_version.matches(&PROGRAM_VERSION) { return false; } true } +async fn fetch_registry( + http_client: Arc, + registry_id: &str, + registry_directory_path: &Path, +) -> Result { + info!("Fetching the {registry_id} registry"); + + let registry_metadata_bytes = http_client + .get_file_from_registry(registry_id, "plugins.json") + .await?; + + fs::create_dir_all(registry_directory_path).await?; + + fs::write( + registry_directory_path.join("plugins.json"), + ®istry_metadata_bytes, + ) + .await?; + + sonic_rs::from_slice::(®istry_metadata_bytes).map_err(Error::new) +} + async fn fetch_plugin( - http_client: &Arc, - registry_id: &Arc>, - plugin_registry_path: &str, - plugin_directory: &Path, + http_client: Arc, + registry_id: &str, plugin_id: &str, -) -> Result<(), ()> { + plugin_url_segment: &str, + plugin_directory_path: &Path, +) -> Result<()> { info!("Fetching the {plugin_id} plugin from its registry"); - let plugin_metadata = http_client - .get_file_from_registry( - registry_id, - &(plugin_registry_path.to_string() + "metadata.json"), - ) + let plugin_metadata_bytes = http_client + .get_file_from_registry(registry_id, &(format!("{plugin_url_segment}metadata.json"))) .await?; - if let Err(err) = fs::create_dir_all(plugin_directory) { - error!("An error occurred while creating the {plugin_id} plugin directory: {err}"); - return Err(()); - } + fs::create_dir_all(plugin_directory_path).await?; - if let Err(err) = fs::write(plugin_directory.join("metadata.json"), plugin_metadata) { - error!( - "An error occurred while saving the metadata.json file for the {plugin_id} plugin: {err}" - ); - return Err(()); - } + fs::write( + plugin_directory_path.join("metadata.json"), + &plugin_metadata_bytes, + ) + .await?; - let plugin_wasm = http_client - .get_file_from_registry( - registry_id, - &(plugin_registry_path.to_string() + "plugin.wasm"), - ) + let plugin_bytes = http_client + .get_file_from_registry(registry_id, &(format!("{plugin_url_segment}plugin.wasm"))) .await?; - if let Err(err) = fs::write(plugin_directory.join("plugin.wasm"), plugin_wasm) { - error!("An error occurred while saving the plugin.wasm file: {err}"); - return Err(()); - } + fs::write(plugin_directory_path.join("plugin.wasm"), &plugin_bytes).await?; Ok(()) } diff --git a/src/plugins/runtime.rs b/src/plugins/runtime.rs index 7efa264..45a6459 100644 --- a/src/plugins/runtime.rs +++ b/src/plugins/runtime.rs @@ -88,7 +88,7 @@ impl Runtime { pub async fn initialize_plugins( runtime: Arc, plugin_builder: PluginBuilder, - plugins: Vec, + plugins: HashMap, plugin_registrations: Arc>, directory: &Path, ) -> Result<(), ()> { @@ -101,15 +101,18 @@ impl Runtime { scheduled_jobs: vec![], }; - for plugin in plugins { - let plugin_directory = directory.join(&plugin.id).join(&plugin.version); + for (plugin_uid, plugin) in plugins { + let plugin_directory = directory + .join(&plugin.registry_id) + .join(&plugin.id) + .join(plugin.version.to_string()); let bytes = match fs::read(plugin_directory.join("plugin.wasm")) { Ok(bytes) => bytes, Err(err) => { error!( "An error occured while reading the {} plugin file: {err}", - plugin.id + plugin_uid ); continue; } @@ -120,7 +123,7 @@ impl Runtime { Err(err) => { error!( "An error occured while creating a WASI component from the {} plugin: {err}", - plugin.id + plugin_uid ); continue; } @@ -140,14 +143,14 @@ impl Runtime { if !exists && let Err(err) = fs::create_dir(&workspace_plugin_dir) { error!( "Something went wrong while creating the workspace directory for the {} plugin, error: {}", - &plugin.id, &err + &plugin_uid, &err ); } } Err(err) => { error!( "Something went wrong while checking if the workspace directory of the {} plugin exists, error: {}", - &plugin.id, &err + &plugin_uid, &err ); return Err(()); } @@ -162,6 +165,7 @@ impl Runtime { let mut store = Store::::new( &plugin_builder.engine, InternalRuntime::new( + plugin_uid.clone(), wasi, WasiHttpCtx::new(), ResourceTable::new(), @@ -177,7 +181,7 @@ impl Runtime { Err(err) => { error!( "Failed to instantiate the {} plugin, error: {}", - &plugin.id, &err + &plugin_uid, &err ); continue; } @@ -197,7 +201,7 @@ impl Runtime { Err(err) => { error!( "Failed to initialize the {} plugin, error: {}", - &plugin.id, &err + &plugin_uid, &err ); continue; } @@ -205,7 +209,7 @@ impl Runtime { Err(err) => { error!( "The {} plugin exprienced a critical error: {}", - &plugin.id, &err + &plugin_uid, &err ); continue; } @@ -216,12 +220,6 @@ impl Runtime { store: Mutex::new(store), }; - runtime - .plugins - .write() - .await - .insert(plugin.id.clone(), plugin_context); - if let Some(discord_events) = plugin_registrations_request.discord_events { if discord_events.message_create { plugin_registrations @@ -229,7 +227,7 @@ impl Runtime { .await .discord_events .message_create - .push(plugin.id.clone()); + .push(plugin_uid.clone()); } if discord_events.thread_create { @@ -238,7 +236,7 @@ impl Runtime { .await .discord_events .thread_create - .push(plugin.id.clone()); + .push(plugin_uid.clone()); } if discord_events.thread_delete { @@ -247,7 +245,7 @@ impl Runtime { .await .discord_events .thread_delete - .push(plugin.id.clone()); + .push(plugin_uid.clone()); } if discord_events.thread_list_sync { @@ -256,7 +254,7 @@ impl Runtime { .await .discord_events .thread_list_sync - .push(plugin.id.clone()); + .push(plugin_uid.clone()); } if discord_events.thread_member_update { @@ -265,7 +263,7 @@ impl Runtime { .await .discord_events .thread_member_update - .push(plugin.id.clone()); + .push(plugin_uid.clone()); } if discord_events.thread_members_update { @@ -274,7 +272,7 @@ impl Runtime { .await .discord_events .thread_members_update - .push(plugin.id.clone()); + .push(plugin_uid.clone()); } if discord_events.thread_update { @@ -283,7 +281,7 @@ impl Runtime { .await .discord_events .thread_update - .push(plugin.id.clone()); + .push(plugin_uid.clone()); } if let Some(interaction_create) = discord_events.interaction_create { @@ -293,7 +291,7 @@ impl Runtime { .discord_event_interaction_create .application_commands .push(PluginRegistrationRequestsApplicationCommand { - plugin_id: plugin.id.clone(), + plugin_id: plugin_uid.clone(), data: application_command, }); } @@ -309,7 +307,7 @@ impl Runtime { .discord_events .interaction_create .message_components - .insert(message_component.clone(), plugin.id.clone()); + .insert(message_component.clone(), plugin_uid.clone()); } } @@ -323,7 +321,7 @@ impl Runtime { .discord_events .interaction_create .modals - .insert(modal.clone(), plugin.id.clone()); + .insert(modal.clone(), plugin_uid.clone()); } } } @@ -333,7 +331,7 @@ impl Runtime { for scheduled_job in scheduled_jobs { registration_requests.scheduled_jobs.push( PluginRegistrationRequestsScheduledJob { - plugin_id: plugin.id.clone(), + plugin_id: plugin_uid.clone(), id: scheduled_job.0, crons: scheduled_job.1, }, @@ -346,12 +344,18 @@ impl Runtime { let mut plugin_registrations = plugin_registrations.write().await; let functions = plugin_registrations .dependency_functions - .entry(plugin.id.clone()) + .entry(plugin_uid.clone()) .or_insert(HashSet::new()); functions.insert(dependency_function); } } + + runtime + .plugins + .write() + .await + .insert(plugin_uid, plugin_context); } let _ = runtime diff --git a/src/plugins/runtime/internal.rs b/src/plugins/runtime/internal.rs index 39aad8e..4aef3fb 100644 --- a/src/plugins/runtime/internal.rs +++ b/src/plugins/runtime/internal.rs @@ -25,6 +25,7 @@ use crate::{ }; pub struct InternalRuntime { + uid: String, wasi: WasiCtx, wasi_http: WasiHttpCtx, table: ResourceTable, @@ -150,12 +151,14 @@ impl DiscordTypes for InternalRuntime {} impl InternalRuntime { pub fn new( + uid: String, wasi: WasiCtx, wasi_http: WasiHttpCtx, table: ResourceTable, runtime: Weak, ) -> Self { InternalRuntime { + uid, wasi, wasi_http, table,