use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
use tokio::fs;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PluginTrustLevel {
Unverified, Verified, Official, Community, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginManifest {
pub name: String,
pub version: String,
pub description: String,
pub author: String,
pub homepage: Option<String>,
pub license: String,
pub kandil_version: String, pub entry_point: String, pub dependencies: Vec<String>,
pub trust_level: PluginTrustLevel,
pub revenue_share: u8, pub download_count: u64,
pub rating: f32, pub security_audit_date: Option<std::time::SystemTime>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginMetadata {
pub manifest: PluginManifest,
pub install_path: String,
pub installed_at: std::time::SystemTime,
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketplaceConfig {
pub trusted_sources: Vec<String>,
pub auto_update: bool,
pub security_scan_on_install: bool,
pub revenue_share_percent: u8, }
impl Default for MarketplaceConfig {
fn default() -> Self {
Self {
trusted_sources: vec!["https://registry.kandil.dev".to_string()],
auto_update: true,
security_scan_on_install: true,
revenue_share_percent: 70, }
}
}
pub struct PluginMarketplace {
plugins: HashMap<String, PluginMetadata>,
config: MarketplaceConfig,
marketplace_url: String,
}
impl PluginMarketplace {
pub fn new(marketplace_url: &str) -> Self {
Self {
plugins: HashMap::new(),
config: MarketplaceConfig::default(),
marketplace_url: marketplace_url.to_string(),
}
}
pub async fn install_plugin(&mut self, source: &str) -> Result<String> {
let plugin_path = if source.starts_with("http") {
self.download_plugin(source).await?
} else if Path::new(source).exists() {
source.to_string()
} else {
self.download_from_registry(source).await?
};
if self.config.security_scan_on_install {
self.scan_plugin_security(&plugin_path).await?;
}
let manifest = self.validate_plugin(&plugin_path).await?;
let install_path = self.install_to_user_dir(&manifest, &plugin_path).await?;
let metadata = PluginMetadata {
manifest,
install_path,
installed_at: std::time::SystemTime::now(),
enabled: true,
};
self.plugins
.insert(metadata.manifest.name.clone(), metadata);
Ok(format!("Plugin '{}' installed successfully", source))
}
async fn download_plugin(&self, url: &str) -> Result<String> {
let temp_dir = std::env::temp_dir();
let plugin_path = temp_dir.join("temp_plugin.kandil");
fs::write(&plugin_path, b"simulated_plugin_content").await?;
Ok(plugin_path.to_string_lossy().to_string())
}
async fn download_from_registry(&self, plugin_name: &str) -> Result<String> {
println!("Would download plugin '{}' from registry", plugin_name);
Err(anyhow::anyhow!(
"Registry download not implemented in simulation"
))
}
async fn scan_plugin_security(&self, plugin_path: &str) -> Result<()> {
println!("Scanning plugin for security: {}", plugin_path);
Ok(())
}
async fn validate_plugin(&self, plugin_path: &str) -> Result<PluginManifest> {
Ok(PluginManifest {
name: "example-plugin".to_string(),
version: "1.0.0".to_string(),
description: "Example plugin".to_string(),
author: "Example Author".to_string(),
homepage: Some("https://example.com".to_string()),
license: "MIT".to_string(),
kandil_version: "0.1.0".to_string(),
entry_point: "main.exe".to_string(),
dependencies: vec![],
trust_level: PluginTrustLevel::Unverified,
revenue_share: self.config.revenue_share_percent,
download_count: 0,
rating: 0.0,
security_audit_date: None,
})
}
async fn install_to_user_dir(
&self,
manifest: &PluginManifest,
source_path: &str,
) -> Result<String> {
let user_plugins_dir = self.get_user_plugins_dir()?;
let plugin_dir = user_plugins_dir.join(&manifest.name);
fs::create_dir_all(&plugin_dir).await?;
let dest_path = plugin_dir.join(&manifest.entry_point);
fs::copy(source_path, &dest_path).await?;
Ok(plugin_dir.to_string_lossy().to_string())
}
fn get_user_plugins_dir(&self) -> Result<std::path::PathBuf> {
let home_dir =
dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not get home directory"))?;
Ok(home_dir.join(".kandil").join("plugins"))
}
pub async fn list_installed_plugins(&self) -> Vec<&PluginMetadata> {
self.plugins.values().collect()
}
pub async fn uninstall_plugin(&mut self, plugin_name: &str) -> Result<()> {
if let Some(metadata) = self.plugins.get(plugin_name) {
if Path::new(&metadata.install_path).exists() {
fs::remove_dir_all(&metadata.install_path).await?;
}
self.plugins.remove(plugin_name);
Ok(())
} else {
Err(anyhow::anyhow!("Plugin '{}' not found", plugin_name))
}
}
pub async fn enable_plugin(&mut self, plugin_name: &str) -> Result<()> {
if let Some(metadata) = self.plugins.get_mut(plugin_name) {
metadata.enabled = true;
Ok(())
} else {
Err(anyhow::anyhow!("Plugin '{}' not found", plugin_name))
}
}
pub async fn disable_plugin(&mut self, plugin_name: &str) -> Result<()> {
if let Some(metadata) = self.plugins.get_mut(plugin_name) {
metadata.enabled = false;
Ok(())
} else {
Err(anyhow::anyhow!("Plugin '{}' not found", plugin_name))
}
}
pub async fn search_plugins(&self, query: &str) -> Result<Vec<PluginManifest>> {
println!("Searching for plugins matching: {}", query);
Ok(vec![])
}
pub async fn get_plugin_details(&self, plugin_name: &str) -> Result<PluginManifest> {
if let Some(metadata) = self.plugins.get(plugin_name) {
Ok(metadata.manifest.clone())
} else {
Err(anyhow::anyhow!("Plugin '{}' not found", plugin_name))
}
}
pub async fn update_plugin(&self, plugin_name: &str) -> Result<()> {
if !self.config.auto_update {
return Err(anyhow::anyhow!("Auto-update is disabled in configuration"));
}
if let Some(metadata) = self.plugins.get(plugin_name) {
println!("Updating plugin: {}", plugin_name);
Ok(())
} else {
Err(anyhow::anyhow!("Plugin '{}' not found", plugin_name))
}
}
pub async fn run_plugin(&self, plugin_name: &str, args: &[String]) -> Result<String> {
if let Some(metadata) = self.plugins.get(plugin_name) {
if !metadata.enabled {
return Err(anyhow::anyhow!("Plugin '{}' is disabled", plugin_name));
}
println!("Running plugin: {} with args: {:?}", plugin_name, args);
Ok(format!("Plugin '{}' executed successfully", plugin_name))
} else {
Err(anyhow::anyhow!("Plugin '{}' not found", plugin_name))
}
}
pub fn get_config(&self) -> &MarketplaceConfig {
&self.config
}
pub fn set_config(&mut self, config: MarketplaceConfig) {
self.config = config;
}
}
pub struct PluginSecurityAuditor;
impl PluginSecurityAuditor {
pub fn new() -> Self {
Self
}
pub async fn audit_plugin(&self, plugin_path: &str) -> Result<SecurityAuditReport> {
println!("Auditing plugin security: {}", plugin_path);
Ok(SecurityAuditReport {
plugin_path: plugin_path.to_string(),
audit_date: std::time::SystemTime::now(),
security_score: 85, vulnerabilities_found: 0,
issues: vec![],
is_safe: true,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityAuditReport {
pub plugin_path: String,
pub audit_date: std::time::SystemTime,
pub security_score: u8, pub vulnerabilities_found: u32,
pub issues: Vec<String>,
pub is_safe: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_marketplace_creation() {
let marketplace = PluginMarketplace::new("https://registry.kandil.dev");
assert_eq!(marketplace.marketplace_url, "https://registry.kandil.dev");
}
#[tokio::test]
async fn test_plugin_metadata_creation() {
let manifest = PluginManifest {
name: "test-plugin".to_string(),
version: "1.0.0".to_string(),
description: "Test plugin".to_string(),
author: "Test Author".to_string(),
homepage: Some("https://example.com".to_string()),
license: "MIT".to_string(),
kandil_version: "0.1.0".to_string(),
entry_point: "main.js".to_string(),
dependencies: vec![],
trust_level: PluginTrustLevel::Verified,
revenue_share: 70,
download_count: 0,
rating: 4.5,
security_audit_date: None,
};
let metadata = PluginMetadata {
manifest,
install_path: "/path/to/plugin".to_string(),
installed_at: std::time::SystemTime::now(),
enabled: true,
};
assert_eq!(metadata.manifest.name, "test-plugin");
assert!(metadata.enabled);
}
}