Files
qiming/qiming-mcp-proxy/document-parser/src/utils/environment_manager.rs
2026-06-01 13:03:20 +08:00

4922 lines
173 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crate::error::AppError;
use anyhow::Result;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use tokio::process::Command;
use tokio::sync::{Mutex, RwLock, mpsc};
use tokio::time::{sleep, timeout};
use tracing::{debug, error, info, instrument, warn};
/// 环境管理器
#[derive(Debug, Clone)]
pub struct EnvironmentManager {
python_path: String,
base_dir: String,
progress_sender: Option<Arc<Mutex<mpsc::UnboundedSender<InstallProgress>>>>,
timeout_duration: Duration,
retry_config: RetryConfig,
environment_cache: Arc<RwLock<Option<EnvironmentStatus>>>,
cache_ttl: Duration,
}
/// 重试配置
#[derive(Debug, Clone)]
pub struct RetryConfig {
pub max_attempts: u32,
pub base_delay: Duration,
pub max_delay: Duration,
pub backoff_multiplier: f64,
}
/// 环境检查结果
#[derive(Debug, Clone)]
pub struct EnvironmentStatus {
pub python_available: bool,
pub python_version: Option<String>,
pub python_path: Option<String>,
pub uv_available: bool,
pub uv_version: Option<String>,
pub cuda_available: bool,
pub cuda_version: Option<String>,
pub cuda_devices: Vec<CudaDevice>,
pub mineru_available: bool,
pub mineru_version: Option<String>,
pub markitdown_available: bool,
pub markitdown_version: Option<String>,
pub virtual_env_active: bool,
pub virtual_env_path: Option<String>,
pub issues: Vec<EnvironmentIssue>,
pub warnings: Vec<EnvironmentWarning>,
pub last_checked: std::time::SystemTime,
pub check_duration: Duration,
}
/// 环境问题详情
#[derive(Debug, Clone)]
pub struct EnvironmentIssue {
pub component: String,
pub severity: IssueSeverity,
pub message: String,
pub suggestion: String,
pub auto_fixable: bool,
}
/// 环境警告详情
#[derive(Debug, Clone)]
pub struct EnvironmentWarning {
pub component: String,
pub message: String,
pub impact: String,
}
/// 问题严重程度
#[derive(Debug, Clone, PartialEq)]
pub enum IssueSeverity {
Critical,
High,
Medium,
Low,
}
/// CUDA设备信息
#[derive(Debug, Clone)]
pub struct CudaDevice {
pub id: u32,
pub name: String,
pub memory_total: u64,
pub memory_free: u64,
pub compute_capability: String,
}
/// 依赖安装进度
#[derive(Debug, Clone)]
pub struct InstallProgress {
pub package: String,
pub stage: InstallStage,
pub progress: f32,
pub message: String,
pub estimated_time_remaining: Option<Duration>,
pub bytes_downloaded: Option<u64>,
pub total_bytes: Option<u64>,
}
/// 安装阶段
#[derive(Debug, Clone)]
pub enum InstallStage {
Preparing,
Downloading,
Installing,
Configuring,
Verifying,
Completed,
Failed(String),
Retrying { attempt: u32, max_attempts: u32 },
}
/// Python环境信息
#[derive(Debug)]
struct PythonInfo {
version: Option<String>,
path: String,
virtual_env_active: bool,
virtual_env_path: Option<String>,
}
/// uv工具信息
#[derive(Debug)]
struct UvInfo {
version: String,
}
/// CUDA环境信息
#[derive(Debug)]
pub struct CudaInfo {
pub available: bool,
pub version: Option<String>,
pub devices: Vec<CudaDevice>,
}
/// Python包信息
#[derive(Debug, Clone)]
pub struct PackageInfo {
pub version: String,
}
/// 包版本兼容性信息
#[derive(Debug, Clone)]
pub struct PackageCompatibility {
pub package_name: String,
pub current_version: String,
pub minimum_version: String,
pub recommended_version: Option<String>,
pub is_compatible: bool,
pub compatibility_issues: Vec<String>,
pub upgrade_available: bool,
pub upgrade_recommendation: Option<String>,
}
/// 依赖验证结果
#[derive(Debug, Clone)]
pub struct DependencyVerificationResult {
pub mineru_status: DependencyStatus,
pub markitdown_status: DependencyStatus,
pub overall_compatible: bool,
pub recommendations: Vec<String>,
pub critical_issues: Vec<String>,
}
/// 依赖状态
#[derive(Debug, Clone)]
pub struct DependencyStatus {
pub package_name: String,
pub is_available: bool,
pub is_functional: bool,
pub version_info: Option<PackageInfo>,
pub compatibility: Option<PackageCompatibility>,
pub issues: Vec<String>,
pub path: Option<String>,
}
/// 虚拟环境状态详细信息
#[derive(Debug, Clone)]
pub struct VirtualEnvStatus {
pub is_active: bool,
pub path: Option<String>,
pub expected_path: Option<String>,
pub python_executable: Option<String>,
pub is_properly_configured: bool,
pub activation_command: String,
}
/// 虚拟环境详细信息(跨平台)
#[derive(Debug, Clone)]
pub struct VirtualEnvInfo {
pub path: std::path::PathBuf,
pub python_executable: std::path::PathBuf,
pub pip_executable: std::path::PathBuf,
pub activation_script: std::path::PathBuf,
pub is_valid: bool,
pub platform: String,
}
/// 诊断报告
#[derive(Debug, Clone)]
pub struct DiagnosticReport {
pub overall_status: String,
pub health_score: u8,
pub components: Vec<ComponentDiagnostic>,
pub recommendations: Vec<String>,
pub next_steps: Vec<String>,
}
/// 组件诊断信息
#[derive(Debug, Clone)]
pub struct ComponentDiagnostic {
pub name: String,
pub status: String,
pub version: Option<String>,
pub path: Option<String>,
pub issues: Vec<String>,
pub details: String,
}
/// UV工具可用性状态
#[derive(Debug, Clone)]
pub enum UvAvailabilityStatus {
/// UV可用且版本兼容
Available {
version: String,
compatibility: UvVersionCompatibility,
},
/// UV已安装但版本不兼容
IncompatibleVersion { version: String, issue: String },
/// UV命令执行失败
ExecutionFailed { error: String },
/// UV未安装
NotInstalled { error: String },
}
/// UV版本兼容性信息
#[derive(Debug, Clone)]
pub struct UvVersionCompatibility {
pub is_compatible: bool,
pub minimum_version: String,
pub current_version: String,
pub recommendation: Option<String>,
}
/// UV安装方法
#[derive(Debug, Clone)]
pub enum UvInstallationMethod {
/// 使用curl脚本安装推荐
CurlScript,
/// 使用PowerShell脚本安装Windows
PowerShellScript,
/// 使用pip安装
PipInstall,
/// 使用系统包管理器
SystemPackageManager,
}
/// 目录验证结果
#[derive(Debug, Clone)]
pub struct DirectoryValidationResult {
pub is_valid: bool,
pub current_directory: std::path::PathBuf,
pub venv_path: std::path::PathBuf,
pub issues: Vec<DirectoryValidationIssue>,
pub warnings: Vec<DirectoryValidationWarning>,
pub cleanup_options: Vec<CleanupOption>,
pub recommendations: Vec<String>,
}
/// 目录验证问题
#[derive(Debug, Clone)]
pub struct DirectoryValidationIssue {
pub issue_type: DirectoryIssueType,
pub message: String,
pub severity: ValidationSeverity,
pub auto_fixable: bool,
pub fix_suggestion: String,
}
/// 目录验证警告
#[derive(Debug, Clone)]
pub struct DirectoryValidationWarning {
pub warning_type: DirectoryWarningType,
pub message: String,
pub impact: String,
}
/// 清理选项
#[derive(Debug, Clone)]
pub struct CleanupOption {
pub option_type: CleanupType,
pub description: String,
pub risk_level: CleanupRisk,
pub command: String,
}
/// 目录问题类型
#[derive(Debug, Clone, PartialEq)]
pub enum DirectoryIssueType {
PermissionDenied,
InsufficientSpace,
PathConflict,
PathTooLong,
}
/// 目录警告类型
#[derive(Debug, Clone, PartialEq)]
pub enum DirectoryWarningType {
ExistingVenv,
CorruptedVenv,
PathWithSpaces,
}
/// 验证严重程度
#[derive(Debug, Clone, PartialEq)]
pub enum ValidationSeverity {
Critical,
High,
Medium,
Low,
}
/// 清理类型
#[derive(Debug, Clone, PartialEq)]
pub enum CleanupType {
RemoveConflictingFile,
RemoveCorruptedVenv,
CreateBackup,
}
/// 清理风险级别
#[derive(Debug, Clone, PartialEq)]
pub enum CleanupRisk {
Low,
Medium,
High,
}
impl Default for EnvironmentStatus {
fn default() -> Self {
Self {
python_available: false,
python_version: None,
python_path: None,
uv_available: false,
uv_version: None,
cuda_available: false,
cuda_version: None,
cuda_devices: Vec::new(),
mineru_available: false,
mineru_version: None,
markitdown_available: false,
markitdown_version: None,
virtual_env_active: false,
virtual_env_path: None,
issues: Vec::new(),
warnings: Vec::new(),
last_checked: std::time::SystemTime::now(),
check_duration: Duration::from_secs(0),
}
}
}
impl Default for RetryConfig {
fn default() -> Self {
Self {
max_attempts: 3,
base_delay: Duration::from_millis(500),
max_delay: Duration::from_secs(30),
backoff_multiplier: 2.0,
}
}
}
impl EnvironmentStatus {
/// 检查环境是否就绪
pub fn is_ready(&self) -> bool {
self.python_available && self.mineru_available && self.markitdown_available
}
/// 获取问题列表
pub fn get_issues(&self) -> &Vec<EnvironmentIssue> {
&self.issues
}
/// 获取警告列表
pub fn get_warnings(&self) -> &Vec<EnvironmentWarning> {
&self.warnings
}
/// 获取关键问题(阻止系统运行的问题)
pub fn get_critical_issues(&self) -> Vec<&EnvironmentIssue> {
self.issues
.iter()
.filter(|issue| issue.severity == IssueSeverity::Critical)
.collect()
}
/// 获取可自动修复的问题
pub fn get_auto_fixable_issues(&self) -> Vec<&EnvironmentIssue> {
self.issues
.iter()
.filter(|issue| issue.auto_fixable)
.collect()
}
/// 检查是否有CUDA支持
pub fn has_cuda_support(&self) -> bool {
self.cuda_available && !self.cuda_devices.is_empty()
}
/// 获取推荐的CUDA设备
pub fn get_recommended_cuda_device(&self) -> Option<&CudaDevice> {
self.cuda_devices
.iter()
.max_by_key(|device| device.memory_free)
}
/// 检查缓存是否过期
pub fn is_cache_expired(&self, ttl: Duration) -> bool {
self.last_checked.elapsed().unwrap_or(Duration::MAX) > ttl
}
/// 获取虚拟环境状态详细信息
pub fn get_virtual_env_status(&self) -> VirtualEnvStatus {
VirtualEnvStatus {
is_active: self.virtual_env_active,
path: self.virtual_env_path.clone(),
expected_path: Some("./venv".to_string()),
python_executable: self.python_path.clone(),
is_properly_configured: self.is_virtual_env_properly_configured(),
activation_command: self.get_activation_command(),
}
}
/// 检查虚拟环境是否正确配置
pub fn is_virtual_env_properly_configured(&self) -> bool {
if !self.virtual_env_active {
return false;
}
// 检查虚拟环境路径是否符合预期当前目录下的venv
if let Some(ref venv_path) = self.virtual_env_path {
// 检查路径是否以当前目录的venv结尾
venv_path.ends_with("venv")
|| venv_path.contains("/venv")
|| venv_path.contains("\\venv")
} else {
false
}
}
/// 获取虚拟环境激活命令
pub fn get_activation_command(&self) -> String {
if cfg!(windows) {
// Windows supports both batch and PowerShell activation
".\\venv\\Scripts\\activate.bat".to_string()
} else {
"source ./venv/bin/activate".to_string()
}
}
/// 获取虚拟环境激活命令PowerShell版本仅Windows
pub fn get_powershell_activation_command(&self) -> Option<String> {
if cfg!(windows) {
Some(".\\venv\\Scripts\\Activate.ps1".to_string())
} else {
None
}
}
/// 生成详细的诊断报告
pub fn generate_diagnostic_report(&self) -> DiagnosticReport {
let mut report = DiagnosticReport {
overall_status: if self.is_ready() {
"Ready"
} else {
"Not Ready"
}
.to_string(),
health_score: self.health_score(),
components: Vec::new(),
recommendations: Vec::new(),
next_steps: Vec::new(),
};
// Python组件诊断
let python_component = ComponentDiagnostic {
name: "Python".to_string(),
status: if self.python_available {
"Available"
} else {
"Missing"
}
.to_string(),
version: self.python_version.clone(),
path: self.python_path.clone(),
issues: self.get_component_issues("Python"),
details: if self.python_available {
format!(
"Python {} is available at {:?}",
self.python_version.as_deref().unwrap_or("unknown"),
self.python_path.as_deref().unwrap_or("unknown")
)
} else {
"Python is not available or not properly configured".to_string()
},
};
report.components.push(python_component);
// 虚拟环境组件诊断
let venv_status = self.get_virtual_env_status();
let venv_component = ComponentDiagnostic {
name: "Virtual Environment".to_string(),
status: if venv_status.is_active {
"Active"
} else {
"Inactive"
}
.to_string(),
version: None,
path: venv_status.path.clone(),
issues: self.get_component_issues("Virtual Environment"),
details: if venv_status.is_active {
format!(
"Virtual environment is active at {:?}",
venv_status.path.as_deref().unwrap_or("unknown")
)
} else {
format!(
"Virtual environment is not active. Expected at ./venv. Use: {}",
venv_status.activation_command
)
},
};
report.components.push(venv_component);
// UV工具诊断
let uv_component = ComponentDiagnostic {
name: "UV Tool".to_string(),
status: if self.uv_available {
"Available"
} else {
"Missing"
}
.to_string(),
version: self.uv_version.clone(),
path: None,
issues: self.get_component_issues("UV"),
details: if self.uv_available {
format!(
"UV {} is available",
self.uv_version.as_deref().unwrap_or("unknown")
)
} else {
"UV tool is not installed. Install with: curl -LsSf https://astral.sh/uv/install.sh | sh".to_string()
},
};
report.components.push(uv_component);
// MinerU组件诊断
let mineru_component = ComponentDiagnostic {
name: "MinerU".to_string(),
status: if self.mineru_available {
"Available"
} else {
"Missing"
}
.to_string(),
version: self.mineru_version.clone(),
path: None,
issues: self.get_component_issues("MinerU"),
details: if self.mineru_available {
format!(
"MinerU {} is available",
self.mineru_version.as_deref().unwrap_or("unknown")
)
} else {
"MinerU is not installed. Install with: uv pip install magic-pdf[full]".to_string()
},
};
report.components.push(mineru_component);
// MarkItDown组件诊断
let markitdown_component = ComponentDiagnostic {
name: "MarkItDown".to_string(),
status: if self.markitdown_available {
"Available"
} else {
"Missing"
}
.to_string(),
version: self.markitdown_version.clone(),
path: None,
issues: self.get_component_issues("MarkItDown"),
details: if self.markitdown_available {
format!(
"MarkItDown {} is available",
self.markitdown_version.as_deref().unwrap_or("unknown")
)
} else {
"MarkItDown is not installed. Install with: uv pip install markitdown".to_string()
},
};
report.components.push(markitdown_component);
// CUDA组件诊断可选
let cuda_component = ComponentDiagnostic {
name: "CUDA".to_string(),
status: if self.cuda_available {
"Available"
} else {
"Not Available"
}
.to_string(),
version: self.cuda_version.clone(),
path: None,
issues: self.get_component_issues("CUDA"),
details: if self.cuda_available {
format!(
"CUDA {} is available with {} device(s)",
self.cuda_version.as_deref().unwrap_or("unknown"),
self.cuda_devices.len()
)
} else {
"CUDA is not available. GPU acceleration will not be used.".to_string()
},
};
report.components.push(cuda_component);
// 生成推荐和下一步操作
self.generate_recommendations(&mut report);
report
}
/// 获取特定组件的问题
fn get_component_issues(&self, component_name: &str) -> Vec<String> {
self.issues
.iter()
.filter(|issue| issue.component == component_name)
.map(|issue| format!("{}: {}", issue.message, issue.suggestion))
.collect()
}
/// 生成推荐和下一步操作
fn generate_recommendations(&self, report: &mut DiagnosticReport) {
// 基于当前状态生成推荐
if !self.python_available {
report
.recommendations
.push("Install Python 3.8+ to enable document parsing functionality".to_string());
report
.next_steps
.push("1. Install Python 3.8 or higher".to_string());
}
if !self.virtual_env_active {
report.recommendations.push(
"Create and activate a virtual environment for isolated dependency management"
.to_string(),
);
report
.next_steps
.push("2. Run 'document-parser uv-init' to set up the environment".to_string());
}
if !self.uv_available {
report
.recommendations
.push("Install UV tool for fast Python package management".to_string());
if !report
.next_steps
.iter()
.any(|step| step.contains("uv-init"))
{
report.next_steps.push(
"2. Run 'document-parser uv-init' to install UV and set up dependencies"
.to_string(),
);
}
}
if !self.mineru_available {
report
.recommendations
.push("Install MinerU for PDF document parsing capabilities".to_string());
if !report
.next_steps
.iter()
.any(|step| step.contains("uv-init"))
{
report
.next_steps
.push("3. Install MinerU with: uv pip install magic-pdf[full]".to_string());
}
}
if !self.markitdown_available {
report
.recommendations
.push("Install MarkItDown for multi-format document parsing".to_string());
if !report
.next_steps
.iter()
.any(|step| step.contains("uv-init"))
{
report
.next_steps
.push("4. Install MarkItDown with: uv pip install markitdown".to_string());
}
}
if self.is_ready() {
report.recommendations.push(
"Environment is ready! You can start the document parsing server".to_string(),
);
report
.next_steps
.push("Run 'document-parser server' to start the service".to_string());
}
// 添加CUDA相关推荐
if !self.cuda_available && self.python_available {
report.recommendations.push(
"Consider installing CUDA for improved PDF processing performance".to_string(),
);
}
// 添加虚拟环境配置推荐
if self.virtual_env_active && !self.is_virtual_env_properly_configured() {
report.recommendations.push(
"Virtual environment detected but may not be in the expected location (./venv)"
.to_string(),
);
}
}
/// 格式化诊断报告为可读字符串
pub fn format_diagnostic_report(&self) -> String {
let report = self.generate_diagnostic_report();
let mut output = String::new();
output.push_str("=== Environment Diagnostic Report ===\n");
output.push_str(&format!("Overall Status: {}\n", report.overall_status));
output.push_str(&format!("Health Score: {}/100\n\n", report.health_score));
output.push_str("=== Components ===\n");
for component in &report.components {
output.push_str(&format!("{}: {} ", component.name, component.status));
if let Some(ref version) = component.version {
output.push_str(&format!("({version})"));
}
output.push('\n');
if let Some(ref path) = component.path {
output.push_str(&format!(" Path: {path}\n"));
}
output.push_str(&format!(" Details: {}\n", component.details));
if !component.issues.is_empty() {
output.push_str(" Issues:\n");
for issue in &component.issues {
output.push_str(&format!(" - {issue}\n"));
}
}
output.push('\n');
}
if !report.recommendations.is_empty() {
output.push_str("=== Recommendations ===\n");
for (i, recommendation) in report.recommendations.iter().enumerate() {
output.push_str(&format!("{}. {}\n", i + 1, recommendation));
}
output.push('\n');
}
if !report.next_steps.is_empty() {
output.push_str("=== Next Steps ===\n");
for step in &report.next_steps {
output.push_str(&format!("{step}\n"));
}
}
output
}
/// 生成环境健康评分 (0-100)
pub fn health_score(&self) -> u8 {
let mut score = 0u8;
// 基础组件检查 (60分)
if self.python_available {
score += 20;
}
if self.mineru_available {
score += 20;
}
if self.markitdown_available {
score += 20;
}
// 工具支持 (20分)
if self.uv_available {
score += 10;
}
if self.virtual_env_active {
score += 10;
}
// CUDA支持 (10分)
if self.has_cuda_support() {
score += 10;
}
// 扣除问题分数 (最多扣30分)
let issue_penalty = self
.issues
.iter()
.map(|issue| match issue.severity {
IssueSeverity::Critical => 10,
IssueSeverity::High => 5,
IssueSeverity::Medium => 2,
IssueSeverity::Low => 1,
})
.sum::<u8>()
.min(30);
score.saturating_sub(issue_penalty)
}
}
impl EnvironmentManager {
/// 创建新的环境管理器
pub fn new(python_path: String, base_dir: String) -> Self {
Self {
python_path,
base_dir,
progress_sender: None,
timeout_duration: Duration::from_secs(300), // 5分钟默认超时
retry_config: RetryConfig::default(),
environment_cache: Arc::new(RwLock::new(None)),
cache_ttl: Duration::from_secs(300), // 5分钟缓存
}
}
/// 为当前目录创建环境管理器(推荐使用)
pub fn for_current_directory() -> Result<Self, AppError> {
let current_dir = std::env::current_dir()
.map_err(|e| AppError::Environment(format!("无法获取当前目录: {e}")))?;
let python_path = Self::get_venv_python_path(&current_dir.join("venv"));
Ok(Self {
python_path: python_path.to_string_lossy().to_string(),
base_dir: current_dir.to_string_lossy().to_string(),
progress_sender: None,
timeout_duration: Duration::from_secs(300), // 5分钟默认超时
retry_config: RetryConfig::default(),
environment_cache: Arc::new(RwLock::new(None)),
cache_ttl: Duration::from_secs(300), // 5分钟缓存
})
}
/// 获取虚拟环境中的Python可执行文件路径跨平台
pub fn get_venv_python_path(venv_path: &Path) -> std::path::PathBuf {
if cfg!(windows) {
// Windows: Scripts/python.exe
venv_path.join("Scripts").join("python.exe")
} else {
// Unix-like: bin/python
venv_path.join("bin").join("python")
}
}
/// 获取虚拟环境中的可执行文件路径(跨平台)
pub fn get_venv_executable_path(venv_path: &Path, executable_name: &str) -> std::path::PathBuf {
if cfg!(windows) {
// Windows: Scripts/{executable}.exe
let exe_name = if executable_name.ends_with(".exe") {
executable_name.to_string()
} else {
format!("{executable_name}.exe")
};
venv_path.join("Scripts").join(exe_name)
} else {
// Unix-like: bin/{executable}
venv_path.join("bin").join(executable_name)
}
}
/// 获取虚拟环境激活脚本路径(跨平台)
pub fn get_venv_activation_script(venv_path: &Path) -> std::path::PathBuf {
if cfg!(windows) {
// Windows: Scripts/activate.bat or Scripts/Activate.ps1
venv_path.join("Scripts").join("activate.bat")
} else {
// Unix-like: bin/activate
venv_path.join("bin").join("activate")
}
}
/// 获取系统Python可执行文件名跨平台
pub fn get_system_python_executable() -> Vec<String> {
if cfg!(windows) {
// Windows: python.exe, python3.exe, py.exe
vec![
"python.exe".to_string(),
"python3.exe".to_string(),
"py.exe".to_string(),
]
} else {
// Unix-like: python3, python
vec!["python3".to_string(), "python".to_string()]
}
}
/// 检查可执行文件是否存在于PATH中跨平台
pub async fn is_executable_in_path(executable: &str) -> bool {
let which_cmd = if cfg!(windows) { "where" } else { "which" };
match Command::new(which_cmd).arg(executable).output().await {
Ok(output) => output.status.success(),
Err(_) => false,
}
}
/// 查找系统中可用的Python可执行文件跨平台
async fn find_system_python(&self) -> Option<String> {
let python_candidates = Self::get_system_python_executable();
for candidate in python_candidates {
if Self::is_executable_in_path(&candidate).await {
debug!("Found system Python: {}", candidate);
return Some(candidate);
}
}
debug!("System Python executable not found");
None
}
/// 测试虚拟环境激活(跨平台)
pub async fn test_virtual_environment_activation(
&self,
venv_path: &Path,
) -> Result<bool, AppError> {
let python_exe = Self::get_venv_python_path(venv_path);
if !python_exe.exists() {
return Ok(false);
}
// 测试Python可执行文件是否工作
let test_cmd = Command::new(&python_exe)
.arg("-c")
.arg("import sys; print('VENV_TEST_SUCCESS'); print(sys.prefix)")
.output();
match timeout(Duration::from_secs(10), test_cmd).await {
Ok(Ok(output)) if output.status.success() => {
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.contains("VENV_TEST_SUCCESS") {
debug!(
"Virtual environment activation test successful: {}",
python_exe.display()
);
Ok(true)
} else {
debug!("Virtual environment activation test failed: Incorrect output");
Ok(false)
}
}
Ok(Ok(output)) => {
let stderr = String::from_utf8_lossy(&output.stderr);
debug!("Virtual environment activation test failed: {}", stderr);
Ok(false)
}
Ok(Err(e)) => {
debug!(
"Virtual environment activation test execution failed: {}",
e
);
Ok(false)
}
Err(_) => {
debug!("Virtual environment activation test timed out");
Ok(false)
}
}
}
/// 获取虚拟环境信息(跨平台)
pub async fn get_virtual_environment_info(
&self,
venv_path: &Path,
) -> Result<VirtualEnvInfo, AppError> {
let python_exe = Self::get_venv_python_path(venv_path);
let activation_script = Self::get_venv_activation_script(venv_path);
let pip_exe = Self::get_venv_executable_path(venv_path, "pip");
let is_valid = self.test_virtual_environment_activation(venv_path).await?;
Ok(VirtualEnvInfo {
path: venv_path.to_path_buf(),
python_executable: python_exe,
pip_executable: pip_exe,
activation_script,
is_valid,
platform: if cfg!(windows) {
"windows".to_string()
} else {
"unix".to_string()
},
})
}
/// 获取跨平台环境变量设置
pub fn get_cross_platform_env_vars(
&self,
venv_path: &Path,
) -> std::collections::HashMap<String, String> {
let mut env_vars = std::collections::HashMap::new();
if cfg!(windows) {
// Windows环境变量
env_vars.insert(
"VIRTUAL_ENV".to_string(),
venv_path.to_string_lossy().to_string(),
);
env_vars.insert(
"PATH".to_string(),
format!(
"{};{}",
venv_path.join("Scripts").to_string_lossy(),
std::env::var("PATH").unwrap_or_default()
),
);
} else {
// Unix-like环境变量
env_vars.insert(
"VIRTUAL_ENV".to_string(),
venv_path.to_string_lossy().to_string(),
);
env_vars.insert(
"PATH".to_string(),
format!(
"{}:{}",
venv_path.join("bin").to_string_lossy(),
std::env::var("PATH").unwrap_or_default()
),
);
}
env_vars
}
/// 创建带进度跟踪的环境管理器
pub fn with_progress_tracking(
python_path: String,
base_dir: String,
progress_sender: mpsc::UnboundedSender<InstallProgress>,
) -> Self {
Self {
python_path,
base_dir,
progress_sender: Some(Arc::new(Mutex::new(progress_sender))),
timeout_duration: Duration::from_secs(300),
retry_config: RetryConfig::default(),
environment_cache: Arc::new(RwLock::new(None)),
cache_ttl: Duration::from_secs(300),
}
}
/// 为当前目录创建带进度跟踪的环境管理器
pub fn for_current_directory_with_progress(
progress_sender: mpsc::UnboundedSender<InstallProgress>,
) -> Result<Self, AppError> {
let current_dir = std::env::current_dir()
.map_err(|e| AppError::Environment(format!("无法获取当前目录: {e}")))?;
let python_path = Self::get_venv_python_path(&current_dir.join("venv"));
Ok(Self {
python_path: python_path.to_string_lossy().to_string(),
base_dir: current_dir.to_string_lossy().to_string(),
progress_sender: Some(Arc::new(Mutex::new(progress_sender))),
timeout_duration: Duration::from_secs(300),
retry_config: RetryConfig::default(),
environment_cache: Arc::new(RwLock::new(None)),
cache_ttl: Duration::from_secs(300),
})
}
/// 添加进度发送器到现有环境管理器
pub fn with_progress_sender(
mut self,
progress_sender: mpsc::UnboundedSender<InstallProgress>,
) -> Self {
self.progress_sender = Some(Arc::new(Mutex::new(progress_sender)));
self
}
/// 设置操作超时时间
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout_duration = timeout;
self
}
/// 设置重试配置
pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self {
self.retry_config = retry_config;
self
}
/// 设置缓存TTL
pub fn with_cache_ttl(mut self, ttl: Duration) -> Self {
self.cache_ttl = ttl;
self
}
/// 检查完整环境状态(带缓存支持)
#[instrument(skip(self))]
pub async fn check_environment(&self) -> Result<EnvironmentStatus, AppError> {
// 检查缓存
if let Some(cached_status) = self.get_cached_status().await {
if !cached_status.is_cache_expired(self.cache_ttl) {
debug!("Using cached environment state");
return Ok(cached_status);
}
}
let start_time = std::time::SystemTime::now();
let mut status = EnvironmentStatus::default();
info!("Start environment check");
// 并行检查各个环境组件
let (python_result, uv_result, cuda_result) = tokio::join!(
self.check_python_environment_with_retry(),
self.check_uv_environment_with_retry(),
self.check_cuda_environment_with_retry()
);
// 处理Python环境检查结果
match python_result {
Ok(python_info) => {
status.python_available = true;
status.python_version = python_info.version.clone();
status.python_path = Some(python_info.path.clone());
status.virtual_env_active = python_info.virtual_env_active;
status.virtual_env_path = python_info.virtual_env_path.clone();
info!(
"Python environment check passed: {:?}",
status.python_version
);
// 增强虚拟环境状态验证
self.validate_virtual_environment_status(&mut status, &python_info);
}
Err(e) => {
let issue = EnvironmentIssue {
component: "Python".to_string(),
severity: IssueSeverity::Critical,
message: format!("Python环境检查失败: {e}"),
suggestion: self.get_python_installation_suggestion(&e.to_string()),
auto_fixable: false,
};
status.issues.push(issue);
error!("Python environment check failed: {}", e);
}
}
// 处理uv工具检查结果
match uv_result {
Ok(uv_info) => {
status.uv_available = true;
status.uv_version = Some(uv_info.version);
info!("UV tool inspection passed: {:?}", status.uv_version);
}
Err(e) => {
let issue = EnvironmentIssue {
component: "UV".to_string(),
severity: IssueSeverity::High,
message: format!("uv工具检查失败: {e}"),
suggestion: self.get_uv_installation_suggestion(&e.to_string()),
auto_fixable: true,
};
status.issues.push(issue);
warn!("uv tool check failed: {}", e);
}
}
// 处理CUDA环境检查结果
match cuda_result {
Ok(cuda_info) => {
status.cuda_available = cuda_info.available;
status.cuda_version = cuda_info.version;
status.cuda_devices = cuda_info.devices;
if status.cuda_available {
info!("CUDA environment check passed: {:?}", status.cuda_version);
} else {
let warning = EnvironmentWarning {
component: "CUDA".to_string(),
message: "CUDA环境不可用".to_string(),
impact: "PDF处理性能可能较慢".to_string(),
};
status.warnings.push(warning);
info!("CUDA environment is not available");
}
}
Err(e) => {
let warning = EnvironmentWarning {
component: "CUDA".to_string(),
message: format!("CUDA环境检查失败: {e}"),
impact: "将使用CPU进行PDF处理".to_string(),
};
status.warnings.push(warning);
warn!("CUDA environment check failed: {}", e);
}
}
// 如果Python可用检查Python包
if status.python_available {
let (mineru_result, markitdown_result) = tokio::join!(
self.check_mineru_environment_with_retry(),
self.check_markitdown_environment_with_retry()
);
match mineru_result {
Ok(mineru_info) => {
status.mineru_available = true;
status.mineru_version = Some(mineru_info.version);
info!(
"MinerU environment check passed: {:?}",
status.mineru_version
);
}
Err(e) => {
let issue = EnvironmentIssue {
component: "MinerU".to_string(),
severity: IssueSeverity::Critical,
message: format!("MinerU环境检查失败: {e}"),
suggestion: self.get_mineru_installation_suggestion(&e.to_string()),
auto_fixable: true,
};
status.issues.push(issue);
warn!("MinerU environment check failed: {}", e);
}
}
match markitdown_result {
Ok(markitdown_info) => {
status.markitdown_available = true;
status.markitdown_version = Some(markitdown_info.version);
info!(
"MarkItDown environment check passed: {:?}",
status.markitdown_version
);
}
Err(e) => {
let issue = EnvironmentIssue {
component: "MarkItDown".to_string(),
severity: IssueSeverity::Critical,
message: format!("MarkItDown环境检查失败: {e}"),
suggestion: self.get_markitdown_installation_suggestion(&e.to_string()),
auto_fixable: true,
};
status.issues.push(issue);
warn!("MarkItDown environment check failed: {}", e);
}
}
} else {
// Python不可用时添加相关问题
let mineru_issue = EnvironmentIssue {
component: "MinerU".to_string(),
severity: IssueSeverity::Critical,
message: "无法检查MinerUPython环境不可用".to_string(),
suggestion: "首先修复Python环境问题".to_string(),
auto_fixable: false,
};
let markitdown_issue = EnvironmentIssue {
component: "MarkItDown".to_string(),
severity: IssueSeverity::Critical,
message: "无法检查MarkItDownPython环境不可用".to_string(),
suggestion: "首先修复Python环境问题".to_string(),
auto_fixable: false,
};
status.issues.push(mineru_issue);
status.issues.push(markitdown_issue);
}
// 设置检查时间和持续时间
status.last_checked = start_time;
status.check_duration = start_time.elapsed().unwrap_or(Duration::from_secs(0));
// 更新缓存
self.update_cache(status.clone()).await;
info!(
"Environmental check completed, status: ready={}, health score: {}/100, time taken: {:?}",
status.is_ready(),
status.health_score(),
status.check_duration
);
Ok(status)
}
/// 获取详细的环境状态报告
pub async fn get_detailed_status_report(&self) -> Result<String, AppError> {
let status = self.check_environment().await?;
Ok(status.format_diagnostic_report())
}
/// 获取增强的依赖验证报告
pub async fn get_enhanced_dependency_report(&self) -> Result<String, AppError> {
let verification_result = self.verify_dependency_compatibility().await?;
let mut report = String::new();
report.push_str("=== 增强依赖验证报告 ===\n\n");
// 总体状态
report.push_str(&format!(
"总体兼容性: {}\n",
if verification_result.overall_compatible {
"✓ 兼容"
} else {
"✗ 不兼容"
}
));
report.push('\n');
// MinerU状态
report.push_str("=== MinerU 状态 ===\n");
let mineru = &verification_result.mineru_status;
report.push_str(&format!(
"可用性: {}\n",
if mineru.is_available {
"✓ 可用"
} else {
"✗ 不可用"
}
));
report.push_str(&format!(
"功能性: {}\n",
if mineru.is_functional {
"✓ 正常"
} else {
"✗ 异常"
}
));
if let Some(ref version_info) = mineru.version_info {
report.push_str(&format!("版本: {}\n", version_info.version));
}
if let Some(ref path) = mineru.path {
report.push_str(&format!("路径: {path}\n"));
}
if let Some(ref compat) = mineru.compatibility {
report.push_str(&format!(
"版本兼容性: {}\n",
if compat.is_compatible {
"✓ 兼容"
} else {
"✗ 不兼容"
}
));
report.push_str(&format!("当前版本: {}\n", compat.current_version));
report.push_str(&format!("最低要求: {}\n", compat.minimum_version));
if !compat.compatibility_issues.is_empty() {
report.push_str("兼容性问题:\n");
for issue in &compat.compatibility_issues {
report.push_str(&format!(" - {issue}\n"));
}
}
if compat.upgrade_available {
if let Some(ref rec) = compat.upgrade_recommendation {
report.push_str(&format!("升级建议: {rec}\n"));
}
}
}
if !mineru.issues.is_empty() {
report.push_str("问题:\n");
for issue in &mineru.issues {
report.push_str(&format!(" - {issue}\n"));
}
}
report.push('\n');
// MarkItDown状态
report.push_str("=== MarkItDown 状态 ===\n");
let markitdown = &verification_result.markitdown_status;
report.push_str(&format!(
"可用性: {}\n",
if markitdown.is_available {
"✓ 可用"
} else {
"✗ 不可用"
}
));
report.push_str(&format!(
"功能性: {}\n",
if markitdown.is_functional {
"✓ 正常"
} else {
"✗ 异常"
}
));
if let Some(ref version_info) = markitdown.version_info {
report.push_str(&format!("版本: {}\n", version_info.version));
}
if let Some(ref path) = markitdown.path {
report.push_str(&format!("路径: {path}\n"));
}
if let Some(ref compat) = markitdown.compatibility {
report.push_str(&format!(
"版本兼容性: {}\n",
if compat.is_compatible {
"✓ 兼容"
} else {
"✗ 不兼容"
}
));
report.push_str(&format!("当前版本: {}\n", compat.current_version));
report.push_str(&format!("最低要求: {}\n", compat.minimum_version));
if !compat.compatibility_issues.is_empty() {
report.push_str("兼容性问题:\n");
for issue in &compat.compatibility_issues {
report.push_str(&format!(" - {issue}\n"));
}
}
if compat.upgrade_available {
if let Some(ref rec) = compat.upgrade_recommendation {
report.push_str(&format!("升级建议: {rec}\n"));
}
}
}
if !markitdown.issues.is_empty() {
report.push_str("问题:\n");
for issue in &markitdown.issues {
report.push_str(&format!(" - {issue}\n"));
}
}
report.push('\n');
// 关键问题
if !verification_result.critical_issues.is_empty() {
report.push_str("=== 关键问题 ===\n");
for issue in &verification_result.critical_issues {
report.push_str(&format!("⚠️ {issue}\n"));
}
report.push('\n');
}
// 推荐操作
if !verification_result.recommendations.is_empty() {
report.push_str("=== 推荐操作 ===\n");
for (i, rec) in verification_result.recommendations.iter().enumerate() {
report.push_str(&format!("{}. {}\n", i + 1, rec));
}
}
Ok(report)
}
/// 检查并报告虚拟环境状态
pub async fn check_virtual_environment_status(&self) -> Result<VirtualEnvStatus, AppError> {
let status = self.check_environment().await?;
Ok(status.get_virtual_env_status())
}
/// 诊断虚拟环境路径问题
pub async fn diagnose_venv_path_issues(&self) -> Vec<String> {
let mut issues = Vec::new();
let venv_path = Path::new(&self.base_dir).join("venv");
let base_dir = Path::new(&self.base_dir);
// 检查基础目录
if !base_dir.exists() {
issues.push(format!("基础目录不存在: {}", base_dir.display()));
} else if !base_dir.is_dir() {
issues.push(format!("基础路径不是目录: {}", base_dir.display()));
} else {
// 检查写入权限
if let Err(e) = self.check_directory_writable(base_dir).await {
issues.push(format!(
"基础目录无写入权限: {} ({})",
base_dir.display(),
e
));
}
}
// 检查虚拟环境路径
if venv_path.exists() {
if !venv_path.is_dir() {
issues.push(format!(
"虚拟环境路径存在但不是目录: {}",
venv_path.display()
));
} else {
// 检查虚拟环境完整性
let python_exe = Self::get_venv_python_path(&venv_path);
if !python_exe.exists() {
issues.push(format!(
"虚拟环境不完整缺少Python可执行文件: {}",
python_exe.display()
));
}
}
}
// 检查路径长度Windows
if cfg!(windows) && venv_path.to_string_lossy().len() > 260 {
issues.push(format!(
"虚拟环境路径过长 ({} 字符)Windows限制为260字符",
venv_path.to_string_lossy().len()
));
}
issues
}
/// 生成虚拟环境问题的恢复建议
pub async fn get_venv_recovery_suggestions(&self) -> Vec<String> {
let mut suggestions = Vec::new();
let issues = self.diagnose_venv_path_issues().await;
if issues.is_empty() {
suggestions.push("虚拟环境路径检查通过,可以尝试创建虚拟环境".to_string());
return suggestions;
}
suggestions.push("检测到以下虚拟环境路径问题:".to_string());
for issue in &issues {
suggestions.push(format!(" - {issue}"));
}
suggestions.push("".to_string());
suggestions.push("建议的解决方案:".to_string());
// 基于问题类型提供具体建议
for issue in &issues {
if issue.contains("不存在") {
suggestions.push("1. 确保在正确的项目目录中运行命令".to_string());
suggestions.push("2. 检查当前工作目录: pwd (Unix) 或 cd (Windows)".to_string());
} else if issue.contains("权限") {
suggestions.push("1. 检查目录权限: ls -la (Unix)".to_string());
suggestions.push("2. 使用管理员权限运行命令".to_string());
if cfg!(unix) {
suggestions.push("3. 修改目录权限: chmod 755 .".to_string());
suggestions.push("4. 修改目录所有者: chown $USER .".to_string());
}
} else if issue.contains("不是目录") {
suggestions
.push("1. 删除同名文件: rm venv (Unix) 或 del venv (Windows)".to_string());
suggestions.push("2. 重新创建虚拟环境".to_string());
} else if issue.contains("不完整") {
suggestions.push("1. 删除损坏的虚拟环境: rm -rf ./venv".to_string());
suggestions.push("2. 重新运行 document-parser uv-init".to_string());
} else if issue.contains("路径过长") {
suggestions.push("1. 移动项目到路径较短的目录".to_string());
suggestions.push("2. 使用较短的目录名称".to_string());
} else if issue.contains("磁盘空间") {
suggestions.push("1. 清理磁盘空间至少保留500MB可用空间".to_string());
suggestions.push("2. 删除不需要的文件和目录".to_string());
}
}
suggestions.push("".to_string());
suggestions.push("如果问题仍然存在,请尝试:".to_string());
suggestions.push("1. 重启终端或命令提示符".to_string());
suggestions.push("2. 检查防病毒软件是否阻止文件操作".to_string());
suggestions.push("3. 在不同的目录中尝试创建虚拟环境".to_string());
suggestions
}
/// 尝试自动修复常见的虚拟环境路径问题
pub async fn auto_fix_venv_path_issues(&self) -> Result<Vec<String>, AppError> {
let mut fixed_issues = Vec::new();
let venv_path = Path::new(&self.base_dir).join("venv");
// 尝试清理损坏的虚拟环境
if venv_path.exists() && !venv_path.is_dir() {
match std::fs::remove_file(&venv_path) {
Ok(_) => {
fixed_issues.push(format!(
"已删除阻碍虚拟环境创建的文件: {}",
venv_path.display()
));
}
Err(e) => {
return Err(AppError::permission_error(
format!("无法删除阻碍文件: {e}"),
&venv_path,
));
}
}
}
// 尝试清理损坏的虚拟环境目录
if venv_path.exists() && venv_path.is_dir() {
let python_exe = Self::get_venv_python_path(&venv_path);
if !python_exe.exists() {
match self.cleanup_corrupted_venv(&venv_path).await {
Ok(_) => {
fixed_issues.push(format!("已清理损坏的虚拟环境: {}", venv_path.display()));
}
Err(e) => {
return Err(e);
}
}
}
}
Ok(fixed_issues)
}
/// 获取缓存的环境状态
async fn get_cached_status(&self) -> Option<EnvironmentStatus> {
self.environment_cache.read().await.clone()
}
/// 更新环境状态缓存
async fn update_cache(&self, status: EnvironmentStatus) {
*self.environment_cache.write().await = Some(status);
}
/// 清除环境状态缓存
pub async fn clear_cache(&self) {
*self.environment_cache.write().await = None;
}
/// 验证虚拟环境状态并添加相关问题和警告
fn validate_virtual_environment_status(
&self,
status: &mut EnvironmentStatus,
python_info: &PythonInfo,
) {
if !python_info.virtual_env_active {
let issue = EnvironmentIssue {
component: "Virtual Environment".to_string(),
severity: IssueSeverity::High,
message: "虚拟环境未激活".to_string(),
suggestion: format!(
"创建并激活虚拟环境: 运行 'document-parser uv-init' 或手动运行 '{}'",
self.get_activation_command()
),
auto_fixable: true,
};
status.issues.push(issue);
} else {
// 检查虚拟环境路径是否符合预期
if let Some(ref venv_path) = python_info.virtual_env_path {
let expected_venv_path = std::env::current_dir().map(|dir| dir.join("venv")).ok();
let is_expected_location = expected_venv_path
.as_ref()
.map(|expected| venv_path.contains(&expected.to_string_lossy().to_string()))
.unwrap_or(false);
if !is_expected_location {
let warning = EnvironmentWarning {
component: "Virtual Environment".to_string(),
message: format!("虚拟环境位于非预期位置: {venv_path}"),
impact: "可能影响依赖管理和路径解析".to_string(),
};
status.warnings.push(warning);
}
}
// 检查虚拟环境中的Python可执行文件
let expected_python_path = std::env::current_dir()
.map(|dir| Self::get_venv_python_path(&dir.join("venv")))
.ok();
if let Some(expected_path) = expected_python_path {
if !expected_path.exists() {
let issue = EnvironmentIssue {
component: "Virtual Environment".to_string(),
severity: IssueSeverity::Medium,
message: format!(
"预期的Python可执行文件不存在: {}",
expected_path.display()
),
suggestion: "重新创建虚拟环境: 运行 'document-parser uv-init'".to_string(),
auto_fixable: true,
};
status.issues.push(issue);
}
}
}
}
/// 获取虚拟环境激活命令
fn get_activation_command(&self) -> String {
if cfg!(windows) {
".\\venv\\Scripts\\activate.bat".to_string()
} else {
"source ./venv/bin/activate".to_string()
}
}
/// 获取Python安装建议
fn get_python_installation_suggestion(&self, error_message: &str) -> String {
if error_message.contains("command not found") || error_message.contains("not found") {
"Python未安装。请安装Python 3.8+: https://www.python.org/downloads/".to_string()
} else if error_message.contains("版本过低") {
"Python版本过低。请升级到Python 3.8或更高版本".to_string()
} else if error_message.contains("超时") {
"Python命令执行超时。检查系统负载或Python安装是否正常".to_string()
} else {
format!("Python环境问题: {error_message}。请检查Python安装并确保可以正常执行")
}
}
/// 获取UV安装建议
fn get_uv_installation_suggestion(&self, error_message: &str) -> String {
if error_message.contains("command not found") || error_message.contains("not found") {
"UV工具未安装。安装命令: curl -LsSf https://astral.sh/uv/install.sh | sh".to_string()
} else if error_message.contains("版本") {
"UV版本不兼容。请更新到最新版本: curl -LsSf https://astral.sh/uv/install.sh | sh"
.to_string()
} else {
format!("UV工具问题: {error_message}。请重新安装UV工具")
}
}
/// 获取MinerU安装建议
fn get_mineru_installation_suggestion(&self, error_message: &str) -> String {
if error_message.contains("command not found") || error_message.contains("not found") {
"MinerU未安装。在虚拟环境中安装: uv pip install magic-pdf[full]".to_string()
} else if error_message.contains("模块") || error_message.contains("module") {
"MinerU模块缺失。重新安装: uv pip install --force-reinstall magic-pdf[full]".to_string()
} else if error_message.contains("版本") {
"MinerU版本问题。更新到最新版本: uv pip install -U magic-pdf[full]".to_string()
} else {
format!("MinerU问题: {error_message}。请检查安装或重新安装")
}
}
/// 获取MarkItDown安装建议
fn get_markitdown_installation_suggestion(&self, error_message: &str) -> String {
if error_message.contains("模块") || error_message.contains("module") {
"MarkItDown模块未找到。在虚拟环境中安装: uv pip install markitdown".to_string()
} else if error_message.contains("版本") {
"MarkItDown版本问题。更新到最新版本: uv pip install -U markitdown".to_string()
} else {
format!("MarkItDown问题: {error_message}。请检查安装或重新安装")
}
}
/// 带重试的Python环境检查
async fn check_python_environment_with_retry(&self) -> Result<PythonInfo, AppError> {
self.retry_with_backoff("Python环境检查", || self.check_python_environment())
.await
}
/// 带重试的uv环境检查
async fn check_uv_environment_with_retry(&self) -> Result<UvInfo, AppError> {
self.retry_with_backoff("uv环境检查", || self.check_uv_environment())
.await
}
/// 带重试的CUDA环境检查
async fn check_cuda_environment_with_retry(&self) -> Result<CudaInfo, AppError> {
self.retry_with_backoff("CUDA环境检查", || self.check_cuda_environment())
.await
}
/// 带重试的MinerU环境检查
async fn check_mineru_environment_with_retry(&self) -> Result<PackageInfo, AppError> {
self.retry_with_backoff("MinerU环境检查", || self.check_mineru_environment())
.await
}
/// 带重试的MarkItDown环境检查
async fn check_markitdown_environment_with_retry(&self) -> Result<PackageInfo, AppError> {
self.retry_with_backoff("MarkItDown环境检查", || {
self.check_markitdown_environment()
})
.await
}
/// 验证依赖版本兼容性
pub async fn verify_dependency_compatibility(
&self,
) -> Result<DependencyVerificationResult, AppError> {
debug!("Start relying on version compatibility verification");
let (mineru_result, markitdown_result) = tokio::join!(
self.verify_mineru_dependency(),
self.verify_markitdown_dependency()
);
let mineru_status = mineru_result.unwrap_or_else(|e| DependencyStatus {
package_name: "MinerU".to_string(),
is_available: false,
is_functional: false,
version_info: None,
compatibility: None,
issues: vec![e.to_string()],
path: None,
});
let markitdown_status = markitdown_result.unwrap_or_else(|e| DependencyStatus {
package_name: "MarkItDown".to_string(),
is_available: false,
is_functional: false,
version_info: None,
compatibility: None,
issues: vec![e.to_string()],
path: None,
});
let overall_compatible = mineru_status.is_available
&& mineru_status.is_functional
&& markitdown_status.is_available
&& markitdown_status.is_functional
&& mineru_status
.compatibility
.as_ref()
.is_none_or(|c| c.is_compatible)
&& markitdown_status
.compatibility
.as_ref()
.is_none_or(|c| c.is_compatible);
let mut recommendations = Vec::new();
let mut critical_issues = Vec::new();
// 收集推荐和关键问题
if let Some(ref compat) = mineru_status.compatibility {
if !compat.is_compatible {
critical_issues.push(format!(
"MinerU版本不兼容: {} (最低要求: {})",
compat.current_version, compat.minimum_version
));
}
if compat.upgrade_available {
if let Some(ref rec) = compat.upgrade_recommendation {
recommendations.push(rec.clone());
}
}
}
if let Some(ref compat) = markitdown_status.compatibility {
if !compat.is_compatible {
critical_issues.push(format!(
"MarkItDown版本不兼容: {} (最低要求: {})",
compat.current_version, compat.minimum_version
));
}
if compat.upgrade_available {
if let Some(ref rec) = compat.upgrade_recommendation {
recommendations.push(rec.clone());
}
}
}
// 添加通用推荐
if !mineru_status.is_available {
recommendations.push("安装MinerU: uv pip install -U \"mineru[core]\"".to_string());
}
if !markitdown_status.is_available {
recommendations.push("安装MarkItDown: uv pip install markitdown".to_string());
}
Ok(DependencyVerificationResult {
mineru_status,
markitdown_status,
overall_compatible,
recommendations,
critical_issues,
})
}
/// 验证MinerU依赖
async fn verify_mineru_dependency(&self) -> Result<DependencyStatus, AppError> {
let current_dir = std::env::current_dir()
.map_err(|e| AppError::Environment(format!("无法获取当前目录: {e}")))?;
let venv_path = current_dir.join("venv");
let mineru_path = Self::get_venv_executable_path(&venv_path, "mineru");
let mut status = DependencyStatus {
package_name: "MinerU".to_string(),
is_available: mineru_path.exists(),
is_functional: false,
version_info: None,
compatibility: None,
issues: Vec::new(),
path: Some(mineru_path.to_string_lossy().to_string()),
};
if !status.is_available {
status.issues.push("MinerU命令不存在".to_string());
return Ok(status);
}
// 检查功能性
match self.check_mineru_environment().await {
Ok(package_info) => {
status.is_functional = true;
status.version_info = Some(package_info.clone());
// 验证版本兼容性
status.compatibility = Some(
self.check_mineru_version_compatibility(&package_info.version)
.await,
);
}
Err(e) => {
status.issues.push(format!("MinerU功能检查失败: {e}"));
}
}
Ok(status)
}
/// 验证MarkItDown依赖
async fn verify_markitdown_dependency(&self) -> Result<DependencyStatus, AppError> {
let current_dir = std::env::current_dir()
.map_err(|e| AppError::Environment(format!("无法获取当前目录: {e}")))?;
let venv_path = current_dir.join("venv");
let python_path = Self::get_venv_python_path(&venv_path);
let mut status = DependencyStatus {
package_name: "MarkItDown".to_string(),
is_available: false,
is_functional: false,
version_info: None,
compatibility: None,
issues: Vec::new(),
path: Some(python_path.to_string_lossy().to_string()),
};
// 检查功能性
match self.check_markitdown_environment().await {
Ok(package_info) => {
status.is_available = true;
status.is_functional = true;
status.version_info = Some(package_info.clone());
// 验证版本兼容性
status.compatibility = Some(
self.check_markitdown_version_compatibility(&package_info.version)
.await,
);
}
Err(e) => {
status.issues.push(format!("MarkItDown检查失败: {e}"));
}
}
Ok(status)
}
/// 检查MinerU版本兼容性
async fn check_mineru_version_compatibility(
&self,
current_version: &str,
) -> PackageCompatibility {
let minimum_version = "0.1.0"; // MinerU最低版本要求
let recommended_version = "latest"; // 推荐版本
let is_compatible = self.is_version_compatible(current_version, minimum_version);
let upgrade_available = current_version != "latest" && current_version != "available";
let mut compatibility_issues = Vec::new();
let mut upgrade_recommendation = None;
if !is_compatible {
compatibility_issues.push(format!(
"当前版本 {current_version} 低于最低要求版本 {minimum_version}"
));
}
if upgrade_available {
upgrade_recommendation =
Some("升级MinerU到最新版本: uv pip install -U \"mineru[core]\"".to_string());
}
// 检查特定版本的已知问题
if current_version.contains("0.0.") {
compatibility_issues.push("检测到早期版本,可能存在稳定性问题".to_string());
}
PackageCompatibility {
package_name: "MinerU".to_string(),
current_version: current_version.to_string(),
minimum_version: minimum_version.to_string(),
recommended_version: Some(recommended_version.to_string()),
is_compatible,
compatibility_issues,
upgrade_available,
upgrade_recommendation,
}
}
/// 检查MarkItDown版本兼容性
async fn check_markitdown_version_compatibility(
&self,
current_version: &str,
) -> PackageCompatibility {
let minimum_version = "0.0.1"; // MarkItDown最低版本要求
let recommended_version = "latest"; // 推荐版本
let is_compatible = self.is_version_compatible(current_version, minimum_version);
let upgrade_available = current_version != "latest" && current_version != "available";
let mut compatibility_issues = Vec::new();
let mut upgrade_recommendation = None;
if !is_compatible {
compatibility_issues.push(format!(
"当前版本 {current_version} 低于最低要求版本 {minimum_version}"
));
}
if upgrade_available {
upgrade_recommendation =
Some("升级MarkItDown到最新版本: uv pip install -U markitdown".to_string());
}
PackageCompatibility {
package_name: "MarkItDown".to_string(),
current_version: current_version.to_string(),
minimum_version: minimum_version.to_string(),
recommended_version: Some(recommended_version.to_string()),
is_compatible,
compatibility_issues,
upgrade_available,
upgrade_recommendation,
}
}
/// 比较版本号兼容性(简单的语义版本比较)
fn is_version_compatible(&self, current: &str, minimum: &str) -> bool {
// 处理特殊版本字符串
if current == "available" || current == "latest" || current == "unknown" {
return true; // 假设可用
}
// 简单的版本比较逻辑
match (self.parse_version(current), self.parse_version(minimum)) {
(Some(current_parts), Some(min_parts)) => {
for i in 0..3 {
let current_part = current_parts.get(i).unwrap_or(&0);
let min_part = min_parts.get(i).unwrap_or(&0);
if current_part > min_part {
return true;
} else if current_part < min_part {
return false;
}
}
true // 版本相等
}
_ => true, // 无法解析版本时假设兼容
}
}
/// 解析版本号为数字数组
fn parse_version(&self, version: &str) -> Option<Vec<u32>> {
// 提取版本号中的数字部分
let version_clean = version
.split_whitespace()
.next()?
.trim_start_matches('v')
.split('-')
.next()?;
let parts: Result<Vec<u32>, _> = version_clean
.split('.')
.take(3)
.map(|s| s.parse::<u32>())
.collect();
parts.ok()
}
/// 通用重试机制
async fn retry_with_backoff<T, F, Fut>(
&self,
operation_name: &str,
mut operation: F,
) -> Result<T, AppError>
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = Result<T, AppError>>,
{
let mut last_error = None;
let mut delay = self.retry_config.base_delay;
for attempt in 1..=self.retry_config.max_attempts {
match operation().await {
Ok(result) => {
if attempt > 1 {
info!("{} succeeded after {} attempt", operation_name, attempt);
}
return Ok(result);
}
Err(e) => {
last_error = Some(e);
if attempt < self.retry_config.max_attempts {
warn!(
"{} The {} attempt failed, try again in {} seconds",
operation_name,
attempt,
delay.as_secs_f32()
);
// 发送重试进度
if let Some(sender) = &self.progress_sender {
let progress = InstallProgress {
package: operation_name.to_string(),
stage: InstallStage::Retrying {
attempt,
max_attempts: self.retry_config.max_attempts,
},
progress: (attempt as f32 / self.retry_config.max_attempts as f32)
* 100.0,
message: format!(
"重试中... ({}/{})",
attempt, self.retry_config.max_attempts
),
estimated_time_remaining: Some(
delay * (self.retry_config.max_attempts - attempt),
),
bytes_downloaded: None,
total_bytes: None,
};
if let Ok(sender) = sender.try_lock() {
let _ = sender.send(progress);
}
}
sleep(delay).await;
delay = std::cmp::min(
Duration::from_secs_f64(
delay.as_secs_f64() * self.retry_config.backoff_multiplier,
),
self.retry_config.max_delay,
);
}
}
}
}
error!(
"{} Still failed after {} attempts",
operation_name, self.retry_config.max_attempts
);
Err(last_error.unwrap_or_else(|| AppError::Environment(format!("{operation_name} 失败"))))
}
/// 检查Python环境
#[instrument(skip(self))]
async fn check_python_environment(&self) -> Result<PythonInfo, AppError> {
debug!("Check Python environment: {}", self.python_path);
// 首先检查配置的Python路径是否存在
let python_executable = if Path::new(&self.python_path).exists() {
self.python_path.clone()
} else {
// 如果虚拟环境Python不存在尝试使用系统Python
debug!(
"The virtual environment Python path does not exist, try to find the system Python"
);
self.find_system_python().await.unwrap_or_else(|| {
// 如果找不到系统Python使用平台默认值
if cfg!(windows) {
"python.exe".to_string()
} else {
"python3".to_string()
}
})
};
// 检查Python版本带超时
let version_cmd = Command::new(&python_executable).arg("--version").output();
let output = timeout(self.timeout_duration, version_cmd)
.await
.map_err(|_| {
AppError::Environment(format!(
"Python版本检查超时: {}",
self.timeout_duration.as_secs()
))
})?
.map_err(|e| AppError::Environment(format!("无法执行Python命令: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(AppError::Environment(format!(
"Python命令执行失败: {stderr}"
)));
}
let version_output = String::from_utf8_lossy(&output.stdout);
let version = version_output.trim().to_string();
// 验证Python版本是否符合要求3.8+
if let Some(version_num) = self.extract_python_version(&version) {
if version_num < (3, 8) {
return Err(AppError::Environment(format!(
"Python版本过低: {version}需要3.8或更高版本"
)));
}
}
// 检查虚拟环境(带超时)
let venv_cmd = Command::new(&python_executable)
.arg("-c")
.arg("import sys; print(hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)); print(getattr(sys, 'prefix', ''))")
.output();
let venv_output = timeout(self.timeout_duration, venv_cmd)
.await
.map_err(|_| AppError::Environment("虚拟环境检查超时".to_string()))?
.map_err(|e| AppError::Environment(format!("无法检查虚拟环境: {e}")))?;
let venv_info = String::from_utf8_lossy(&venv_output.stdout);
let lines: Vec<&str> = venv_info.trim().split('\n').collect();
let virtual_env_active = lines
.first()
.and_then(|line| line.parse::<bool>().ok())
.unwrap_or(false);
let virtual_env_path = if virtual_env_active {
lines.get(1).map(|s| s.to_string())
} else {
None
};
debug!("Python environment check passed: {}", version);
if virtual_env_active {
debug!("Virtual environment detected: {:?}", virtual_env_path);
}
Ok(PythonInfo {
version: Some(version),
path: python_executable,
virtual_env_active,
virtual_env_path,
})
}
/// 提取Python版本号
fn extract_python_version(&self, version_str: &str) -> Option<(u32, u32)> {
// 解析类似 "Python 3.9.7" 的版本字符串
let parts: Vec<&str> = version_str.split_whitespace().collect();
if parts.len() >= 2 {
let version_part = parts[1];
let version_nums: Vec<&str> = version_part.split('.').collect();
if version_nums.len() >= 2 {
if let (Ok(major), Ok(minor)) = (
version_nums[0].parse::<u32>(),
version_nums[1].parse::<u32>(),
) {
return Some((major, minor));
}
}
}
None
}
/// 检查uv工具
async fn check_uv_environment(&self) -> Result<UvInfo, AppError> {
debug!("Check uv tools");
let uv_cmd = Command::new("uv").arg("--version").output();
let output = timeout(self.timeout_duration, uv_cmd)
.await
.map_err(|_| AppError::Environment("uv版本检查超时".to_string()))?
.map_err(|e| AppError::Environment(format!("无法执行uv命令: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(AppError::Environment(format!("uv命令执行失败: {stderr}")));
}
let version_output = String::from_utf8_lossy(&output.stdout);
let version = version_output.trim().to_string();
debug!("UV tool inspection passed: {}", version);
Ok(UvInfo { version })
}
/// 检查CUDA环境
pub async fn check_cuda_environment(&self) -> Result<CudaInfo, AppError> {
debug!("Check CUDA environment");
let nvidia_cmd = Command::new("nvidia-smi")
.arg("--query-gpu=index,name,memory.total,memory.free,compute_cap")
.arg("--format=csv,noheader,nounits")
.output();
let output = match timeout(Duration::from_secs(10), nvidia_cmd).await {
Ok(Ok(output)) if output.status.success() => output,
Ok(Ok(_)) => {
debug!("nvidia-smi execution failed, CUDA is not available");
return Ok(CudaInfo {
available: false,
version: None,
devices: Vec::new(),
});
}
Ok(Err(_)) | Err(_) => {
debug!("CUDA environment is not available");
return Ok(CudaInfo {
available: false,
version: None,
devices: Vec::new(),
});
}
};
// 解析CUDA设备信息
let output_str = String::from_utf8_lossy(&output.stdout);
let mut devices = Vec::new();
for line in output_str.lines() {
if let Some(device) = self.parse_cuda_device_info(line) {
devices.push(device);
}
}
// 获取CUDA版本
let version = self.get_cuda_version().await;
debug!(
"CUDA environment check completed: available={}, devices={}",
!devices.is_empty(),
devices.len()
);
Ok(CudaInfo {
available: !devices.is_empty(),
version,
devices,
})
}
/// 解析CUDA设备信息
fn parse_cuda_device_info(&self, line: &str) -> Option<CudaDevice> {
let parts: Vec<&str> = line.split(',').map(|s| s.trim()).collect();
if parts.len() >= 5 {
if let (Ok(id), Ok(memory_total), Ok(memory_free)) = (
parts[0].parse::<u32>(),
parts[2].parse::<u64>(),
parts[3].parse::<u64>(),
) {
return Some(CudaDevice {
id,
name: parts[1].to_string(),
memory_total: memory_total * 1024 * 1024, // 转换为字节
memory_free: memory_free * 1024 * 1024, // 转换为字节
compute_capability: parts[4].to_string(),
});
}
}
None
}
/// 获取CUDA版本
async fn get_cuda_version(&self) -> Option<String> {
let version_cmd = Command::new("nvidia-smi")
.arg("--query-gpu=driver_version")
.arg("--format=csv,noheader,nounits")
.output();
if let Ok(Ok(output)) = timeout(Duration::from_secs(5), version_cmd).await {
if output.status.success() {
let version_str = String::from_utf8_lossy(&output.stdout);
return Some(version_str.trim().to_string());
}
}
None
}
/// 检查MinerU环境
async fn check_mineru_environment(&self) -> Result<PackageInfo, AppError> {
debug!("Check MinerU environment");
// 使用当前目录的虚拟环境中的mineru命令路径
let current_dir = std::env::current_dir()
.map_err(|e| AppError::Environment(format!("无法获取当前目录: {e}")))?;
let venv_path = current_dir.join("venv");
let mineru_path = Self::get_venv_executable_path(&venv_path, "mineru");
// 首先检查mineru可执行文件是否存在
if !mineru_path.exists() {
return Err(AppError::Environment(format!(
"MinerU命令不存在: {}. 请运行 'uv pip install -U \"mineru[core]\"' 安装MinerU",
mineru_path.display()
)));
}
// 检查mineru命令是否可执行
let help_cmd = Command::new(&mineru_path).arg("--help").output();
let help_output = timeout(self.timeout_duration, help_cmd)
.await
.map_err(|_| AppError::Environment("MinerU帮助命令检查超时".to_string()))?
.map_err(|e| {
AppError::Environment(format!(
"无法执行MinerU帮助命令: {e}. 请确保已正确安装MinerU"
))
})?;
if !help_output.status.success() {
let stderr = String::from_utf8_lossy(&help_output.stderr);
return Err(AppError::Environment(format!(
"MinerU帮助命令执行失败: {stderr}. 请检查MinerU安装"
)));
}
// 验证mineru命令功能性 - 测试基本功能
let test_cmd = Command::new(&mineru_path).arg("--version").output();
let version_output = timeout(Duration::from_secs(30), test_cmd)
.await
.map_err(|_| AppError::Environment("MinerU版本检查超时".to_string()))?;
let version = match version_output {
Ok(output) if output.status.success() => {
let version_str = String::from_utf8_lossy(&output.stdout);
let version = version_str.trim().to_string();
if version.is_empty() {
// 如果版本输出为空尝试从stderr获取
let stderr_str = String::from_utf8_lossy(&output.stderr);
if !stderr_str.is_empty() {
stderr_str.trim().to_string()
} else {
"unknown".to_string()
}
} else {
version
}
}
Ok(output) => {
// 版本命令失败但帮助命令成功说明mineru可用但版本获取有问题
let stderr = String::from_utf8_lossy(&output.stderr);
warn!(
"MinerU version acquisition failed, but the command is available: {}",
stderr
);
"available".to_string()
}
Err(e) => {
return Err(AppError::Environment(format!(
"MinerU版本检查执行失败: {e}. 请检查MinerU安装"
)));
}
};
// MinerU命令验证已通过无需额外的模块导入测试
debug!("MinerU environment check passed, version: {}", version);
Ok(PackageInfo { version })
}
/// 检查MarkItDown环境
async fn check_markitdown_environment(&self) -> Result<PackageInfo, AppError> {
debug!("Check MarkItDown environment");
// 优先使用虚拟环境中的Python
let current_dir = std::env::current_dir()
.map_err(|e| AppError::Environment(format!("无法获取当前目录: {e}")))?;
let venv_path = current_dir.join("venv");
let python_executable = if venv_path.exists() {
Self::get_venv_python_path(&venv_path)
} else if Path::new(&self.python_path).exists() {
std::path::PathBuf::from(&self.python_path)
} else {
// 回退到系统Python
let system_python = self.find_system_python().await.unwrap_or_else(|| {
if cfg!(windows) {
"python.exe".to_string()
} else {
"python3".to_string()
}
});
std::path::PathBuf::from(system_python)
};
// 首先测试MarkItDown模块导入
let import_test_cmd = Command::new(&python_executable)
.arg("-c")
.arg("import markitdown; print('MarkItDown模块导入成功')")
.output();
let import_output = timeout(self.timeout_duration, import_test_cmd)
.await
.map_err(|_| AppError::Environment("MarkItDown模块导入测试超时".to_string()))?
.map_err(|e| AppError::Environment(format!("无法测试MarkItDown模块导入: {e}")))?;
if !import_output.status.success() {
let stderr = String::from_utf8_lossy(&import_output.stderr);
return Err(AppError::Environment(format!(
"MarkItDown模块导入失败: {stderr}. 请运行 'uv pip install markitdown' 安装MarkItDown"
)));
}
// 获取版本信息
let version_cmd = Command::new(&python_executable)
.arg("-c")
.arg("import markitdown; print(markitdown.__version__)")
.output();
let version_output = timeout(self.timeout_duration, version_cmd)
.await
.map_err(|_| AppError::Environment("MarkItDown版本检查超时".to_string()))?
.map_err(|e| AppError::Environment(format!("无法获取MarkItDown版本: {e}")))?;
let version = if version_output.status.success() {
let version_str = String::from_utf8_lossy(&version_output.stdout);
version_str.trim().to_string()
} else {
// 如果版本获取失败但导入成功,使用默认版本
warn!("MarkItDown version acquisition failed, but the module is available");
"available".to_string()
};
// 功能性验证 - 测试MarkItDown基本功能
let functionality_test_cmd = Command::new(&python_executable)
.arg("-c")
.arg(
r#"
import markitdown
from markitdown import MarkItDown
md = MarkItDown()
# 测试基本功能是否可用
print('MarkItDown功能验证成功')
"#,
)
.output();
let func_result = timeout(Duration::from_secs(15), functionality_test_cmd).await;
match func_result {
Ok(Ok(output)) if output.status.success() => {
debug!("MarkItDown function verification successful");
}
Ok(Ok(output)) => {
let stderr = String::from_utf8_lossy(&output.stderr);
warn!("MarkItDown function verification failed: {}", stderr);
return Err(AppError::Environment(format!(
"MarkItDown功能验证失败: {stderr}. 请重新安装MarkItDown"
)));
}
Ok(Err(e)) => {
warn!("MarkItDown functional test execution failed: {}", e);
}
Err(_) => {
warn!("MarkItDown function test timeout");
}
}
debug!("MarkItDown environment check passed: {}", version);
Ok(PackageInfo { version })
}
/// Python环境设置
#[instrument(skip(self))]
pub async fn setup_python_environment(&self) -> Result<(), AppError> {
info!("Start Python environment setup");
// 发送开始进度
self.send_progress("环境设置", InstallStage::Preparing, 0.0, "准备环境设置")
.await;
// 确保基础目录存在
self.ensure_base_directory().await?;
self.send_progress("环境设置", InstallStage::Preparing, 10.0, "准备工作目录")
.await;
// 检查并安装uv
match self.is_uv_available().await? {
UvAvailabilityStatus::Available {
version,
compatibility,
} => {
if compatibility.is_compatible {
info!("uv tool is available and compatible: {}", version);
if let Some(recommendation) = compatibility.recommendation {
info!("UV upgrade suggestion: {}", recommendation);
}
} else {
warn!(
"The uv version is incompatible, reinstall: {}",
compatibility.recommendation.unwrap_or_default()
);
self.send_progress(
"环境设置",
InstallStage::Installing,
20.0,
"重新安装兼容版本的uv",
)
.await;
self.install_uv_with_progress().await?;
}
}
UvAvailabilityStatus::IncompatibleVersion { version, issue } => {
warn!("UV version is not compatible: {} - {}", version, issue);
self.send_progress(
"环境设置",
InstallStage::Installing,
20.0,
"安装兼容版本的uv",
)
.await;
self.install_uv_with_progress().await?;
}
UvAvailabilityStatus::ExecutionFailed { error } => {
warn!("UV execution failed, reinstall: {}", error);
self.send_progress("环境设置", InstallStage::Installing, 20.0, "重新安装uv工具")
.await;
self.install_uv_with_progress().await?;
}
UvAvailabilityStatus::NotInstalled { error: _ } => {
info!("The uv tool is not installed, start the installation");
self.send_progress("环境设置", InstallStage::Installing, 20.0, "安装uv工具")
.await;
self.install_uv_with_progress().await?;
}
}
// 创建Python虚拟环境
self.send_progress(
"环境设置",
InstallStage::Configuring,
40.0,
"创建Python虚拟环境",
)
.await;
self.create_python_venv_with_progress().await?;
// 安装依赖
self.send_progress("环境设置", InstallStage::Installing, 60.0, "安装Python依赖")
.await;
self.install_dependencies().await?;
// 验证安装(非阻塞)
self.send_progress("环境设置", InstallStage::Verifying, 90.0, "验证环境")
.await;
match self.validate_engines().await {
Ok(is_valid) => {
if is_valid {
self.send_progress("环境设置", InstallStage::Completed, 100.0, "环境设置完成")
.await;
info!("Python environment setup completed");
} else {
warn!(
"Environment verification did not fully pass, but the installation process was completed"
);
self.send_progress(
"环境设置",
InstallStage::Completed,
100.0,
"安装完成(部分验证待完善)",
)
.await;
}
}
Err(e) => {
warn!(
"There was a problem with the environment verification process: {}",
e
);
self.send_progress(
"环境设置",
InstallStage::Completed,
100.0,
"安装完成(验证待重试)",
)
.await;
}
}
// 清除缓存以强制重新检查
self.clear_cache().await;
Ok(())
}
/// 安装依赖包
#[instrument(skip(self))]
pub async fn install_dependencies(&self) -> Result<(), AppError> {
info!("Start installing Python dependencies");
// 并行安装MinerU和MarkItDown
let (mineru_result, markitdown_result) = tokio::join!(
self.install_mineru_with_progress(),
self.install_markitdown_with_progress()
);
mineru_result?;
markitdown_result?;
info!("Python dependency installation completed");
Ok(())
}
/// 验证所有引擎
#[instrument(skip(self))]
pub async fn validate_engines(&self) -> Result<bool, AppError> {
info!("Verify parsing engine");
// 清除缓存以确保获取最新状态
self.clear_cache().await;
// 等待一小段时间确保安装完成
sleep(Duration::from_millis(500)).await;
let status = self.check_environment().await?;
let is_valid = status.is_ready();
if !is_valid {
let critical_issues = status.get_critical_issues();
for issue in critical_issues {
error!("Key questions: {} - {}", issue.component, issue.message);
}
}
Ok(is_valid)
}
/// 发送安装进度
async fn send_progress(
&self,
package: &str,
stage: InstallStage,
progress: f32,
message: &str,
) {
if let Some(sender) = &self.progress_sender {
let progress_info = InstallProgress {
package: package.to_string(),
stage,
progress,
message: message.to_string(),
estimated_time_remaining: None,
bytes_downloaded: None,
total_bytes: None,
};
if let Ok(sender) = sender.try_lock() {
let _ = sender.send(progress_info);
}
}
}
/// 确保基础目录存在
async fn ensure_base_directory(&self) -> Result<(), AppError> {
if !Path::new(&self.base_dir).exists() {
std::fs::create_dir_all(&self.base_dir)
.map_err(|e| AppError::File(format!("创建基础目录失败: {e}")))?;
info!("Create base directory: {}", self.base_dir);
}
Ok(())
}
/// 检查uv是否可用增强版本带详细错误报告
pub async fn is_uv_available(&self) -> Result<UvAvailabilityStatus, AppError> {
debug!("Check uv tool availability");
let uv_cmd = Command::new("uv").arg("--version").output();
let output = timeout(Duration::from_secs(10), uv_cmd)
.await
.map_err(|_| AppError::Environment("uv版本检查超时".to_string()))?;
match output {
Ok(output) if output.status.success() => {
let version_output = String::from_utf8_lossy(&output.stdout);
let version = version_output.trim().to_string();
// 检查版本兼容性
match self.check_uv_version_compatibility(&version) {
Ok(compatibility) => {
debug!("uv tool available: {}", version);
Ok(UvAvailabilityStatus::Available {
version,
compatibility,
})
}
Err(e) => {
warn!("Incompatible uv version: {}", e);
Ok(UvAvailabilityStatus::IncompatibleVersion {
version,
issue: e.to_string(),
})
}
}
}
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
let error_msg = if stderr.is_empty() {
"uv命令执行失败未知错误".to_string()
} else {
format!("uv命令执行失败: {stderr}")
};
debug!("uv command execution failed: {}", error_msg);
Ok(UvAvailabilityStatus::ExecutionFailed { error: error_msg })
}
Err(e) => {
let error_msg = format!("无法执行uv命令: {e}");
debug!(
"The uv command does not exist or cannot be executed: {}",
error_msg
);
Ok(UvAvailabilityStatus::NotInstalled { error: error_msg })
}
}
}
/// 检查UV版本兼容性
fn check_uv_version_compatibility(
&self,
version_str: &str,
) -> Result<UvVersionCompatibility, AppError> {
// UV最低版本要求0.1.0
let minimum_version = "0.1.0";
// 解析版本号
let current_version = self
.extract_uv_version(version_str)
.ok_or_else(|| AppError::Environment(format!("无法解析uv版本: {version_str}")))?;
let min_version = self.parse_version_tuple(minimum_version).ok_or_else(|| {
AppError::Environment(format!("无法解析最低版本要求: {minimum_version}"))
})?;
let is_compatible = current_version >= min_version;
let recommendation = if !is_compatible {
Some(format!(
"请升级uv到{minimum_version}或更高版本,运行: curl -LsSf https://astral.sh/uv/install.sh | sh"
))
} else if current_version.0 == 0 && current_version.1 < 5 {
// 如果版本低于0.5.0,建议升级以获得更好的性能
Some("建议升级到uv 0.5.0+以获得更好的性能和稳定性".to_string())
} else {
None
};
Ok(UvVersionCompatibility {
is_compatible,
minimum_version: minimum_version.to_string(),
current_version: version_str.to_string(),
recommendation,
})
}
/// 提取UV版本号
fn extract_uv_version(&self, version_str: &str) -> Option<(u32, u32, u32)> {
// 解析类似 "uv 0.4.15" 或 "0.4.15" 的版本字符串
let version_part = if version_str.starts_with("uv ") {
version_str.strip_prefix("uv ").unwrap_or(version_str)
} else {
version_str
};
self.parse_version_tuple(version_part)
}
/// 解析版本号为元组 (major, minor, patch)
fn parse_version_tuple(&self, version_str: &str) -> Option<(u32, u32, u32)> {
let parts: Vec<&str> = version_str.split('.').collect();
if parts.len() >= 2 {
let major = parts[0].parse::<u32>().ok()?;
let minor = parts[1].parse::<u32>().ok()?;
let patch = if parts.len() >= 3 {
parts[2].parse::<u32>().unwrap_or(0)
} else {
0
};
Some((major, minor, patch))
} else {
None
}
}
/// 安装uv工具增强版本带进度跟踪和多种安装方法
pub async fn install_uv_with_progress(&self) -> Result<(), AppError> {
info!("Start installing uv tools");
self.send_progress("uv", InstallStage::Preparing, 0.0, "准备安装uv工具")
.await;
// 确定最佳安装方法
let installation_method = self.determine_best_uv_installation_method().await;
info!("Select installation method: {:?}", installation_method);
// 尝试安装
let install_result = match installation_method {
UvInstallationMethod::CurlScript => self.install_uv_with_curl_script().await,
UvInstallationMethod::PowerShellScript => {
self.install_uv_with_powershell_script().await
}
UvInstallationMethod::PipInstall => self.install_uv_with_pip().await,
UvInstallationMethod::SystemPackageManager => {
self.install_uv_with_system_package_manager().await
}
};
match install_result {
Ok(_) => {
self.send_progress("uv", InstallStage::Verifying, 90.0, "验证uv安装")
.await;
// 验证安装
match self.is_uv_available().await? {
UvAvailabilityStatus::Available {
version,
compatibility,
} => {
if compatibility.is_compatible {
self.send_progress(
"uv",
InstallStage::Completed,
100.0,
&format!("uv安装完成: {version}"),
)
.await;
info!("UV installation successful: {}", version);
Ok(())
} else {
let error_msg = format!(
"uv版本不兼容: {}",
compatibility.recommendation.unwrap_or_default()
);
self.send_progress(
"uv",
InstallStage::Failed(error_msg.clone()),
0.0,
"版本不兼容",
)
.await;
Err(AppError::Environment(error_msg))
}
}
UvAvailabilityStatus::IncompatibleVersion { version, issue } => {
let error_msg = format!("uv版本不兼容: {version} - {issue}");
self.send_progress(
"uv",
InstallStage::Failed(error_msg.clone()),
0.0,
"版本不兼容",
)
.await;
Err(AppError::Environment(error_msg))
}
UvAvailabilityStatus::ExecutionFailed { error } => {
let error_msg = format!("uv安装后执行失败: {error}");
self.send_progress(
"uv",
InstallStage::Failed(error_msg.clone()),
0.0,
"执行失败",
)
.await;
Err(AppError::Environment(error_msg))
}
UvAvailabilityStatus::NotInstalled { error } => {
let error_msg = format!("uv安装后仍不可用: {error}");
self.send_progress(
"uv",
InstallStage::Failed(error_msg.clone()),
0.0,
"安装失败",
)
.await;
Err(AppError::Environment(error_msg))
}
}
}
Err(e) => {
let error_msg = format!("uv安装失败: {e}");
self.send_progress(
"uv",
InstallStage::Failed(error_msg.clone()),
0.0,
"安装失败",
)
.await;
// 如果主要方法失败,尝试备用方法
warn!("Primary installation method failed, try alternate method");
self.try_fallback_uv_installation().await
}
}
}
/// 确定最佳UV安装方法
async fn determine_best_uv_installation_method(&self) -> UvInstallationMethod {
if cfg!(target_os = "windows") {
// Windows优先使用PowerShell脚本
if self.is_powershell_available().await {
UvInstallationMethod::PowerShellScript
} else {
UvInstallationMethod::CurlScript
}
} else {
// Unix系统优先使用curl脚本
if self.is_curl_available().await {
UvInstallationMethod::CurlScript
} else if self.is_pip_available().await {
UvInstallationMethod::PipInstall
} else {
UvInstallationMethod::SystemPackageManager
}
}
}
/// 检查PowerShell是否可用
async fn is_powershell_available(&self) -> bool {
Command::new("powershell")
.arg("-Command")
.arg("Get-Host")
.output()
.await
.map(|output| output.status.success())
.unwrap_or(false)
}
/// 检查curl是否可用
async fn is_curl_available(&self) -> bool {
Command::new("curl")
.arg("--version")
.output()
.await
.map(|output| output.status.success())
.unwrap_or(false)
}
/// 检查pip是否可用
async fn is_pip_available(&self) -> bool {
Command::new("pip")
.arg("--version")
.output()
.await
.map(|output| output.status.success())
.unwrap_or(false)
}
/// 使用curl脚本安装UV
async fn install_uv_with_curl_script(&self) -> Result<(), AppError> {
self.send_progress("uv", InstallStage::Downloading, 10.0, "下载uv安装脚本")
.await;
let install_cmd = Command::new("sh")
.arg("-c")
.arg("curl -LsSf https://astral.sh/uv/install.sh | sh")
.output();
self.send_progress("uv", InstallStage::Installing, 50.0, "执行curl安装脚本")
.await;
let output = timeout(Duration::from_secs(300), install_cmd)
.await
.map_err(|_| AppError::Environment("uv curl安装超时".to_string()))?
.map_err(|e| AppError::Environment(format!("curl安装uv失败: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(AppError::Environment(format!("curl安装uv失败: {stderr}")));
}
info!("Successfully installed uv using curl script");
Ok(())
}
/// 使用PowerShell脚本安装UV
async fn install_uv_with_powershell_script(&self) -> Result<(), AppError> {
self.send_progress(
"uv",
InstallStage::Downloading,
10.0,
"下载uv PowerShell脚本",
)
.await;
let install_cmd = Command::new("powershell")
.arg("-ExecutionPolicy")
.arg("ByPass")
.arg("-c")
.arg("irm https://astral.sh/uv/install.ps1 | iex")
.output();
self.send_progress(
"uv",
InstallStage::Installing,
50.0,
"执行PowerShell安装脚本",
)
.await;
let output = timeout(Duration::from_secs(300), install_cmd)
.await
.map_err(|_| AppError::Environment("uv PowerShell安装超时".to_string()))?
.map_err(|e| AppError::Environment(format!("PowerShell安装uv失败: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(AppError::Environment(format!(
"PowerShell安装uv失败: {stderr}"
)));
}
info!("Successfully installed uv using PowerShell script");
Ok(())
}
/// 使用pip安装UV
async fn install_uv_with_pip(&self) -> Result<(), AppError> {
self.send_progress("uv", InstallStage::Installing, 30.0, "使用pip安装uv")
.await;
let install_cmd = Command::new("pip").arg("install").arg("uv").output();
let output = timeout(Duration::from_secs(180), install_cmd)
.await
.map_err(|_| AppError::Environment("uv pip安装超时".to_string()))?
.map_err(|e| AppError::Environment(format!("pip安装uv失败: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(AppError::Environment(format!("pip安装uv失败: {stderr}")));
}
info!("Successfully installed uv using pip");
Ok(())
}
/// 使用系统包管理器安装UV
async fn install_uv_with_system_package_manager(&self) -> Result<(), AppError> {
self.send_progress(
"uv",
InstallStage::Installing,
30.0,
"使用系统包管理器安装uv",
)
.await;
// 尝试不同的包管理器
let package_managers = if cfg!(target_os = "macos") {
vec![("brew", vec!["install", "uv"])]
} else if cfg!(target_os = "linux") {
vec![
("apt", vec!["install", "-y", "uv"]),
("yum", vec!["install", "-y", "uv"]),
("dnf", vec!["install", "-y", "uv"]),
("pacman", vec!["-S", "--noconfirm", "uv"]),
]
} else {
vec![]
};
for (manager, args) in package_managers {
if self.is_command_available(manager).await {
let install_cmd = Command::new(manager).args(&args).output();
match timeout(Duration::from_secs(300), install_cmd).await {
Ok(Ok(output)) if output.status.success() => {
info!("Use {} to install uv successfully", manager);
return Ok(());
}
Ok(Ok(output)) => {
let stderr = String::from_utf8_lossy(&output.stderr);
warn!("{} failed to install uv: {}", manager, stderr);
}
Ok(Err(e)) => {
warn!("{} command execution failed: {}", manager, e);
}
Err(_) => {
warn!("{} installation uv timeout", manager);
}
}
}
}
Err(AppError::Environment(
"所有系统包管理器都无法安装uv".to_string(),
))
}
/// 检查命令是否可用
async fn is_command_available(&self, command: &str) -> bool {
Command::new("which")
.arg(command)
.output()
.await
.map(|output| output.status.success())
.unwrap_or(false)
}
/// 尝试备用UV安装方法
async fn try_fallback_uv_installation(&self) -> Result<(), AppError> {
warn!("Try alternative uv installation methods");
// 备用方法列表
let fallback_methods = vec![
UvInstallationMethod::PipInstall,
UvInstallationMethod::CurlScript,
UvInstallationMethod::SystemPackageManager,
];
for method in fallback_methods {
info!("Try alternative installation method: {:?}", method);
let result = match method {
UvInstallationMethod::PipInstall => self.install_uv_with_pip().await,
UvInstallationMethod::CurlScript => self.install_uv_with_curl_script().await,
UvInstallationMethod::SystemPackageManager => {
self.install_uv_with_system_package_manager().await
}
UvInstallationMethod::PowerShellScript => {
self.install_uv_with_powershell_script().await
}
};
match result {
Ok(_) => {
// 验证安装
match self.is_uv_available().await? {
UvAvailabilityStatus::Available {
version,
compatibility,
} => {
if compatibility.is_compatible {
self.send_progress(
"uv",
InstallStage::Completed,
100.0,
&format!("uv备用安装成功: {version}"),
)
.await;
info!("UV backup installation successful: {}", version);
return Ok(());
}
}
_ => continue,
}
}
Err(e) => {
warn!("Alternate installation method failed: {}", e);
continue;
}
}
}
Err(AppError::Environment("所有uv安装方法都失败了".to_string()))
}
/// 验证虚拟环境创建的前置条件
async fn validate_venv_creation_preconditions(&self, venv_path: &Path) -> Result<(), AppError> {
let base_dir = Path::new(&self.base_dir);
// 检查基础目录是否存在
if !base_dir.exists() {
return Err(AppError::path_error("基础目录不存在".to_string(), base_dir));
}
// 检查基础目录是否为目录
if !base_dir.is_dir() {
return Err(AppError::path_error(
"基础路径不是目录".to_string(),
base_dir,
));
}
// 检查基础目录写入权限
if let Err(e) = self.check_directory_writable(base_dir).await {
return Err(AppError::permission_error(
format!("基础目录无写入权限: {e}"),
base_dir,
));
}
// 检查虚拟环境路径是否已存在且为文件(而非目录)
if venv_path.exists() && !venv_path.is_dir() {
return Err(AppError::virtual_environment_path_error(
"虚拟环境路径已存在但不是目录".to_string(),
venv_path,
));
}
// 检查路径长度Windows路径长度限制
if cfg!(windows) && venv_path.to_string_lossy().len() > 260 {
return Err(AppError::virtual_environment_path_error(
"虚拟环境路径过长Windows系统限制为260字符".to_string(),
venv_path,
));
}
Ok(())
}
/// 检查目录是否可写
async fn check_directory_writable(&self, dir: &Path) -> Result<(), std::io::Error> {
let test_file = dir.join(".write_test");
// 尝试创建测试文件
match std::fs::File::create(&test_file) {
Ok(_) => {
// 清理测试文件
let _ = std::fs::remove_file(&test_file);
Ok(())
}
Err(e) => Err(e),
}
}
/// 处理虚拟环境创建错误并提供恢复建议
fn handle_venv_creation_error(&self, error: &str, venv_path: &Path) -> AppError {
let error_lower = error.to_lowercase();
if error_lower.contains("permission") || error_lower.contains("权限") {
AppError::permission_error(
format!("虚拟环境创建权限错误: {error}"),
venv_path.parent().unwrap_or(venv_path),
)
} else if error_lower.contains("space")
|| error_lower.contains("空间")
|| error_lower.contains("disk")
{
AppError::virtual_environment_path_error(
format!("磁盘空间不足导致虚拟环境创建失败: {error}"),
venv_path,
)
} else if error_lower.contains("exists") || error_lower.contains("存在") {
AppError::virtual_environment_path_error(
format!("虚拟环境路径冲突: {error}"),
venv_path,
)
} else if error_lower.contains("path") || error_lower.contains("路径") {
AppError::path_error(format!("虚拟环境路径错误: {error}"), venv_path)
} else if error_lower.contains("timeout") || error_lower.contains("超时") {
AppError::Environment(format!("虚拟环境创建超时: {error}"))
} else {
AppError::virtual_environment_path_error(
format!("虚拟环境创建失败: {error}"),
venv_path,
)
}
}
/// 尝试清理损坏的虚拟环境
async fn cleanup_corrupted_venv(&self, venv_path: &Path) -> Result<(), AppError> {
if !venv_path.exists() {
return Ok(());
}
info!(
"Try cleaning the corrupted virtual environment: {}",
venv_path.display()
);
// 检查是否有权限删除
if let Err(e) = self
.check_directory_writable(venv_path.parent().unwrap_or(venv_path))
.await
{
return Err(AppError::permission_error(
format!("无权限清理虚拟环境: {e}"),
venv_path,
));
}
// 尝试删除虚拟环境目录
match std::fs::remove_dir_all(venv_path) {
Ok(_) => {
info!("Successfully cleans corrupted virtual environment");
Ok(())
}
Err(e) => Err(AppError::permission_error(
format!("清理虚拟环境失败: {e}"),
venv_path,
)),
}
}
/// 验证当前目录设置任务12的核心功能
#[instrument(skip(self))]
pub async fn validate_current_directory_setup(
&self,
) -> Result<DirectoryValidationResult, AppError> {
let current_dir = Path::new(&self.base_dir);
let venv_path = current_dir.join("venv");
info!(
"Start verifying current directory settings: {}",
current_dir.display()
);
let mut result = DirectoryValidationResult {
is_valid: true,
current_directory: current_dir.to_path_buf(),
venv_path: venv_path.clone(),
issues: Vec::new(),
warnings: Vec::new(),
cleanup_options: Vec::new(),
recommendations: Vec::new(),
};
// 1. 检查当前目录是否可写
if let Err(e) = self.check_directory_writable(current_dir).await {
result.is_valid = false;
result.issues.push(DirectoryValidationIssue {
issue_type: DirectoryIssueType::PermissionDenied,
message: format!("当前目录不可写: {e}"),
severity: ValidationSeverity::Critical,
auto_fixable: false,
fix_suggestion: "检查目录权限,确保当前用户有写入权限".to_string(),
});
}
// 3. 检查虚拟环境路径冲突
if venv_path.exists() {
if venv_path.is_file() {
result.is_valid = false;
result.issues.push(DirectoryValidationIssue {
issue_type: DirectoryIssueType::PathConflict,
message: "虚拟环境路径被文件占用".to_string(),
severity: ValidationSeverity::High,
auto_fixable: true,
fix_suggestion: "删除冲突的文件".to_string(),
});
result.cleanup_options.push(CleanupOption {
option_type: CleanupType::RemoveConflictingFile,
description: format!("删除冲突文件: {}", venv_path.display()),
risk_level: CleanupRisk::Low,
command: format!("rm {}", venv_path.display()),
});
} else if venv_path.is_dir() {
// 检查虚拟环境是否损坏
let python_exe = Self::get_venv_python_path(&venv_path);
if !python_exe.exists() {
result.warnings.push(DirectoryValidationWarning {
warning_type: DirectoryWarningType::CorruptedVenv,
message: "检测到损坏的虚拟环境".to_string(),
impact: "虚拟环境无法正常使用".to_string(),
});
result.cleanup_options.push(CleanupOption {
option_type: CleanupType::RemoveCorruptedVenv,
description: format!("清理损坏的虚拟环境: {}", venv_path.display()),
risk_level: CleanupRisk::Medium,
command: format!("rm -rf {}", venv_path.display()),
});
} else {
// 虚拟环境存在且看起来完整,进行更深入的验证
match self.test_virtual_environment_activation(&venv_path).await {
Ok(true) => {
result.warnings.push(DirectoryValidationWarning {
warning_type: DirectoryWarningType::ExistingVenv,
message: "检测到现有的虚拟环境".to_string(),
impact: "将使用现有虚拟环境,可能需要更新依赖".to_string(),
});
}
Ok(false) => {
result.warnings.push(DirectoryValidationWarning {
warning_type: DirectoryWarningType::CorruptedVenv,
message: "现有虚拟环境无法激活".to_string(),
impact: "虚拟环境可能已损坏".to_string(),
});
result.cleanup_options.push(CleanupOption {
option_type: CleanupType::RemoveCorruptedVenv,
description: format!(
"清理无法激活的虚拟环境: {}",
venv_path.display()
),
risk_level: CleanupRisk::Medium,
command: format!("rm -rf {}", venv_path.display()),
});
}
Err(e) => {
result.warnings.push(DirectoryValidationWarning {
warning_type: DirectoryWarningType::CorruptedVenv,
message: format!("虚拟环境测试失败: {e}"),
impact: "无法确定虚拟环境状态".to_string(),
});
}
}
}
}
}
// 4. 检查路径长度Windows特有问题
if cfg!(windows) && venv_path.to_string_lossy().len() > 260 {
result.is_valid = false;
result.issues.push(DirectoryValidationIssue {
issue_type: DirectoryIssueType::PathTooLong,
message: format!(
"虚拟环境路径过长 ({} 字符)Windows限制为260字符",
venv_path.to_string_lossy().len()
),
severity: ValidationSeverity::High,
auto_fixable: false,
fix_suggestion: "移动项目到路径较短的目录".to_string(),
});
}
// 5. 检查特殊字符和编码问题
let path_str = venv_path.to_string_lossy();
if path_str.contains(' ') {
result.warnings.push(DirectoryValidationWarning {
warning_type: DirectoryWarningType::PathWithSpaces,
message: "路径包含空格".to_string(),
impact: "某些工具可能无法正确处理包含空格的路径".to_string(),
});
}
// 6. 生成推荐建议
self.generate_directory_recommendations(&mut result);
info!(
"Directory verification completed: valid={}, issues={}, warnings={}",
result.is_valid,
result.issues.len(),
result.warnings.len()
);
Ok(result)
}
/// 生成目录验证推荐建议
fn generate_directory_recommendations(&self, result: &mut DirectoryValidationResult) {
if result.is_valid && result.warnings.is_empty() {
result
.recommendations
.push("当前目录设置良好,可以安全创建虚拟环境".to_string());
return;
}
if !result.is_valid {
result
.recommendations
.push("请先解决关键问题后再创建虚拟环境".to_string());
}
// 基于问题类型生成具体建议
for issue in &result.issues {
match issue.issue_type {
DirectoryIssueType::PermissionDenied => {
if cfg!(unix) {
result
.recommendations
.push("使用 'chmod 755 .' 修改目录权限".to_string());
result
.recommendations
.push("使用 'chown $USER .' 修改目录所有者".to_string());
} else if cfg!(windows) {
result
.recommendations
.push("以管理员身份运行命令".to_string());
result
.recommendations
.push("检查Windows用户账户控制(UAC)设置".to_string());
}
}
DirectoryIssueType::InsufficientSpace => {
result
.recommendations
.push("清理不需要的文件释放磁盘空间".to_string());
result
.recommendations
.push("考虑移动项目到有更多可用空间的磁盘".to_string());
}
DirectoryIssueType::PathConflict => {
result
.recommendations
.push("删除或重命名冲突的文件/目录".to_string());
}
DirectoryIssueType::PathTooLong => {
result
.recommendations
.push("移动项目到路径较短的目录".to_string());
result
.recommendations
.push("使用较短的目录名称".to_string());
}
}
}
// 基于清理选项生成建议
if !result.cleanup_options.is_empty() {
result
.recommendations
.push("可以使用以下清理选项解决问题:".to_string());
for option in &result.cleanup_options {
result.recommendations.push(format!(
" - {} (风险: {:?})",
option.description, option.risk_level
));
}
}
}
/// 执行自动清理选项
pub async fn execute_cleanup_option(
&self,
option_type: CleanupType,
) -> Result<String, AppError> {
let venv_path = Path::new(&self.base_dir).join("venv");
match option_type {
CleanupType::RemoveConflictingFile => {
if venv_path.exists() && venv_path.is_file() {
std::fs::remove_file(&venv_path).map_err(|e| {
AppError::permission_error(format!("删除冲突文件失败: {e}"), &venv_path)
})?;
Ok(format!("成功删除冲突文件: {}", venv_path.display()))
} else {
Err(AppError::path_error(
"冲突文件不存在".to_string(),
&venv_path,
))
}
}
CleanupType::RemoveCorruptedVenv => {
if venv_path.exists() && venv_path.is_dir() {
self.cleanup_corrupted_venv(&venv_path).await?;
Ok(format!("成功清理损坏的虚拟环境: {}", venv_path.display()))
} else {
Err(AppError::path_error(
"虚拟环境目录不存在".to_string(),
&venv_path,
))
}
}
CleanupType::CreateBackup => {
if venv_path.exists() {
let backup_path = Path::new(&self.base_dir).join("venv.backup");
std::fs::rename(&venv_path, &backup_path).map_err(|e| {
AppError::permission_error(format!("创建备份失败: {e}"), &venv_path)
})?;
Ok(format!("成功备份虚拟环境到: {}", backup_path.display()))
} else {
Err(AppError::path_error(
"虚拟环境不存在,无需备份".to_string(),
&venv_path,
))
}
}
}
}
/// 创建Python虚拟环境带进度跟踪和增强错误处理
async fn create_python_venv_with_progress(&self) -> Result<(), AppError> {
let venv_path = Path::new(&self.base_dir).join("venv");
// 预检查:验证创建条件
self.send_progress("虚拟环境", InstallStage::Preparing, 5.0, "验证创建条件")
.await;
if let Err(e) = self.validate_venv_creation_preconditions(&venv_path).await {
self.send_progress(
"虚拟环境",
InstallStage::Failed(e.to_string()),
0.0,
"前置条件检查失败",
)
.await;
return Err(e);
}
// 检查虚拟环境是否已存在
if venv_path.exists() && venv_path.is_dir() {
// 验证现有虚拟环境是否完整
let python_exe = Self::get_venv_python_path(&venv_path);
if python_exe.exists() {
info!(
"The Python virtual environment exists and is complete: {}",
venv_path.display()
);
self.send_progress("虚拟环境", InstallStage::Completed, 100.0, "虚拟环境已存在")
.await;
return Ok(());
} else {
warn!("Corrupted virtual environment detected, attempt to clean");
self.send_progress(
"虚拟环境",
InstallStage::Preparing,
10.0,
"清理损坏的虚拟环境",
)
.await;
self.cleanup_corrupted_venv(&venv_path).await?;
}
}
info!(
"Create a Python virtual environment: {}",
venv_path.display()
);
self.send_progress(
"虚拟环境",
InstallStage::Preparing,
15.0,
"准备创建虚拟环境",
)
.await;
// 使用 uv venv venv 在当前目录下创建名为venv的虚拟环境
let create_cmd = Command::new("uv")
.arg("venv")
.arg("venv")
.arg("--python")
.arg("python3")
.current_dir(&self.base_dir)
.output();
self.send_progress("虚拟环境", InstallStage::Installing, 50.0, "创建虚拟环境")
.await;
let output = timeout(Duration::from_secs(120), create_cmd)
.await
.map_err(|_| self.handle_venv_creation_error("虚拟环境创建超时", &venv_path))?
.map_err(|e| {
self.handle_venv_creation_error(&format!("命令执行失败: {e}"), &venv_path)
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
let error_msg = if !stderr.is_empty() {
stderr.to_string()
} else if !stdout.is_empty() {
stdout.to_string()
} else {
"未知错误".to_string()
};
self.send_progress(
"虚拟环境",
InstallStage::Failed(error_msg.clone()),
0.0,
"创建失败",
)
.await;
let error = self.handle_venv_creation_error(&error_msg, &venv_path);
// 记录详细的错误信息和恢复建议
error!("Virtual environment creation failed: {}", error);
for suggestion in error.get_path_recovery_suggestions() {
error!("Recovery suggestion: {}", suggestion);
}
return Err(error);
}
self.send_progress("虚拟环境", InstallStage::Verifying, 90.0, "验证虚拟环境")
.await;
// 验证虚拟环境创建结果
if let Err(e) = self.verify_venv_creation(&venv_path).await {
self.send_progress(
"虚拟环境",
InstallStage::Failed(e.to_string()),
0.0,
"验证失败",
)
.await;
return Err(e);
}
self.send_progress(
"虚拟环境",
InstallStage::Completed,
100.0,
"虚拟环境创建完成",
)
.await;
info!("The Python virtual environment is created");
Ok(())
}
/// 验证虚拟环境创建结果
async fn verify_venv_creation(&self, venv_path: &Path) -> Result<(), AppError> {
// 检查虚拟环境目录是否存在
if !venv_path.exists() {
return Err(AppError::virtual_environment_path_error(
"虚拟环境创建后目录不存在".to_string(),
venv_path,
));
}
if !venv_path.is_dir() {
return Err(AppError::virtual_environment_path_error(
"虚拟环境路径不是目录".to_string(),
venv_path,
));
}
// 检查Python可执行文件
let python_exe = Self::get_venv_python_path(venv_path);
if !python_exe.exists() {
return Err(AppError::virtual_environment_path_error(
"虚拟环境中Python可执行文件不存在".to_string(),
&python_exe,
));
}
// 检查pip是否可用
let pip_exe = Self::get_venv_executable_path(venv_path, "pip");
if !pip_exe.exists() {
warn!(
"pip does not exist in the virtual environment, but this may be normal (using uv management package)"
);
}
// 尝试运行Python验证虚拟环境
let test_cmd = Command::new(&python_exe)
.arg("-c")
.arg("import sys; print(sys.prefix)")
.output();
match timeout(Duration::from_secs(10), test_cmd).await {
Ok(Ok(output)) if output.status.success() => {
let prefix = String::from_utf8_lossy(&output.stdout).trim().to_string();
debug!("Virtual environment Python prefix: {}", prefix);
// 验证Python前缀是否指向虚拟环境
if !prefix.contains("venv") {
warn!(
"Python prefix may not point to virtual environment: {}",
prefix
);
}
}
Ok(Ok(output)) => {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(AppError::virtual_environment_path_error(
format!("虚拟环境Python测试失败: {stderr}"),
&python_exe,
));
}
Ok(Err(e)) => {
return Err(AppError::virtual_environment_path_error(
format!("无法执行虚拟环境Python: {e}"),
&python_exe,
));
}
Err(_) => {
return Err(AppError::virtual_environment_path_error(
"虚拟环境Python测试超时".to_string(),
&python_exe,
));
}
}
Ok(())
}
/// 安装MinerU带进度跟踪
async fn install_mineru_with_progress(&self) -> Result<(), AppError> {
info!("Anso MinerU");
self.send_progress("MinerU", InstallStage::Preparing, 0.0, "准备安装MinerU")
.await;
// 检测是否在中国大陆,如果是则使用国内镜像
let is_china = self.is_china_region().await;
// 检查CUDA环境状态决定安装哪个版本的MinerU
let cuda_status = self.check_cuda_environment().await;
let mineru_package = match cuda_status {
Ok(cuda_info) if cuda_info.available && !cuda_info.devices.is_empty() => {
info!("CUDA environment detected, install mineru[all] to support GPU acceleration");
"mineru[all]"
}
_ => {
info!("CUDA environment not detected, install mineru[core] (CPU version only)");
"mineru[core]"
}
};
let venv_path = Path::new(&self.base_dir).join("venv");
let python_path = Self::get_venv_python_path(&venv_path);
let mut install_cmd = Command::new("uv");
install_cmd
.arg("pip")
.arg("install")
.arg("-U")
.arg("--python")
.arg(&python_path)
.arg(mineru_package);
// 如果在中国大陆,添加镜像配置
if is_china {
info!("Mainland China environment detected, using Alibaba Cloud mirror source");
install_cmd
.arg("-i")
.arg("https://mirrors.aliyun.com/pypi/simple/")
.arg("--trusted-host")
.arg("mirrors.aliyun.com");
}
//install_cmd 命令打印
info!("mineru installation command={:?}", &install_cmd);
let install_cmd = install_cmd.output();
self.send_progress("MinerU", InstallStage::Downloading, 20.0, "下载MinerU包")
.await;
let output = timeout(Duration::from_secs(900), install_cmd)
.await
.map_err(|_| AppError::Environment("MinerU安装超时".to_string()))?
.map_err(|e| AppError::Environment(format!("安装MinerU失败: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
self.send_progress(
"MinerU",
InstallStage::Failed(stderr.to_string()),
0.0,
"安装失败",
)
.await;
return Err(AppError::Environment(format!("MinerU安装失败: {stderr}")));
}
self.send_progress("MinerU", InstallStage::Configuring, 80.0, "配置MinerU环境")
.await;
// 如果在中国大陆,配置模型源
if is_china {
if let Err(e) = self.configure_mineru_model_source().await {
warn!("Failed to configure MinerU model source: {}", e);
// 不阻断安装流程,只记录警告
}
}
self.send_progress("MinerU", InstallStage::Verifying, 90.0, "验证MinerU安装")
.await;
// 验证安装
match self.check_mineru_environment().await {
Ok(_) => {
self.send_progress("MinerU", InstallStage::Completed, 100.0, "MinerU安装完成")
.await;
info!("MinerU installation completed");
Ok(())
}
Err(e) => {
self.send_progress(
"MinerU",
InstallStage::Failed(e.to_string()),
0.0,
"验证失败",
)
.await;
Err(AppError::Environment(format!("MinerU安装验证失败: {e}")))
}
}
}
/// 安装MarkItDown带进度跟踪
async fn install_markitdown_with_progress(&self) -> Result<(), AppError> {
info!("InstallMarkItDown");
self.send_progress(
"MarkItDown",
InstallStage::Preparing,
0.0,
"准备安装MarkItDown",
)
.await;
// 检测是否在中国大陆,如果是则使用国内镜像
let is_china = self.is_china_region().await;
let venv_path = Path::new(&self.base_dir).join("venv");
let python_path = Self::get_venv_python_path(&venv_path);
let mut install_cmd = Command::new("uv");
install_cmd
.arg("pip")
.arg("install")
.arg("--python")
.arg(&python_path)
.arg("markitdown");
// 如果在中国大陆,添加镜像配置
if is_china {
info!("Mainland China environment detected, using domestic mirror source");
install_cmd
.arg("-i")
.arg("https://mirrors.aliyun.com/pypi/simple/")
.arg("--trusted-host")
.arg("mirrors.aliyun.com");
}
let install_cmd = install_cmd.output();
self.send_progress(
"MarkItDown",
InstallStage::Downloading,
20.0,
"下载MarkItDown包",
)
.await;
let output = timeout(Duration::from_secs(600), install_cmd)
.await
.map_err(|_| AppError::Environment("MarkItDown安装超时".to_string()))?
.map_err(|e| AppError::Environment(format!("安装MarkItDown失败: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
self.send_progress(
"MarkItDown",
InstallStage::Failed(stderr.to_string()),
0.0,
"安装失败",
)
.await;
return Err(AppError::Environment(format!(
"MarkItDown安装失败: {stderr}"
)));
}
self.send_progress(
"MarkItDown",
InstallStage::Verifying,
90.0,
"验证MarkItDown安装",
)
.await;
// 验证安装
match self.check_markitdown_environment().await {
Ok(_) => {
self.send_progress(
"MarkItDown",
InstallStage::Completed,
100.0,
"MarkItDown安装完成",
)
.await;
info!("MarkItDown installation completed");
Ok(())
}
Err(e) => {
self.send_progress(
"MarkItDown",
InstallStage::Failed(e.to_string()),
0.0,
"验证失败",
)
.await;
Err(AppError::Environment(format!(
"MarkItDown安装验证失败: {e}"
)))
}
}
}
/// 检测是否在中国大陆地区
async fn is_china_region(&self) -> bool {
// 检查时区
if let Ok(tz) = std::env::var("TZ") {
if tz.contains("Asia/Shanghai") || tz.contains("Asia/Beijing") {
return true;
}
}
// 检查语言环境
if let Ok(lang) = std::env::var("LANG") {
if lang.contains("zh_CN") {
return true;
}
}
// 检查系统语言macOS
if let Ok(output) = Command::new("defaults")
.arg("read")
.arg("-g")
.arg("AppleLanguages")
.output()
.await
{
let output_str = String::from_utf8_lossy(&output.stdout);
if output_str.contains("zh-Hans") || output_str.contains("zh-CN") {
return true;
}
}
// 尝试ping测试简单的网络检测
if let Ok(output) = Command::new("ping")
.arg("-c")
.arg("1")
.arg("-W")
.arg("3000")
.arg("baidu.com")
.output()
.await
{
if output.status.success() {
return true;
}
}
false
}
/// 配置MinerU模型源为ModelScope中国大陆
async fn configure_mineru_model_source(&self) -> Result<(), AppError> {
info!("Configure MinerU to use the ModelScope model source");
// 创建配置目录
let home_dir = std::env::var("HOME")
.map_err(|_| AppError::Environment("无法获取HOME目录".to_string()))?;
let config_dir = format!("{home_dir}/.mineru");
if let Err(e) = std::fs::create_dir_all(&config_dir) {
warn!("Failed to create MinerU configuration directory: {}", e);
}
// 创建配置文件内容
let config_content = r#"{
"model_source": "modelscope",
"default_source": "modelscope"
}"#;
let config_file = format!("{config_dir}/config.json");
if let Err(e) = std::fs::write(&config_file, config_content) {
return Err(AppError::Environment(format!(
"写入MinerU配置文件失败: {e}"
)));
}
info!(
"MinerU model source configuration completed: {}",
config_file
);
Ok(())
}
/// 验证环境完整性
#[instrument(skip(self))]
pub async fn validate_environment(&self) -> Result<bool, AppError> {
let status = self.check_environment().await?;
let is_valid = status.is_ready();
let health_score = status.health_score();
if !is_valid {
warn!(
"Environment verification failed, health score: {}/100",
health_score
);
for issue in status.get_critical_issues() {
error!(
"Key questions [{}]: {} - {}",
issue.component, issue.message, issue.suggestion
);
}
} else {
info!(
"Environmental verification passed, health score: {}/100",
health_score
);
}
Ok(is_valid)
}
/// 自动激活虚拟环境(如果存在且未激活)
#[instrument(skip(self))]
pub async fn auto_activate_virtual_environment(&self) -> Result<(), AppError> {
let venv_path = Path::new(&self.base_dir).join("venv");
// 检查虚拟环境是否存在
if !venv_path.exists() {
debug!(
"The virtual environment does not exist: {}",
venv_path.display()
);
return Ok(());
}
// 检查虚拟环境是否已经激活
if let Ok(virtual_env) = std::env::var("VIRTUAL_ENV") {
if virtual_env == venv_path.to_string_lossy() {
debug!(
"The virtual environment has been activated: {}",
virtual_env
);
return Ok(());
}
}
// 检查Python可执行文件是否存在
let python_exe = Self::get_venv_python_path(&venv_path);
if !python_exe.exists() {
debug!(
"The Python executable file in the virtual environment does not exist: {}",
python_exe.display()
);
return Ok(());
}
// 设置环境变量以模拟虚拟环境激活
info!(
"Automatically activate virtual environment: {}",
venv_path.display()
);
// 计算虚拟环境bin目录路径
let venv_bin_path = if cfg!(windows) {
venv_path.join("Scripts").to_string_lossy().to_string()
} else {
venv_path.join("bin").to_string_lossy().to_string()
};
// 设置环境变量
unsafe {
std::env::set_var("VIRTUAL_ENV", venv_path.to_string_lossy().to_string());
// 更新PATH环境变量将虚拟环境的bin目录放在前面
let current_path = std::env::var("PATH").unwrap_or_default();
let new_path = if cfg!(windows) {
format!("{venv_bin_path};{current_path}")
} else {
format!("{venv_bin_path}:{current_path}")
};
std::env::set_var("PATH", new_path);
// 设置Python相关环境变量
std::env::set_var("PYTHONPATH", venv_path.to_string_lossy().to_string());
}
info!(
"The virtual environment has been automatically activated, Python path: {}",
python_exe.display()
);
debug!(
"VIRTUAL_ENV: {}",
std::env::var("VIRTUAL_ENV").unwrap_or_default()
);
debug!("PATH prefix: {}", venv_bin_path);
Ok(())
}
/// 生成详细环境报告
#[instrument(skip(self))]
pub async fn generate_environment_report(&self) -> Result<String, AppError> {
let status = self.check_environment().await?;
let mut report = String::new();
// 标题和概览
report.push_str("=== 环境检查报告 ===\n");
report.push_str(&format!("检查时间: {:?}\n", status.last_checked));
report.push_str(&format!("检查耗时: {:?}\n", status.check_duration));
report.push_str(&format!("健康评分: {}/100\n", status.health_score()));
report.push_str(&format!(
"环境状态: {}\n\n",
if status.is_ready() {
"就绪"
} else {
"未就绪"
}
));
// 组件状态
report.push_str("=== 组件状态 ===\n");
report.push_str(&format!(
"Python: {} ({:?})\n",
if status.python_available {
""
} else {
""
},
status.python_version.as_deref().unwrap_or("未知")
));
if status.virtual_env_active {
report.push_str(&format!(
" 虚拟环境: ✓ ({:?})\n",
status.virtual_env_path.as_deref().unwrap_or("未知路径")
));
}
report.push_str(&format!(
"uv工具: {} ({:?})\n",
if status.uv_available { "" } else { "" },
status.uv_version.as_deref().unwrap_or("未安装")
));
report.push_str(&format!(
"CUDA: {} ({:?})\n",
if status.cuda_available { "" } else { "" },
status.cuda_version.as_deref().unwrap_or("不可用")
));
if !status.cuda_devices.is_empty() {
report.push_str(" CUDA设备:\n");
for device in &status.cuda_devices {
report.push_str(&format!(
" - GPU {}: {} ({}MB 可用)\n",
device.id,
device.name,
device.memory_free / 1024 / 1024
));
}
}
report.push_str(&format!(
"MinerU: {} ({:?})\n",
if status.mineru_available {
""
} else {
""
},
status.mineru_version.as_deref().unwrap_or("未安装")
));
report.push_str(&format!(
"MarkItDown: {} ({:?})\n",
if status.markitdown_available {
""
} else {
""
},
status.markitdown_version.as_deref().unwrap_or("未安装")
));
// 问题列表
if !status.issues.is_empty() {
report.push_str("\n=== 问题列表 ===\n");
for issue in &status.issues {
let severity_icon = match issue.severity {
IssueSeverity::Critical => "🔴",
IssueSeverity::High => "🟠",
IssueSeverity::Medium => "🟡",
IssueSeverity::Low => "🔵",
};
report.push_str(&format!(
"{} [{}] {}: {}\n",
severity_icon, issue.component, issue.message, issue.suggestion
));
if issue.auto_fixable {
report.push_str(" ↳ 可自动修复\n");
}
}
}
// 警告列表
if !status.warnings.is_empty() {
report.push_str("\n=== 警告列表 ===\n");
for warning in &status.warnings {
report.push_str(&format!(
"⚠️ [{}] {}\n",
warning.component, warning.message
));
report.push_str(&format!(" 影响: {}\n", warning.impact));
}
}
// 建议
report.push_str("\n=== 建议 ===\n");
if status.is_ready() {
report.push_str("✅ 环境配置良好,可以正常使用文档解析服务\n");
} else {
let auto_fixable = status.get_auto_fixable_issues();
if !auto_fixable.is_empty() {
report.push_str("🔧 可以运行自动修复来解决以下问题:\n");
for issue in auto_fixable {
report.push_str(&format!(" - {}: {}\n", issue.component, issue.suggestion));
}
}
let critical_issues = status.get_critical_issues();
if !critical_issues.is_empty() {
report.push_str("❌ 需要手动解决以下关键问题:\n");
for issue in critical_issues {
if !issue.auto_fixable {
report
.push_str(&format!(" - {}: {}\n", issue.component, issue.suggestion));
}
}
}
}
Ok(report)
}
/// 获取环境摘要信息
pub async fn get_environment_summary(&self) -> Result<String, AppError> {
let status = self.check_environment().await?;
Ok(format!(
"环境状态: {} | 健康评分: {}/100 | Python: {} | MinerU: {} | MarkItDown: {} | CUDA: {}",
if status.is_ready() {
"就绪"
} else {
"未就绪"
},
status.health_score(),
if status.python_available {
""
} else {
""
},
if status.mineru_available {
""
} else {
""
},
if status.markitdown_available {
""
} else {
""
},
if status.cuda_available { "" } else { "" }
))
}
/// 检查当前目录是否适合创建虚拟环境(公共接口)
pub async fn check_current_directory_readiness(
&self,
) -> Result<DirectoryValidationResult, AppError> {
self.validate_current_directory_setup().await
}
/// 获取目录验证报告的格式化字符串
pub async fn get_directory_validation_report(&self) -> Result<String, AppError> {
let result = self.validate_current_directory_setup().await?;
Ok(self.format_directory_validation_report(&result))
}
/// 格式化目录验证报告
fn format_directory_validation_report(&self, result: &DirectoryValidationResult) -> String {
let mut report = String::new();
report.push_str("=== 当前目录验证报告 ===\n");
report.push_str(&format!("目录: {}\n", result.current_directory.display()));
report.push_str(&format!("虚拟环境路径: {}\n", result.venv_path.display()));
report.push_str(&format!(
"验证状态: {}\n\n",
if result.is_valid {
"✓ 通过"
} else {
"✗ 失败"
}
));
if !result.issues.is_empty() {
report.push_str("=== 发现的问题 ===\n");
for (i, issue) in result.issues.iter().enumerate() {
report.push_str(&format!(
"{}. [{}] {}\n",
i + 1,
format!("{:?}", issue.severity).to_uppercase(),
issue.message
));
report.push_str(&format!(" 建议: {}\n", issue.fix_suggestion));
if issue.auto_fixable {
report.push_str(" 状态: 可自动修复\n");
}
report.push('\n');
}
}
if !result.warnings.is_empty() {
report.push_str("=== 警告信息 ===\n");
for (i, warning) in result.warnings.iter().enumerate() {
report.push_str(&format!("{}. {}\n", i + 1, warning.message));
report.push_str(&format!(" 影响: {}\n\n", warning.impact));
}
}
if !result.cleanup_options.is_empty() {
report.push_str("=== 可用的清理选项 ===\n");
for (i, option) in result.cleanup_options.iter().enumerate() {
report.push_str(&format!(
"{}. {} (风险: {:?})\n",
i + 1,
option.description,
option.risk_level
));
report.push_str(&format!(" 命令: {}\n\n", option.command));
}
}
if !result.recommendations.is_empty() {
report.push_str("=== 推荐操作 ===\n");
for (i, recommendation) in result.recommendations.iter().enumerate() {
report.push_str(&format!("{}. {}\n", i + 1, recommendation));
}
}
report
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[tokio::test]
async fn test_environment_manager_creation() {
let temp_dir = TempDir::new().unwrap();
let manager = EnvironmentManager::new(
"/usr/bin/python3".to_string(),
temp_dir.path().to_string_lossy().to_string(),
);
// 基本创建测试
assert_eq!(manager.python_path, "/usr/bin/python3");
assert_eq!(manager.retry_config.max_attempts, 3);
assert_eq!(manager.cache_ttl, Duration::from_secs(300));
}
#[tokio::test]
#[ignore = "Depends on global current directory, fails when other tests change it"]
async fn test_for_current_directory_factory() {
// 测试当前目录工厂方法
let manager = EnvironmentManager::for_current_directory();
assert!(manager.is_ok());
let manager = manager.unwrap();
// 验证路径设置正确
let current_dir = std::env::current_dir().unwrap();
assert_eq!(manager.base_dir, current_dir.to_string_lossy().to_string());
// 验证Python路径根据平台正确设置
let expected_python_path =
EnvironmentManager::get_venv_python_path(&current_dir.join("venv"));
assert_eq!(
manager.python_path,
expected_python_path.to_string_lossy().to_string()
);
// 验证默认配置
assert_eq!(manager.retry_config.max_attempts, 3);
assert_eq!(manager.cache_ttl, Duration::from_secs(300));
assert!(manager.progress_sender.is_none());
}
#[tokio::test]
#[ignore = "Depends on global current directory, fails when other tests change it"]
async fn test_for_current_directory_with_progress_factory() {
let (tx, _rx) = mpsc::unbounded_channel();
// 测试带进度跟踪的当前目录工厂方法
let manager = EnvironmentManager::for_current_directory_with_progress(tx);
assert!(manager.is_ok());
let manager = manager.unwrap();
// 验证路径设置正确
let current_dir = std::env::current_dir().unwrap();
assert_eq!(manager.base_dir, current_dir.to_string_lossy().to_string());
// 验证Python路径根据平台正确设置
let expected_python_path =
EnvironmentManager::get_venv_python_path(&current_dir.join("venv"));
assert_eq!(
manager.python_path,
expected_python_path.to_string_lossy().to_string()
);
// 验证进度发送器已设置
assert!(manager.progress_sender.is_some());
}
#[tokio::test]
async fn test_environment_check() {
let temp_dir = TempDir::new().unwrap();
let manager = EnvironmentManager::new(
"python3".to_string(),
temp_dir.path().to_string_lossy().to_string(),
);
// 环境检查不应该失败(即使某些工具不可用)
let result = manager.check_environment().await;
assert!(result.is_ok());
let status = result.unwrap();
assert!(status.health_score() <= 100);
}
#[tokio::test]
async fn test_uv_availability_check() {
let temp_dir = TempDir::new().unwrap();
let env_manager = EnvironmentManager::new(
"python3".to_string(),
temp_dir.path().to_string_lossy().to_string(),
);
// 测试UV可用性检查
let result = env_manager.is_uv_available().await;
assert!(result.is_ok());
// 检查返回的状态类型
match result.unwrap() {
UvAvailabilityStatus::Available {
version,
compatibility,
} => {
assert!(!version.is_empty());
assert!(!compatibility.minimum_version.is_empty());
assert!(!compatibility.current_version.is_empty());
}
UvAvailabilityStatus::IncompatibleVersion { version, issue } => {
assert!(!version.is_empty());
assert!(!issue.is_empty());
}
UvAvailabilityStatus::ExecutionFailed { error } => {
assert!(!error.is_empty());
}
UvAvailabilityStatus::NotInstalled { error } => {
assert!(!error.is_empty());
}
}
}
#[tokio::test]
async fn test_uv_version_parsing() {
let temp_dir = TempDir::new().unwrap();
let env_manager = EnvironmentManager::new(
"python3".to_string(),
temp_dir.path().to_string_lossy().to_string(),
);
// 测试版本解析
assert_eq!(
env_manager.extract_uv_version("uv 0.4.15"),
Some((0, 4, 15))
);
assert_eq!(env_manager.extract_uv_version("0.4.15"), Some((0, 4, 15)));
assert_eq!(env_manager.extract_uv_version("1.0.0"), Some((1, 0, 0)));
assert_eq!(env_manager.extract_uv_version("invalid"), None);
// 测试版本兼容性检查
let compatibility = env_manager.check_uv_version_compatibility("uv 0.4.15");
assert!(compatibility.is_ok());
let compat = compatibility.unwrap();
assert!(compat.is_compatible); // 0.4.15 >= 0.1.0
assert_eq!(compat.minimum_version, "0.1.0");
assert_eq!(compat.current_version, "uv 0.4.15");
}
#[tokio::test]
async fn test_uv_installation_method_detection() {
let temp_dir = TempDir::new().unwrap();
let env_manager = EnvironmentManager::new(
"python3".to_string(),
temp_dir.path().to_string_lossy().to_string(),
);
// 测试安装方法检测
let method = env_manager.determine_best_uv_installation_method().await;
// 确保返回了一个有效的安装方法
match method {
UvInstallationMethod::CurlScript
| UvInstallationMethod::PowerShellScript
| UvInstallationMethod::PipInstall
| UvInstallationMethod::SystemPackageManager => {
// 所有方法都是有效的
}
}
}
#[tokio::test]
async fn test_retry_config() {
let temp_dir = TempDir::new().unwrap();
let retry_config = RetryConfig {
max_attempts: 5,
base_delay: Duration::from_millis(100),
max_delay: Duration::from_secs(10),
backoff_multiplier: 1.5,
};
let manager = EnvironmentManager::new(
"python3".to_string(),
temp_dir.path().to_string_lossy().to_string(),
)
.with_retry_config(retry_config.clone());
assert_eq!(manager.retry_config.max_attempts, 5);
assert_eq!(manager.retry_config.backoff_multiplier, 1.5);
}
#[tokio::test]
async fn test_environment_status_health_score() {
let mut status = EnvironmentStatus::default();
// 初始状态应该得分很低
assert_eq!(status.health_score(), 0);
// 添加基础组件
status.python_available = true;
status.mineru_available = true;
status.markitdown_available = true;
assert_eq!(status.health_score(), 60);
// 添加工具支持
status.uv_available = true;
status.virtual_env_active = true;
assert_eq!(status.health_score(), 80);
// 添加CUDA支持
status.cuda_available = true;
status.cuda_devices.push(CudaDevice {
id: 0,
name: "Test GPU".to_string(),
memory_total: 8 * 1024 * 1024 * 1024,
memory_free: 4 * 1024 * 1024 * 1024,
compute_capability: "8.6".to_string(),
});
assert_eq!(status.health_score(), 90);
}
#[tokio::test]
async fn test_cross_platform_path_functions() {
use std::path::Path;
let venv_path = Path::new("test_venv");
// 测试Python路径生成
let python_path = EnvironmentManager::get_venv_python_path(venv_path);
if cfg!(windows) {
assert_eq!(python_path, venv_path.join("Scripts").join("python.exe"));
} else {
assert_eq!(python_path, venv_path.join("bin").join("python"));
}
// 测试可执行文件路径生成
let mineru_path = EnvironmentManager::get_venv_executable_path(venv_path, "mineru");
if cfg!(windows) {
assert_eq!(mineru_path, venv_path.join("Scripts").join("mineru.exe"));
} else {
assert_eq!(mineru_path, venv_path.join("bin").join("mineru"));
}
// 测试激活脚本路径
let activation_script = EnvironmentManager::get_venv_activation_script(venv_path);
if cfg!(windows) {
assert_eq!(
activation_script,
venv_path.join("Scripts").join("activate.bat")
);
} else {
assert_eq!(activation_script, venv_path.join("bin").join("activate"));
}
// 测试系统Python可执行文件列表
let python_executables = EnvironmentManager::get_system_python_executable();
assert!(!python_executables.is_empty());
if cfg!(windows) {
assert!(python_executables.contains(&"python.exe".to_string()));
assert!(python_executables.contains(&"python3.exe".to_string()));
} else {
assert!(python_executables.contains(&"python3".to_string()));
assert!(python_executables.contains(&"python".to_string()));
}
}
#[tokio::test]
async fn test_cross_platform_environment_variables() {
let temp_dir = tempfile::TempDir::new().unwrap();
let manager = EnvironmentManager::new(
"python3".to_string(),
temp_dir.path().to_string_lossy().to_string(),
);
let venv_path = temp_dir.path().join("venv");
let env_vars = manager.get_cross_platform_env_vars(&venv_path);
// 验证VIRTUAL_ENV变量
assert_eq!(
env_vars.get("VIRTUAL_ENV").unwrap(),
&venv_path.to_string_lossy().to_string()
);
// 验证PATH变量包含正确的路径
let path_var = env_vars.get("PATH").unwrap();
if cfg!(windows) {
assert!(path_var.contains(&venv_path.join("Scripts").to_string_lossy().to_string()));
} else {
assert!(path_var.contains(&venv_path.join("bin").to_string_lossy().to_string()));
}
}
#[tokio::test]
async fn test_virtual_environment_activation_commands() {
let status = EnvironmentStatus::default();
// 测试基本激活命令
let activation_cmd = status.get_activation_command();
if cfg!(windows) {
assert_eq!(activation_cmd, ".\\venv\\Scripts\\activate.bat");
} else {
assert_eq!(activation_cmd, "source ./venv/bin/activate");
}
// 测试PowerShell激活命令仅Windows
let powershell_cmd = status.get_powershell_activation_command();
if cfg!(windows) {
assert_eq!(
powershell_cmd,
Some(".\\venv\\Scripts\\Activate.ps1".to_string())
);
} else {
assert_eq!(powershell_cmd, None);
}
}
#[tokio::test]
async fn test_virtual_environment_info() {
let temp_dir = tempfile::TempDir::new().unwrap();
let manager = EnvironmentManager::new(
"python3".to_string(),
temp_dir.path().to_string_lossy().to_string(),
);
let venv_path = temp_dir.path().join("venv");
// 测试虚拟环境信息获取(即使虚拟环境不存在)
let venv_info_result = manager.get_virtual_environment_info(&venv_path).await;
assert!(venv_info_result.is_ok());
let venv_info = venv_info_result.unwrap();
assert_eq!(venv_info.path, venv_path);
assert!(!venv_info.is_valid); // 因为虚拟环境不存在
// 验证平台特定路径
if cfg!(windows) {
assert_eq!(
venv_info.python_executable,
venv_path.join("Scripts").join("python.exe")
);
assert_eq!(
venv_info.pip_executable,
venv_path.join("Scripts").join("pip.exe")
);
assert_eq!(
venv_info.activation_script,
venv_path.join("Scripts").join("activate.bat")
);
assert_eq!(venv_info.platform, "windows");
} else {
assert_eq!(
venv_info.python_executable,
venv_path.join("bin").join("python")
);
assert_eq!(venv_info.pip_executable, venv_path.join("bin").join("pip"));
assert_eq!(
venv_info.activation_script,
venv_path.join("bin").join("activate")
);
assert_eq!(venv_info.platform, "unix");
}
}
#[tokio::test]
async fn test_system_python_detection() {
let temp_dir = tempfile::TempDir::new().unwrap();
let manager = EnvironmentManager::new(
"python3".to_string(),
temp_dir.path().to_string_lossy().to_string(),
);
// 测试系统Python查找
let system_python = manager.find_system_python().await;
// 注意这个测试可能在某些环境中失败如果系统没有安装Python
// 但我们至少可以验证函数不会panic
if let Some(python_exe) = system_python {
assert!(!python_exe.is_empty());
// 验证返回的是我们期望的可执行文件名之一
let expected_names = EnvironmentManager::get_system_python_executable();
assert!(expected_names.contains(&python_exe));
}
}
}