195 lines
5.4 KiB
Rust
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")
|
|
}
|
|
}
|