diff --git a/pumpkin/src/plugin/cache.rs b/pumpkin/src/plugin/cache.rs new file mode 100644 index 000000000..f19566932 --- /dev/null +++ b/pumpkin/src/plugin/cache.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::path::Path; +use tokio::fs; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PermissionCacheEntry { + pub permissions_requested: Vec, + pub approved: bool, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct PermissionCache { + pub entries: HashMap, // Key is the hex hash of the plugin +} + +impl PermissionCache { + pub async fn load(path: &Path) -> Self { + if let Ok(data) = fs::read_to_string(path).await { + serde_json::from_str(&data).unwrap_or_default() + } else { + Self::default() + } + } + + pub async fn save(&self, path: &Path) -> tokio::io::Result<()> { + let data = serde_json::to_string_pretty(self).unwrap(); + fs::write(path, data).await + } +} + +pub async fn calculate_hash(path: &Path) -> tokio::io::Result { + let bytes = fs::read(path).await?; + let mut hasher = Sha256::new(); + hasher.update(&bytes); + let result = hasher.finalize(); + Ok(result + .iter() + .map(|b| format!("{b:02x}")) + .collect::>() + .join("")) +} diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index f7066d022..d38ecf168 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -17,6 +17,7 @@ use tokio::{ use tracing::{debug, error, info, warn}; pub mod api; +pub mod cache; pub mod loader; /// Constants for plugin permissions. /// @@ -671,6 +672,9 @@ impl PluginManager { return Ok(std::time::Duration::ZERO); } + let cache_path = path.join("permission_cache.json"); + let mut cache = cache::PermissionCache::load(&cache_path).await; + let mut prepared_plugins = Vec::new(); for entry in std::fs::read_dir(path)? { @@ -746,7 +750,40 @@ impl PluginManager { for name in sorted_names { if let Some((instance, metadata, loader_data, loader, path)) = plugins_map.remove(&name) { - let (allowed, wait_time) = Self::ask_permission_confirmation(&metadata); + let hash = cache::calculate_hash(&path).await.unwrap_or_default(); + let allowed; + let mut wait_time = std::time::Duration::ZERO; + + if let Some(entry) = cache.entries.get(&hash) { + if entry.permissions_requested == metadata.permissions { + allowed = entry.approved; + info!( + "Found cached permission decision for plugin \"{}\" (approved: {})", + metadata.name, allowed + ); + } else { + (allowed, wait_time) = Self::ask_permission_confirmation(&metadata); + cache.entries.insert( + hash, + cache::PermissionCacheEntry { + permissions_requested: metadata.permissions.clone(), + approved: allowed, + }, + ); + let _ = cache.save(&cache_path).await; + } + } else { + (allowed, wait_time) = Self::ask_permission_confirmation(&metadata); + cache.entries.insert( + hash, + cache::PermissionCacheEntry { + permissions_requested: metadata.permissions.clone(), + approved: allowed, + }, + ); + let _ = cache.save(&cache_path).await; + } + total_wait_time += wait_time; if !allowed { @@ -784,7 +821,42 @@ impl PluginManager { if loader.can_load(path) { let (instance, metadata, loader_data) = loader.load(path).await?; - let (allowed, _wait_time) = Self::ask_permission_confirmation(&metadata); + let cache_path = Path::new(PLUGIN_DIR).join("permission_cache.json"); + let mut cache = cache::PermissionCache::load(&cache_path).await; + let hash = cache::calculate_hash(path).await.unwrap_or_default(); + + let allowed; + + if let Some(entry) = cache.entries.get(&hash) { + if entry.permissions_requested == metadata.permissions { + allowed = entry.approved; + info!( + "Found cached permission decision for plugin \"{}\" (approved: {})", + metadata.name, allowed + ); + } else { + (allowed, _) = Self::ask_permission_confirmation(&metadata); + cache.entries.insert( + hash, + cache::PermissionCacheEntry { + permissions_requested: metadata.permissions.clone(), + approved: allowed, + }, + ); + let _ = cache.save(&cache_path).await; + } + } else { + (allowed, _) = Self::ask_permission_confirmation(&metadata); + cache.entries.insert( + hash, + cache::PermissionCacheEntry { + permissions_requested: metadata.permissions.clone(), + approved: allowed, + }, + ); + let _ = cache.save(&cache_path).await; + } + if !allowed { warn!( "Permission denied for plugin \"{}\", skipping loading.",