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, settings: Arc, plugins: Arc>>, } #[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, pub permissions: Vec, #[serde(default)] pub config_schema: Option, } impl PluginManager { pub fn new( app_handle: AppHandle, event_bus: Arc, settings: Arc ) -> 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 { 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") } }