use super::core_traits::Plugin;
use super::types_config::{PluginCapability, PluginCategory, PluginMetadata};
use super::validation::{PluginManifest, PluginValidator, ValidationReport};
use crate::error::{Result, SklearsError};
use std::cmp::Ordering;
use std::collections::HashMap;
#[derive(Debug)]
pub struct PluginDiscoveryService {
repositories: Vec<PluginRepository>,
cache: PluginCache,
client: NetworkClient,
search_index: SearchIndex,
}
impl PluginDiscoveryService {
pub fn new() -> Self {
Self {
repositories: vec![PluginRepository::official(), PluginRepository::community()],
cache: PluginCache::new(),
client: NetworkClient::new(),
search_index: SearchIndex::new(),
}
}
pub fn add_repository(&mut self, repository: PluginRepository) {
self.repositories.push(repository);
self.repositories
.sort_by_key(|r| std::cmp::Reverse(r.priority));
}
pub async fn discover_all(&self) -> Result<Vec<PluginManifest>> {
let mut all_plugins = Vec::new();
let mut discovered_from_any = false;
for repository in &self.repositories {
match self.discover_from_repository(repository).await {
Ok(mut plugins) => {
all_plugins.append(&mut plugins);
discovered_from_any = true;
}
Err(e) => {
eprintln!(
"Failed to discover from repository {}: {}",
repository.name, e
);
}
}
}
if !discovered_from_any && !self.repositories.is_empty() {
return Err(SklearsError::InvalidOperation(
"Failed to discover plugins from any repository".to_string(),
));
}
all_plugins.sort_by(|a, b| {
a.metadata
.name
.cmp(&b.metadata.name)
.then_with(|| a.metadata.version.cmp(&b.metadata.version))
});
all_plugins.dedup_by(|a, b| {
a.metadata.name == b.metadata.name && a.metadata.version == b.metadata.version
});
Ok(all_plugins)
}
pub async fn discover_from_repository(
&self,
repository: &PluginRepository,
) -> Result<Vec<PluginManifest>> {
if let Some(cached) = self.cache.get_repository_plugins(&repository.url) {
if !cached.is_expired() {
return Ok(cached.plugins);
}
}
let plugins = self
.client
.fetch_repository_plugins(repository)
.await
.map_err(|e| {
SklearsError::InvalidOperation(format!(
"Failed to fetch plugins from {}: {}",
repository.name, e
))
})?;
self.cache
.store_repository_plugins(&repository.url, &plugins);
self.search_index.index_plugins(&plugins);
Ok(plugins)
}
pub async fn search(&self, query: &SearchQuery) -> Result<Vec<PluginSearchResult>> {
let mut results = self.search_index.search(query)?;
let target_count = query.limit.unwrap_or(10);
if results.len() < target_count {
for repository in &self.repositories {
if let Ok(remote_results) = self.client.search_repository(repository, query).await {
results.extend(remote_results);
if results.len() >= target_count * 2 {
break; }
}
}
}
if let Some(category) = &query.category {
results.retain(|r| r.category == *category);
}
if !query.capabilities.is_empty() {
results.retain(|r| {
query
.capabilities
.iter()
.all(|cap| r.capabilities.contains(cap))
});
}
if let Some(min_rating) = query.min_rating {
results.retain(|r| r.rating >= min_rating);
}
results.sort_by(|a, b| {
b.relevance_score
.partial_cmp(&a.relevance_score)
.unwrap_or(Ordering::Equal)
.then_with(|| {
b.popularity_score
.partial_cmp(&a.popularity_score)
.unwrap_or(Ordering::Equal)
})
});
Ok(results.into_iter().take(target_count).collect())
}
pub async fn install_plugin(
&self,
plugin_id: &str,
version: Option<&str>,
) -> Result<PluginInstallResult> {
let manifest = self.find_plugin_manifest(plugin_id, version).await?;
let validator = PluginValidator::new();
let dummy_plugin = DummyPlugin::new();
let validation_report = validator.validate_comprehensive(&*dummy_plugin, &manifest)?;
if validation_report.has_errors() {
return Err(SklearsError::InvalidOperation(format!(
"Plugin validation failed: {} errors found",
validation_report.errors.len()
)));
}
let plugin_data = self.client.download_plugin(&manifest).await.map_err(|e| {
SklearsError::InvalidOperation(format!("Failed to download plugin: {}", e))
})?;
let computed_hash = self.compute_content_hash(&plugin_data);
if computed_hash != manifest.content_hash {
return Err(SklearsError::InvalidOperation(
"Plugin content hash verification failed".to_string(),
));
}
let install_path = self.install_plugin_locally(&manifest, &plugin_data)?;
Ok(PluginInstallResult {
manifest,
install_path,
validation_report,
})
}
async fn find_plugin_manifest(
&self,
plugin_id: &str,
version: Option<&str>,
) -> Result<PluginManifest> {
let mut last_error = None;
for repository in &self.repositories {
match self
.client
.get_plugin_manifest(repository, plugin_id, version)
.await
{
Ok(manifest) => return Ok(manifest),
Err(e) => last_error = Some(e),
}
}
Err(SklearsError::InvalidOperation(format!(
"Plugin '{}' not found in any repository. Last error: {:?}",
plugin_id, last_error
)))
}
fn install_plugin_locally(&self, manifest: &PluginManifest, data: &[u8]) -> Result<String> {
let plugin_dir = std::env::temp_dir()
.join("plugins")
.join(&manifest.metadata.name)
.display()
.to_string();
std::fs::create_dir_all(&plugin_dir).map_err(|e| {
SklearsError::InvalidOperation(format!("Failed to create plugin directory: {}", e))
})?;
let plugin_file = format!("{}/plugin.so", plugin_dir);
std::fs::write(&plugin_file, data).map_err(|e| {
SklearsError::InvalidOperation(format!("Failed to write plugin file: {}", e))
})?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&plugin_file, std::fs::Permissions::from_mode(0o755))
.map_err(|e| {
SklearsError::InvalidOperation(format!("Failed to set permissions: {}", e))
})?;
}
let manifest_file = format!("{}/manifest.json", plugin_dir);
let manifest_json = serde_json::to_string_pretty(manifest).map_err(|e| {
SklearsError::InvalidOperation(format!("Failed to serialize manifest: {}", e))
})?;
std::fs::write(manifest_file, manifest_json).map_err(|e| {
SklearsError::InvalidOperation(format!("Failed to write manifest: {}", e))
})?;
Ok(plugin_file)
}
fn compute_content_hash(&self, data: &[u8]) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
data.hash(&mut hasher);
format!("{:x}", hasher.finish())
}
pub async fn get_repository_stats(&self) -> Vec<RepositoryStats> {
let mut stats = Vec::new();
for repository in &self.repositories {
let plugin_count = match self.discover_from_repository(repository).await {
Ok(plugins) => plugins.len(),
Err(_) => 0,
};
stats.push(RepositoryStats {
name: repository.name.clone(),
url: repository.url.clone(),
plugin_count,
verified: repository.verified,
last_updated: std::time::SystemTime::now(), });
}
stats
}
}
impl Default for PluginDiscoveryService {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct PluginMarketplace {
discovery: PluginDiscoveryService,
rating_system: RatingSystem,
review_system: ReviewSystem,
download_tracker: DownloadTracker,
analytics: PluginAnalytics,
}
impl PluginMarketplace {
pub fn new() -> Self {
Self {
discovery: PluginDiscoveryService::new(),
rating_system: RatingSystem::new(),
review_system: ReviewSystem::new(),
download_tracker: DownloadTracker::new(),
analytics: PluginAnalytics::new(),
}
}
pub async fn get_featured_plugins(&self) -> Result<Vec<FeaturedPlugin>> {
let plugins = self.discovery.discover_all().await?;
let mut featured = Vec::new();
for plugin_manifest in plugins {
let plugin_id = plugin_manifest.metadata.name.clone();
let rating = self.rating_system.get_average_rating(&plugin_id).await?;
let download_count = self.download_tracker.get_download_count(&plugin_id).await?;
let review_count = self.review_system.get_review_count(&plugin_id).await?;
let feature_score = self.calculate_feature_score(rating, download_count, review_count);
if feature_score > 7.0 {
featured.push(FeaturedPlugin {
manifest: plugin_manifest,
rating,
download_count,
review_count,
feature_score,
trend_direction: self
.analytics
.get_trend_direction(&plugin_id)
.await
.unwrap_or(TrendDirection::Stable),
});
}
}
featured.sort_by(|a, b| {
b.feature_score
.partial_cmp(&a.feature_score)
.unwrap_or(Ordering::Equal)
});
Ok(featured.into_iter().take(10).collect())
}
pub async fn rate_plugin(&self, plugin_id: &str, user_id: &str, rating: f32) -> Result<()> {
if !(1.0..=5.0).contains(&rating) {
return Err(SklearsError::InvalidOperation(
"Rating must be between 1.0 and 5.0".to_string(),
));
}
self.rating_system
.submit_rating(plugin_id, user_id, rating)
.await?;
self.analytics.track_rating_event(plugin_id, rating).await?;
Ok(())
}
pub async fn submit_review(&self, plugin_id: &str, review: PluginReview) -> Result<()> {
self.review_system.submit_review(plugin_id, review).await?;
self.analytics.track_review_event(plugin_id).await?;
Ok(())
}
pub async fn get_plugin_stats(&self, plugin_id: &str) -> Result<PluginStats> {
let rating = self.rating_system.get_average_rating(plugin_id).await?;
let downloads = self.download_tracker.get_download_count(plugin_id).await?;
let reviews = self.review_system.get_reviews(plugin_id, 0, 5).await?;
let trend = self.analytics.get_trend_data(plugin_id).await?;
let rating_distribution = self
.rating_system
.get_rating_distribution(plugin_id)
.await?;
Ok(PluginStats {
average_rating: rating,
total_downloads: downloads,
recent_reviews: reviews,
trend_data: trend,
rating_distribution,
monthly_downloads: self
.download_tracker
.get_monthly_downloads(plugin_id)
.await?,
last_updated: self.analytics.get_last_update_time(plugin_id).await?,
})
}
pub async fn get_trending_plugins(&self, limit: usize) -> Result<Vec<TrendingPlugin>> {
let all_plugins = self.discovery.discover_all().await?;
let mut trending = Vec::new();
for manifest in all_plugins {
let plugin_id = manifest.metadata.name.clone();
let trend_score = self.analytics.calculate_trend_score(&plugin_id).await?;
if trend_score > 0.5 {
trending.push(TrendingPlugin {
manifest,
trend_score,
recent_downloads: self
.download_tracker
.get_recent_downloads(&plugin_id, 7)
.await?,
velocity: self.analytics.get_download_velocity(&plugin_id).await?,
});
}
}
trending.sort_by(|a, b| {
b.trend_score
.partial_cmp(&a.trend_score)
.unwrap_or(Ordering::Equal)
});
Ok(trending.into_iter().take(limit).collect())
}
pub fn calculate_feature_score(&self, rating: f32, downloads: u64, reviews: u64) -> f32 {
let rating_weight = 0.4;
let download_weight = 0.4;
let review_weight = 0.2;
let normalized_rating = rating * 2.0;
let normalized_downloads = if downloads == 0 {
0.0
} else {
(downloads as f32).log10().min(6.0) / 6.0 * 10.0 };
let normalized_reviews = if reviews == 0 {
0.0
} else {
(reviews as f32).log10().min(3.0) / 3.0 * 10.0 };
normalized_rating * rating_weight
+ normalized_downloads * download_weight
+ normalized_reviews * review_weight
}
pub async fn get_marketplace_summary(&self) -> Result<MarketplaceSummary> {
let total_plugins = self.discovery.discover_all().await?.len();
let total_downloads = self.download_tracker.get_total_downloads().await?;
let active_users = self.rating_system.get_active_user_count().await?;
let trending_categories = self.analytics.get_trending_categories().await?;
Ok(MarketplaceSummary {
total_plugins,
total_downloads,
active_users,
trending_categories,
last_updated: std::time::SystemTime::now(),
})
}
}
impl Default for PluginMarketplace {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PluginRepository {
pub name: String,
pub url: String,
pub verified: bool,
pub priority: u8,
}
impl PluginRepository {
pub fn official() -> Self {
Self {
name: "Official".to_string(),
url: "https://plugins.sklears.rs".to_string(),
verified: true,
priority: 10,
}
}
pub fn community() -> Self {
Self {
name: "Community".to_string(),
url: "https://community.sklears.rs".to_string(),
verified: true,
priority: 5,
}
}
}
#[derive(Debug, Clone)]
pub struct SearchQuery {
pub text: String,
pub category: Option<PluginCategory>,
pub capabilities: Vec<PluginCapability>,
pub limit: Option<usize>,
pub min_rating: Option<f32>,
}
#[derive(Debug, Clone)]
pub struct PluginSearchResult {
pub plugin_id: String,
pub description: String,
pub relevance_score: f32,
pub popularity_score: f32,
pub category: PluginCategory,
pub capabilities: Vec<PluginCapability>,
pub rating: f32,
pub download_count: u64,
}
#[derive(Debug, Clone)]
pub struct PluginInstallResult {
pub manifest: PluginManifest,
pub install_path: String,
pub validation_report: ValidationReport,
}
#[derive(Debug, Clone)]
pub struct FeaturedPlugin {
pub manifest: PluginManifest,
pub rating: f32,
pub download_count: u64,
pub review_count: u64,
pub feature_score: f32,
pub trend_direction: TrendDirection,
}
#[derive(Debug, Clone)]
pub struct PluginReview {
pub user_id: String,
pub rating: f32,
pub title: String,
pub content: String,
pub verified_download: bool,
}
#[derive(Debug, Clone)]
pub struct PluginStats {
pub average_rating: f32,
pub total_downloads: u64,
pub recent_reviews: Vec<PluginReview>,
pub trend_data: Vec<f32>,
pub rating_distribution: HashMap<u8, u64>,
pub monthly_downloads: Vec<u64>,
pub last_updated: std::time::SystemTime,
}
#[derive(Debug, Clone)]
pub struct TrendingPlugin {
pub manifest: PluginManifest,
pub trend_score: f32,
pub recent_downloads: u64,
pub velocity: f32,
}
#[derive(Debug, Clone)]
pub struct RepositoryStats {
pub name: String,
pub url: String,
pub plugin_count: usize,
pub verified: bool,
pub last_updated: std::time::SystemTime,
}
#[derive(Debug, Clone)]
pub struct MarketplaceSummary {
pub total_plugins: usize,
pub total_downloads: u64,
pub active_users: u64,
pub trending_categories: Vec<PluginCategory>,
pub last_updated: std::time::SystemTime,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TrendDirection {
Rising,
Stable,
Declining,
}
#[derive(Debug, Clone)]
pub struct MarketplaceInfo {
pub tags: Vec<String>,
pub license: String,
pub repository_url: Option<String>,
pub documentation_url: Option<String>,
pub pricing: PricingInfo,
pub support_level: SupportLevel,
}
#[derive(Debug, Clone)]
pub enum PricingInfo {
Free,
OneTime(f64),
Subscription(f64, SubscriptionPeriod),
UsageBased(f64, UsageUnit),
}
#[derive(Debug, Clone)]
pub enum SubscriptionPeriod {
Monthly,
Yearly,
}
#[derive(Debug, Clone)]
pub enum UsageUnit {
PerPrediction,
PerDataPoint,
PerHour,
}
#[derive(Debug, Clone)]
pub enum SupportLevel {
Community,
Basic,
Professional,
Enterprise,
}
#[derive(Debug, Clone)]
pub struct PluginCache;
impl Default for PluginCache {
fn default() -> Self {
Self::new()
}
}
impl PluginCache {
pub fn new() -> Self {
Self
}
pub fn get_repository_plugins(&self, _url: &str) -> Option<CachedPlugins> {
None
}
pub fn store_repository_plugins(&self, _url: &str, _plugins: &[PluginManifest]) {}
}
#[derive(Debug, Clone)]
pub struct SearchIndex;
impl Default for SearchIndex {
fn default() -> Self {
Self::new()
}
}
impl SearchIndex {
pub fn new() -> Self {
Self
}
pub fn index_plugins(&self, _plugins: &[PluginManifest]) {}
pub fn search(&self, _query: &SearchQuery) -> Result<Vec<PluginSearchResult>> {
Ok(Vec::new())
}
}
#[derive(Debug, Clone)]
pub struct NetworkClient;
impl Default for NetworkClient {
fn default() -> Self {
Self::new()
}
}
impl NetworkClient {
pub fn new() -> Self {
Self
}
pub async fn fetch_repository_plugins(
&self,
_repo: &PluginRepository,
) -> Result<Vec<PluginManifest>> {
Ok(Vec::new())
}
pub async fn search_repository(
&self,
_repo: &PluginRepository,
_query: &SearchQuery,
) -> Result<Vec<PluginSearchResult>> {
Ok(Vec::new())
}
pub async fn download_plugin(&self, _manifest: &PluginManifest) -> Result<Vec<u8>> {
Ok(Vec::new())
}
pub async fn get_plugin_manifest(
&self,
_repo: &PluginRepository,
_id: &str,
_version: Option<&str>,
) -> Result<PluginManifest> {
Err(SklearsError::InvalidOperation("Not found".to_string()))
}
}
#[derive(Debug, Clone)]
pub struct RatingSystem;
impl Default for RatingSystem {
fn default() -> Self {
Self::new()
}
}
impl RatingSystem {
pub fn new() -> Self {
Self
}
pub async fn get_average_rating(&self, _plugin_id: &str) -> Result<f32> {
Ok(4.5)
}
pub async fn submit_rating(
&self,
_plugin_id: &str,
_user_id: &str,
_rating: f32,
) -> Result<()> {
Ok(())
}
pub async fn get_rating_distribution(&self, _plugin_id: &str) -> Result<HashMap<u8, u64>> {
Ok(HashMap::new())
}
pub async fn get_active_user_count(&self) -> Result<u64> {
Ok(1000)
}
}
#[derive(Debug, Clone)]
pub struct ReviewSystem;
impl Default for ReviewSystem {
fn default() -> Self {
Self::new()
}
}
impl ReviewSystem {
pub fn new() -> Self {
Self
}
pub async fn get_review_count(&self, _plugin_id: &str) -> Result<u64> {
Ok(100)
}
pub async fn submit_review(&self, _plugin_id: &str, _review: PluginReview) -> Result<()> {
Ok(())
}
pub async fn get_reviews(
&self,
_plugin_id: &str,
_offset: usize,
_limit: usize,
) -> Result<Vec<PluginReview>> {
Ok(Vec::new())
}
}
#[derive(Debug, Clone)]
pub struct DownloadTracker;
impl Default for DownloadTracker {
fn default() -> Self {
Self::new()
}
}
impl DownloadTracker {
pub fn new() -> Self {
Self
}
pub async fn get_download_count(&self, _plugin_id: &str) -> Result<u64> {
Ok(1000)
}
pub async fn get_monthly_downloads(&self, _plugin_id: &str) -> Result<Vec<u64>> {
Ok(vec![100, 120, 150, 180])
}
pub async fn get_recent_downloads(&self, _plugin_id: &str, _days: u32) -> Result<u64> {
Ok(50)
}
pub async fn get_total_downloads(&self) -> Result<u64> {
Ok(1000000)
}
}
#[derive(Debug, Clone)]
pub struct PluginAnalytics;
impl Default for PluginAnalytics {
fn default() -> Self {
Self::new()
}
}
impl PluginAnalytics {
pub fn new() -> Self {
Self
}
pub async fn track_rating_event(&self, _plugin_id: &str, _rating: f32) -> Result<()> {
Ok(())
}
pub async fn track_review_event(&self, _plugin_id: &str) -> Result<()> {
Ok(())
}
pub async fn get_trend_data(&self, _plugin_id: &str) -> Result<Vec<f32>> {
Ok(vec![1.0, 1.2, 1.5, 1.8])
}
pub async fn get_trend_direction(&self, _plugin_id: &str) -> Result<TrendDirection> {
Ok(TrendDirection::Stable)
}
pub async fn calculate_trend_score(&self, _plugin_id: &str) -> Result<f32> {
Ok(0.7)
}
pub async fn get_download_velocity(&self, _plugin_id: &str) -> Result<f32> {
Ok(5.0)
}
pub async fn get_last_update_time(&self, _plugin_id: &str) -> Result<std::time::SystemTime> {
Ok(std::time::SystemTime::now())
}
pub async fn get_trending_categories(&self) -> Result<Vec<PluginCategory>> {
Ok(vec![PluginCategory::Algorithm, PluginCategory::Transformer])
}
}
#[derive(Debug, Clone)]
pub struct CachedPlugins {
pub plugins: Vec<PluginManifest>,
}
impl CachedPlugins {
pub fn is_expired(&self) -> bool {
false }
}
#[derive(Debug)]
pub struct DummyPlugin;
impl DummyPlugin {
#[allow(clippy::new_ret_no_self)]
pub fn new() -> Box<dyn Plugin> {
Box::new(Self)
}
}
impl Plugin for DummyPlugin {
fn id(&self) -> &str {
"dummy"
}
fn metadata(&self) -> PluginMetadata {
PluginMetadata::default()
}
fn initialize(&mut self, _config: &super::types_config::PluginConfig) -> Result<()> {
Ok(())
}
fn is_compatible(&self, _input_type: std::any::TypeId) -> bool {
true
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn validate_config(&self, _config: &super::types_config::PluginConfig) -> Result<()> {
Ok(())
}
fn cleanup(&mut self) -> Result<()> {
Ok(())
}
}