use crate::error::{CryptoError, Result};
use crate::i18n::{translate, translate_with_args};
use crate::plugin::{Plugin, PluginMetadata};
use libloading::{Library, Symbol};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{Duration, Instant};
const DEFAULT_MAX_PLUGIN_SIZE: u64 = 50 * 1024 * 1024;
const DEFAULT_PLUGIN_LOAD_TIMEOUT: Duration = Duration::from_secs(30);
const DEFAULT_MAX_CONCURRENT_LOADS: usize = 4;
pub struct PluginLoader {
plugin_dirs: Vec<PathBuf>,
loaded_plugins: HashMap<String, Arc<dyn Plugin>>,
libraries: Vec<Arc<Library>>,
max_plugin_size: u64,
load_timeout: Duration,
max_concurrent_loads: usize,
active_loads: std::sync::atomic::AtomicUsize,
}
#[allow(dead_code)]
impl PluginLoader {
pub fn new(plugin_dirs: Vec<PathBuf>) -> Self {
Self {
plugin_dirs,
loaded_plugins: HashMap::new(),
libraries: Vec::new(),
max_plugin_size: DEFAULT_MAX_PLUGIN_SIZE,
load_timeout: DEFAULT_PLUGIN_LOAD_TIMEOUT,
max_concurrent_loads: DEFAULT_MAX_CONCURRENT_LOADS,
active_loads: std::sync::atomic::AtomicUsize::new(0),
}
}
pub fn with_limits(
plugin_dirs: Vec<PathBuf>,
max_plugin_size: u64,
load_timeout: Duration,
max_concurrent_loads: usize,
) -> Self {
Self {
plugin_dirs,
loaded_plugins: HashMap::new(),
libraries: Vec::new(),
max_plugin_size,
load_timeout,
max_concurrent_loads,
active_loads: std::sync::atomic::AtomicUsize::new(0),
}
}
pub fn load_plugin_from_file(&mut self, path: &Path) -> Result<Arc<dyn Plugin>> {
let metadata = self.validate_plugin_file(path)?;
self.check_concurrent_load_limit()?;
let load_start = Instant::now();
let _guard = LoadGuard::new(&self.active_loads, self.max_concurrent_loads);
if load_start.elapsed() > self.load_timeout {
return Err(CryptoError::PluginError(
translate("plugin.load_timeout").to_string(),
));
}
let file_size = fs::metadata(path)
.map_err(|e| CryptoError::PluginError(format!("获取插件文件元数据失败: {}", e)))?
.len();
if file_size > self.max_plugin_size {
return Err(CryptoError::PluginError(format!(
"插件文件大小 {} 超过最大允许大小 {}",
file_size, self.max_plugin_size
)));
}
let lib = unsafe {
Library::new(path).map_err(|e| {
CryptoError::PluginError(translate_with_args(
"plugin.load_library_failed",
&[("error", &e.to_string())],
))
})?
};
let lib_arc = Arc::new(lib);
type PluginConstructor = unsafe fn() -> *mut dyn Plugin;
let plugin = unsafe {
let constructor: Symbol<PluginConstructor> =
lib_arc.get(b"_create_plugin").map_err(|e| {
CryptoError::PluginError(translate_with_args(
"plugin.find_symbol_failed",
&[("error", &e.to_string())],
))
})?;
let plugin_ptr = constructor();
if plugin_ptr.is_null() {
return Err(CryptoError::PluginError(translate_with_args(
"plugin.constructor_null",
&[],
)));
}
Arc::from_raw(plugin_ptr)
};
self.libraries.push(lib_arc);
self.loaded_plugins
.insert(metadata.name.clone(), plugin.clone());
Ok(plugin)
}
pub fn load_all_plugins(&mut self) -> Vec<Result<Arc<dyn Plugin>>> {
let mut results = Vec::with_capacity(64);
let mut paths_to_load = Vec::new();
for dir in &self.plugin_dirs {
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("plugin") {
paths_to_load.push(path);
}
}
}
}
for path in paths_to_load {
results.push(self.load_plugin_from_file(&path));
}
results
}
fn validate_plugin_file(&self, path: &Path) -> Result<PluginMetadata> {
let content = fs::read(path)
.map_err(|e| CryptoError::PluginError(format!("读取插件文件失败: {}", e)))?;
let mut hasher = Sha256::new();
hasher.update(&content);
let checksum = format!("{:x}", hasher.finalize());
let metadata_path = path.with_extension("json");
let metadata = if metadata_path.exists() {
let metadata_content = fs::read_to_string(&metadata_path)
.map_err(|e| CryptoError::PluginError(format!("读取元数据文件失败: {}", e)))?;
let metadata: PluginMetadata = serde_json::from_str(&metadata_content)
.map_err(|e| CryptoError::PluginError(format!("解析元数据失败: {}", e)))?;
if metadata.checksum != checksum {
return Err(CryptoError::PluginError(
translate("plugin.checksum_mismatch").to_string(),
));
}
metadata
} else {
PluginMetadata {
name: path.file_stem().unwrap().to_string_lossy().to_string(),
version: "0.0.0".to_string(),
author: "Unknown".to_string(),
description: "未找到元数据文件".to_string(),
dependencies: vec![],
checksum,
}
};
Ok(metadata)
}
pub fn unload_plugin(&mut self, name: &str) -> Result<()> {
if let Some(plugin) = self.loaded_plugins.remove(name) {
let mut plugin_clone = plugin.clone();
if let Some(p) = Arc::get_mut(&mut plugin_clone) {
p.shutdown()?;
}
self.libraries.retain(|lib| Arc::strong_count(lib) > 1);
log::info!(
"{}",
translate_with_args("plugin.unloaded", &[("name", name)])
);
Ok(())
} else {
Err(CryptoError::PluginError(translate_with_args(
"plugin.not_found",
&[("name", name)],
)))
}
}
pub fn get_plugin(&self, name: &str) -> Option<Arc<dyn Plugin>> {
self.loaded_plugins.get(name).cloned()
}
pub fn list_plugins(&self) -> Vec<String> {
self.loaded_plugins.keys().cloned().collect()
}
}
struct LoadGuard<'a> {
active_loads: &'a std::sync::atomic::AtomicUsize,
_marker: std::marker::PhantomData<()>,
}
impl<'a> LoadGuard<'a> {
fn new(active_loads: &'a std::sync::atomic::AtomicUsize, _max_loads: usize) -> Self {
active_loads.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Self {
active_loads,
_marker: std::marker::PhantomData,
}
}
}
impl<'a> Drop for LoadGuard<'a> {
fn drop(&mut self) {
self.active_loads
.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
}
}
impl PluginLoader {
fn check_concurrent_load_limit(&self) -> Result<()> {
let current_loads = self.active_loads.load(std::sync::atomic::Ordering::SeqCst);
if current_loads >= self.max_concurrent_loads {
return Err(CryptoError::PluginError(format!(
"已达到最大并发插件加载数 {},请等待当前加载完成",
self.max_concurrent_loads
)));
}
Ok(())
}
}