EU-Utility-V3/src-tauri/src/plugins.rs

195 lines
5.4 KiB
Rust

use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tauri::AppHandle;
use tokio::fs;
use tracing::{error, info, warn};
use crate::api::PluginInfo;
use crate::events::EventBus;
use crate::settings::SettingsManager;
pub struct PluginManager {
app_handle: AppHandle,
event_bus: Arc<EventBus>,
settings: Arc<SettingsManager>,
plugins: Arc<tokio::sync::RwLock<HashMap<String, Plugin>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Plugin {
pub manifest: PluginManifest,
pub path: PathBuf,
pub active: bool,
pub data: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginManifest {
pub id: String,
pub name: String,
pub version: String,
pub author: String,
pub description: String,
pub category: String,
pub has_overlay: bool,
pub hotkey: Option<String>,
pub permissions: Vec<String>,
#[serde(default)]
pub config_schema: Option<Value>,
}
impl PluginManager {
pub fn new(
app_handle: AppHandle,
event_bus: Arc<EventBus>,
settings: Arc<SettingsManager>
) -> Self {
Self {
app_handle,
event_bus,
settings,
plugins: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
}
}
pub async fn load_all(&self
) {
let plugins_dir = self.get_plugins_dir();
if !plugins_dir.exists() {
info!("Plugins directory does not exist, creating...");
if let Err(e) = std::fs::create_dir_all(&plugins_dir) {
error!("Failed to create plugins directory: {}", e);
return;
}
}
let mut entries = match fs::read_dir(&plugins_dir).await {
Ok(entries) => entries,
Err(e) => {
error!("Failed to read plugins directory: {}", e);
return;
}
};
while let Ok(Some(entry)) = entries.next_entry().await {
let path = entry.path();
if path.is_dir() {
if let Err(e) = self.load_plugin(&path).await {
warn!("Failed to load plugin at {:?}: {}", path, e);
}
}
}
info!("Loaded {} plugins", self.plugins.read().await.len());
}
async fn load_plugin(
&self,
path: &PathBuf
) -> Result<(), String> {
let manifest_path = path.join("plugin.json");
if !manifest_path.exists() {
return Err("No plugin.json found".to_string());
}
let manifest_content = fs::read_to_string(&manifest_path)
.await
.map_err(|e| e.to_string())?;
let manifest: PluginManifest = serde_json::from_str(&manifest_content)
.map_err(|e| format!("Invalid plugin.json: {}", e))?;
info!("Loaded plugin: {} v{}", manifest.name, manifest.version);
let plugin = Plugin {
manifest: manifest.clone(),
path: path.clone(),
active: false,
data: Value::Null,
};
let mut plugins = self.plugins.write().await;
plugins.insert(manifest.id.clone(), plugin);
Ok(())
}
pub async fn get_plugins(&self
) -> Vec<PluginInfo> {
let plugins = self.plugins.read().await;
plugins.values().map(|p| PluginInfo {
id: p.manifest.id.clone(),
name: p.manifest.name.clone(),
version: p.manifest.version.clone(),
author: p.manifest.author.clone(),
description: p.manifest.description.clone(),
active: p.active,
has_overlay: p.manifest.has_overlay,
hotkey: p.manifest.hotkey.clone(),
}).collect()
}
pub async fn activate(
&self,
plugin_id: &str
) -> Result<(), String> {
let mut plugins = self.plugins.write().await;
if let Some(plugin) = plugins.get_mut(plugin_id) {
plugin.active = true;
// Emit activation event
self.event_bus.publish(
"plugin.activated",
serde_json::json!({
"plugin_id": plugin_id,
"plugin_name": plugin.manifest.name
})
);
info!("Activated plugin: {}", plugin.manifest.name);
Ok(())
} else {
Err(format!("Plugin not found: {}", plugin_id))
}
}
pub async fn deactivate(
&self,
plugin_id: &str
) -> Result<(), String> {
let mut plugins = self.plugins.write().await;
if let Some(plugin) = plugins.get_mut(plugin_id) {
plugin.active = false;
// Emit deactivation event
self.event_bus.publish(
"plugin.deactivated",
serde_json::json!({
"plugin_id": plugin_id
})
);
info!("Deactivated plugin: {}", plugin_id);
Ok(())
} else {
Err(format!("Plugin not found: {}", plugin_id))
}
}
fn get_plugins_dir(&self
) -> PathBuf {
self.app_handle
.path_resolver()
.app_data_dir()
.expect("Failed to get app data dir")
.join("plugins")
}
}