secra_plugins 0.1.32

生产级插件系统 - 插件的生命周期
Documentation
//! 查询插件相关方法

use crate::error::{PluginManagerError, PluginManagerResult};
use super::types::PluginInfo;
use super::validation::sanitize_plugin_name;
use secra_pluginctl as pluginctl;
use std::collections::HashMap;
use std::path::Path;
use tracing::{debug, error, info, trace, warn};

/// 根据插件ID查找对应的插件文件路径
///
/// 在插件目录中查找与指定插件ID和名称匹配的插件文件(.spk 格式)。
/// 使用两种策略:首先尝试直接文件名匹配(性能优化),如果失败则扫描目录。
///
/// # 参数
/// * `plugin_dir` - 插件文件所在的目录路径
/// * `plugin_id` - 要查找的插件ID(package_id)
/// * `plugin_name` - 要查找的插件名称
///
/// # 返回值
/// * `PluginManagerResult<String>` - 成功时返回插件文件的完整路径
///
/// # 错误
/// * `PluginManagerError::NotFound` - 如果未找到匹配的插件文件
/// * `PluginManagerError::ValidationFailed` - 如果插件ID或名称验证失败
/// * `PluginManagerError::Io` - 如果目录读取失败
///
/// # 查找策略
/// 1. **直接匹配**:尝试构造可能的文件名(`{name}.spk` 或 `{id}.spk`),如果文件存在则直接返回
/// 2. **目录扫描**:如果直接匹配失败,扫描目录中的所有 `.spk` 文件,加载每个文件的元数据进行比较
///
/// # 匹配规则
/// * 优先匹配 `package_id`(插件ID)
/// * 如果 `package_id` 不匹配,则尝试匹配 `name`(插件名称)
///
/// # 性能
/// 直接匹配策略避免了不必要的文件扫描和元数据加载,提高了查找性能。
///
/// # 示例
/// ```no_run
/// # async fn example() {
/// let plugin_file = find_plugin_file_by_id(
///     "/opt/secra/plugins",
///     "my-plugin-id",
///     "my-plugin",
/// ).await?;
/// println!("找到插件文件: {}", plugin_file);
/// # }
/// ```
pub async fn find_plugin_file_by_id(
    plugin_dir: &str,
    plugin_id: &str,
    plugin_name: &str,
) -> PluginManagerResult<String> {
    trace!("查找插件文件 - ID: {}, 名称: {}, 目录: {}", plugin_id, plugin_name, plugin_dir);
    
    // 清理插件名称,用于文件名匹配
    let sanitized_name = sanitize_plugin_name(plugin_name)?;
    let sanitized_plugin_id = sanitize_plugin_name(plugin_id)?;
    debug!("清理后的名称: {} -> {}, ID: {} -> {}", 
           plugin_name, sanitized_name, plugin_id, sanitized_plugin_id);

    // 策略1:尝试直接构造可能的文件名(性能优化)
    let possible_names = vec![
        format!("{}.spk", sanitized_name),
        format!("{}.spk", sanitized_plugin_id),
    ];
    debug!("尝试直接匹配文件名: {:?}", possible_names);

    for possible_name in &possible_names {
        let possible_path = Path::new(plugin_dir).join(possible_name);
        trace!("检查文件路径: {}", possible_path.display());
        
        if possible_path.exists() && possible_path.is_file() {
            if let Some(path_str) = possible_path.to_str() {
                info!("找到插件文件(直接匹配): {}", path_str);
                debug!("使用直接匹配策略找到文件");
                return Ok(path_str.to_string());
            }
        }
    }

    // 策略2:扫描目录查找匹配的插件文件
    info!("未找到直接匹配的插件文件,开始扫描目录...");
    debug!("策略2: 扫描目录查找匹配的插件文件");
    let mut entries = tokio::fs::read_dir(plugin_dir).await?;
    let mut scanned_count = 0;
    
    while let Some(entry) = entries.next_entry().await? {
        let path = entry.path();
        if path.is_file() {
            if let Some(extension) = path.extension() {
                if extension == "spk" {
                    scanned_count += 1;
                    trace!("检查插件文件: {}", path.display());
                    
                    if let Some(plugin_file) = path.to_str() {
                        // 尝试快速加载插件包头部信息
                        match pluginctl::PluginArtifact::load_from_file(plugin_file) {
                            Ok(artifact) => {
                                let package_id_str = artifact.header.metadata.package_id.to_string();
                                let package_id_match = package_id_str == sanitized_plugin_id;
                                
                                if !package_id_match {
                                    if artifact.header.metadata.name == plugin_name {
                                        info!("找到插件文件(名称匹配): {}", plugin_file);
                                        debug!("通过名称匹配找到文件,扫描了 {} 个文件", scanned_count);
                                        return Ok(plugin_file.to_string());
                                    }
                                    continue;
                                }
                                
                                info!("找到插件文件(扫描匹配): {}", plugin_file);
                                debug!("通过ID匹配找到文件,扫描了 {} 个文件", scanned_count);
                                return Ok(plugin_file.to_string());
                            }
                            Err(e) => {
                                warn!("无法加载插件包头部信息 {}: {},跳过", plugin_file, e);
                                trace!("跳过无效的插件文件: {}", plugin_file);
                                continue;
                            }
                        }
                    }
                }
            }
        }
    }

    // 如果都找不到,返回错误
    error!("未找到插件文件 - ID: {}, 名称: {}, 扫描了 {} 个文件", 
           plugin_id, plugin_name, scanned_count);
    Err(PluginManagerError::NotFound(
        format!("未找到插件 {} 对应的插件文件", plugin_id)
    ))
}