mxivideo/src-tauri/src/commands/python_env_manager.rs

393 lines
13 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.

/**
* Python环境管理命令
* 用于管理Python环境和依赖
*/
use serde::{Deserialize, Serialize};
use tauri::{command, AppHandle};
use std::process::Command;
use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize)]
pub struct PythonEnvInfo {
pub python_type: String,
pub python_path: Option<String>,
pub version: Option<String>,
pub is_available: bool,
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PythonPackage {
pub name: String,
pub version: String,
pub location: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PythonEnvStatus {
pub embedded: PythonEnvInfo,
pub system: PythonEnvInfo,
pub current_env: String,
pub packages: Vec<PythonPackage>,
}
/// 获取嵌入式Python路径
fn get_embedded_python_path() -> Option<PathBuf> {
let python_exe = if cfg!(target_os = "windows") {
"python.exe"
} else {
"python3"
};
let possible_paths = vec![
std::env::current_dir().ok()
.map(|p| p.join("src-tauri").join("python-embed").join(python_exe)),
std::env::current_exe().ok()
.and_then(|p| p.parent().map(|p| p.join("python-embed").join(python_exe))),
];
for path in possible_paths.into_iter().flatten() {
if path.exists() {
return Some(path);
}
}
None
}
/// 检查Python环境信息
fn check_python_env(python_path: &str) -> PythonEnvInfo {
let mut cmd = Command::new(python_path);
cmd.arg("--version");
match cmd.output() {
Ok(output) => {
if output.status.success() {
let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
PythonEnvInfo {
python_type: if python_path.contains("python-embed") { "embedded".to_string() } else { "system".to_string() },
python_path: Some(python_path.to_string()),
version: Some(version),
is_available: true,
error: None,
}
} else {
let error = String::from_utf8_lossy(&output.stderr).trim().to_string();
PythonEnvInfo {
python_type: if python_path.contains("python-embed") { "embedded".to_string() } else { "system".to_string() },
python_path: Some(python_path.to_string()),
version: None,
is_available: false,
error: Some(error),
}
}
}
Err(e) => PythonEnvInfo {
python_type: if python_path.contains("python-embed") { "embedded".to_string() } else { "system".to_string() },
python_path: Some(python_path.to_string()),
version: None,
is_available: false,
error: Some(e.to_string()),
}
}
}
/// 获取已安装的Python包列表
fn get_installed_packages(python_path: &str) -> Vec<PythonPackage> {
let mut cmd = Command::new(python_path);
cmd.args(&["-m", "pip", "list", "--format=json"]);
match cmd.output() {
Ok(output) => {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
match serde_json::from_str::<Vec<serde_json::Value>>(&stdout) {
Ok(packages) => {
packages.into_iter().filter_map(|pkg| {
let name = pkg.get("name")?.as_str()?.to_string();
let version = pkg.get("version")?.as_str()?.to_string();
Some(PythonPackage {
name,
version,
location: None,
})
}).collect()
}
Err(_) => Vec::new(),
}
} else {
Vec::new()
}
}
Err(_) => Vec::new(),
}
}
/// 获取Python环境状态
#[command]
pub async fn get_python_env_status(_app: AppHandle) -> Result<PythonEnvStatus, String> {
println!("Getting Python environment status...");
// 检查嵌入式Python
let embedded_info = if let Some(embedded_path) = get_embedded_python_path() {
check_python_env(&embedded_path.to_string_lossy())
} else {
PythonEnvInfo {
python_type: "embedded".to_string(),
python_path: None,
version: None,
is_available: false,
error: Some("Embedded Python not found".to_string()),
}
};
// 检查系统Python
let system_info = check_python_env("python");
// 确定当前使用的环境
let current_env = if embedded_info.is_available {
"embedded".to_string()
} else if system_info.is_available {
"system".to_string()
} else {
"none".to_string()
};
// 获取当前环境的包列表
let packages = if embedded_info.is_available {
if let Some(ref path) = embedded_info.python_path {
get_installed_packages(path)
} else {
Vec::new()
}
} else if system_info.is_available {
get_installed_packages("python")
} else {
Vec::new()
};
Ok(PythonEnvStatus {
embedded: embedded_info,
system: system_info,
current_env,
packages,
})
}
/// 安装Python包
#[command]
pub async fn install_python_package(_app: AppHandle, package_name: String) -> Result<String, String> {
println!("Installing Python package: {}", package_name);
// 确定使用哪个Python
let python_path = if let Some(embedded_path) = get_embedded_python_path() {
embedded_path.to_string_lossy().to_string()
} else {
"python".to_string()
};
let mut cmd = Command::new(&python_path);
cmd.args(&["-m", "pip", "install", &package_name]);
match cmd.output() {
Ok(output) => {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
Ok(format!("Successfully installed {}: {}", package_name, stdout))
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("Failed to install {}: {}", package_name, stderr))
}
}
Err(e) => Err(format!("Failed to execute pip install: {}", e))
}
}
/// 卸载Python包
#[command]
pub async fn uninstall_python_package(_app: AppHandle, package_name: String) -> Result<String, String> {
println!("Uninstalling Python package: {}", package_name);
// 确定使用哪个Python
let python_path = if let Some(embedded_path) = get_embedded_python_path() {
embedded_path.to_string_lossy().to_string()
} else {
"python".to_string()
};
let mut cmd = Command::new(&python_path);
cmd.args(&["-m", "pip", "uninstall", "-y", &package_name]);
match cmd.output() {
Ok(output) => {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
Ok(format!("Successfully uninstalled {}: {}", package_name, stdout))
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("Failed to uninstall {}: {}", package_name, stderr))
}
}
Err(e) => Err(format!("Failed to execute pip uninstall: {}", e))
}
}
/// 升级Python包
#[command]
pub async fn upgrade_python_package(_app: AppHandle, package_name: String) -> Result<String, String> {
println!("Upgrading Python package: {}", package_name);
// 确定使用哪个Python
let python_path = if let Some(embedded_path) = get_embedded_python_path() {
embedded_path.to_string_lossy().to_string()
} else {
"python".to_string()
};
let mut cmd = Command::new(&python_path);
cmd.args(&["-m", "pip", "install", "--upgrade", &package_name]);
match cmd.output() {
Ok(output) => {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
Ok(format!("Successfully upgraded {}: {}", package_name, stdout))
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("Failed to upgrade {}: {}", package_name, stderr))
}
}
Err(e) => Err(format!("Failed to execute pip upgrade: {}", e))
}
}
/// 设置嵌入式Python环境
#[command]
pub async fn setup_embedded_python(_app: AppHandle) -> Result<String, String> {
println!("Setting up embedded Python environment...");
// 尝试多个可能的脚本路径
let possible_script_paths = vec![
// 开发环境:从项目根目录
std::env::current_dir().ok()
.map(|p| p.join("scripts").join("setup_embedded_python.py")),
// 相对路径
Some(std::path::PathBuf::from("scripts/setup_embedded_python.py")),
Some(std::path::PathBuf::from("./scripts/setup_embedded_python.py")),
// 绝对路径如果在scripts目录中运行
Some(std::path::PathBuf::from("setup_embedded_python.py")),
];
let mut script_path = None;
for path in possible_script_paths.into_iter().flatten() {
println!("Checking script path: {:?}", path);
if path.exists() {
script_path = Some(path);
break;
}
}
let script_path = script_path.ok_or_else(|| {
let current_dir = std::env::current_dir().unwrap_or_default();
format!(
"Setup script not found. Current directory: {:?}. Please ensure scripts/setup_embedded_python.py exists.",
current_dir
)
})?;
println!("Using script path: {:?}", script_path);
// 尝试多个Python命令
let python_commands = vec!["python", "python3", "py"];
let mut last_error = String::new();
for python_cmd in python_commands {
println!("Trying Python command: {}", python_cmd);
let mut cmd = Command::new(python_cmd);
cmd.arg(&script_path);
cmd.current_dir(std::env::current_dir().unwrap_or_default());
match cmd.output() {
Ok(output) => {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
return Ok(format!("Embedded Python setup completed using {}: {}", python_cmd, stdout));
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
last_error = format!("Failed with {}: {}", python_cmd, stderr);
println!("{}", last_error);
}
}
Err(e) => {
last_error = format!("Failed to execute {} {}: {}", python_cmd, script_path.display(), e);
println!("{}", last_error);
}
}
}
Err(format!("All Python commands failed. Last error: {}", last_error))
}
/// 简化的嵌入式Python设置使用批处理脚本
#[command]
pub async fn setup_embedded_python_simple(_app: AppHandle) -> Result<String, String> {
println!("Setting up embedded Python environment using batch script...");
// 查找批处理脚本
let possible_script_paths = vec![
std::env::current_dir().ok()
.map(|p| p.join("scripts").join("setup_embedded_python.bat")),
Some(std::path::PathBuf::from("scripts/setup_embedded_python.bat")),
Some(std::path::PathBuf::from("./scripts/setup_embedded_python.bat")),
];
let mut script_path = None;
for path in possible_script_paths.into_iter().flatten() {
println!("Checking batch script path: {:?}", path);
if path.exists() {
script_path = Some(path);
break;
}
}
let script_path = script_path.ok_or_else(|| {
let current_dir = std::env::current_dir().unwrap_or_default();
format!(
"Batch script not found. Current directory: {:?}. Please ensure scripts/setup_embedded_python.bat exists.",
current_dir
)
})?;
println!("Using batch script: {:?}", script_path);
// 在Windows上运行批处理脚本
#[cfg(target_os = "windows")]
{
let mut cmd = Command::new("cmd");
cmd.args(&["/C", &script_path.to_string_lossy()]);
cmd.current_dir(std::env::current_dir().unwrap_or_default());
match cmd.output() {
Ok(output) => {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if output.status.success() {
Ok(format!("Embedded Python setup completed:\nOutput: {}\nErrors: {}", stdout, stderr))
} else {
Err(format!("Batch script failed:\nOutput: {}\nErrors: {}", stdout, stderr))
}
}
Err(e) => Err(format!("Failed to execute batch script: {}", e))
}
}
// 在非Windows系统上返回错误
#[cfg(not(target_os = "windows"))]
{
Err("Batch script setup is only available on Windows. Please run the Python script manually.".to_string())
}
}