(all):增加桌面程序
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::sync::Mutex;
|
||||
use tauri::Manager;
|
||||
|
||||
struct StreamlitProcess(Mutex<Option<Child>>);
|
||||
|
||||
/// 等待指定端口可连接,最多等待 timeout_secs 秒
|
||||
fn wait_for_port(host: &str, port: u16, timeout_secs: u64) -> bool {
|
||||
let start = std::time::Instant::now();
|
||||
while start.elapsed() < std::time::Duration::from_secs(timeout_secs) {
|
||||
if std::net::TcpStream::connect((host, port)).is_ok() {
|
||||
return true;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// 停止 Streamlit 子进程
|
||||
///
|
||||
/// - Unix: 先 SIGTERM(优雅退出),等待 3 秒无响应后 SIGKILL
|
||||
/// - Windows: 直接 TerminateProcess
|
||||
fn kill_streamlit(child: &mut Child) {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// 先发 SIGTERM 请求优雅退出
|
||||
let _ = Command::new("kill")
|
||||
.arg("-TERM")
|
||||
.arg(child.id().to_string())
|
||||
.spawn();
|
||||
std::thread::sleep(std::time::Duration::from_secs(3));
|
||||
|
||||
// 尝试 wait(如果已退出则返回 Ok(Some(status)))
|
||||
if let Ok(Some(_)) = child.try_wait() {
|
||||
log::info!("Streamlit 进程已优雅退出");
|
||||
return;
|
||||
}
|
||||
log::warn!("Streamlit 进程未响应 SIGTERM,强制终止...");
|
||||
}
|
||||
|
||||
// 强制终止
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let app = tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let child = if cfg!(debug_assertions) {
|
||||
// ── 开发模式 ── 使用系统安装的 streamlit ──
|
||||
let manifest_dir =
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let project_root = manifest_dir.parent().unwrap_or(&manifest_dir);
|
||||
let script_path = project_root.join("geo_tool.py");
|
||||
|
||||
log::info!(
|
||||
"[开发模式] 启动 Streamlit: {:?}",
|
||||
script_path
|
||||
);
|
||||
|
||||
Command::new("streamlit")
|
||||
.args([
|
||||
"run",
|
||||
script_path.to_str().unwrap_or("geo_tool.py"),
|
||||
"--server.port",
|
||||
"8501",
|
||||
"--server.address",
|
||||
"127.0.0.1",
|
||||
"--server.headless",
|
||||
"true",
|
||||
"--browser.gatherUsageStats",
|
||||
"false",
|
||||
"--logger.level",
|
||||
"error",
|
||||
])
|
||||
.current_dir(project_root)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
} else {
|
||||
// ── 生产模式 ── 使用 PyInstaller 打包的独立可执行文件 ──
|
||||
let resource_dir = app
|
||||
.path()
|
||||
.resource_dir()
|
||||
.expect("无法获取资源目录");
|
||||
|
||||
let exe_name = if cfg!(target_os = "windows") {
|
||||
"geo_tool_app.exe"
|
||||
} else {
|
||||
"geo_tool_app"
|
||||
};
|
||||
let exe_path = resource_dir.join("_up_").join("dist").join(exe_name);
|
||||
|
||||
log::info!(
|
||||
"[生产模式] 启动打包应用: {:?}",
|
||||
exe_path
|
||||
);
|
||||
|
||||
let mut cmd = Command::new(&exe_path);
|
||||
cmd.current_dir(&resource_dir)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
|
||||
// Windows: 不显示控制台窗口
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000u32;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
cmd.spawn()
|
||||
};
|
||||
|
||||
match child {
|
||||
Ok(child) => {
|
||||
log::info!("Streamlit 进程已启动 (PID: {})", child.id());
|
||||
app.manage(StreamlitProcess(Mutex::new(Some(child))));
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("启动 Streamlit 失败: {}", e);
|
||||
eprintln!(
|
||||
"错误:无法启动 Streamlit 服务。请确保已安装 streamlit。\n {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 等待 Streamlit 就绪(最多 15 秒)
|
||||
if !wait_for_port("127.0.0.1", 8501, 15) {
|
||||
log::warn!("Streamlit 在 15 秒内未能就绪");
|
||||
} else {
|
||||
log::info!("Streamlit 已就绪 (127.0.0.1:8501)");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while building tauri application");
|
||||
|
||||
app.run(|app_handle, event| {
|
||||
// 窗口关闭 → 停止子进程 → 退出应用
|
||||
if let tauri::RunEvent::ExitRequested { .. } = event {
|
||||
if let Some(state) = app_handle.try_state::<StreamlitProcess>() {
|
||||
if let Ok(mut guard) = state.0.lock() {
|
||||
if let Some(ref mut child) = *guard {
|
||||
log::info!("正在停止 Streamlit 进程...");
|
||||
kill_streamlit(child);
|
||||
log::info!("Streamlit 进程已停止");
|
||||
}
|
||||
}
|
||||
}
|
||||
// 确保应用进程完全退出
|
||||
std::process::exit(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::sync::Mutex;
|
||||
use tauri::Manager;
|
||||
|
||||
struct StreamlitProcess(Mutex<Option<Child>>);
|
||||
|
||||
/// 等待指定端口可连接,最多等待 timeout_secs 秒
|
||||
fn wait_for_port(host: &str, port: u16, timeout_secs: u64) -> bool {
|
||||
let start = std::time::Instant::now();
|
||||
while start.elapsed() < std::time::Duration::from_secs(timeout_secs) {
|
||||
if std::net::TcpStream::connect((host, port)).is_ok() {
|
||||
return true;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// 停止 Streamlit 子进程
|
||||
///
|
||||
/// - Unix: 先 SIGTERM(优雅退出),等待 3 秒无响应后 SIGKILL
|
||||
/// - Windows: 直接 TerminateProcess
|
||||
fn kill_streamlit(child: &mut Child) {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// 先发 SIGTERM 请求优雅退出
|
||||
let _ = Command::new("kill")
|
||||
.arg("-TERM")
|
||||
.arg(child.id().to_string())
|
||||
.spawn();
|
||||
std::thread::sleep(std::time::Duration::from_secs(3));
|
||||
|
||||
// 尝试 wait(如果已退出则返回 Ok(Some(status)))
|
||||
if let Ok(Some(_)) = child.try_wait() {
|
||||
log::info!("Streamlit 进程已优雅退出");
|
||||
return;
|
||||
}
|
||||
log::warn!("Streamlit 进程未响应 SIGTERM,强制终止...");
|
||||
}
|
||||
|
||||
// 强制终止
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let app = tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let child = if cfg!(debug_assertions) {
|
||||
// ── 开发模式 ── 使用系统安装的 streamlit ──
|
||||
let manifest_dir =
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let project_root = manifest_dir.parent().unwrap_or(&manifest_dir);
|
||||
let script_path = project_root.join("geo_tool.py");
|
||||
|
||||
log::info!(
|
||||
"[开发模式] 启动 Streamlit: {:?}",
|
||||
script_path
|
||||
);
|
||||
|
||||
Command::new("streamlit")
|
||||
.args([
|
||||
"run",
|
||||
script_path.to_str().unwrap_or("geo_tool.py"),
|
||||
"--server.port",
|
||||
"8501",
|
||||
"--server.address",
|
||||
"127.0.0.1",
|
||||
"--server.headless",
|
||||
"true",
|
||||
"--browser.gatherUsageStats",
|
||||
"false",
|
||||
"--logger.level",
|
||||
"error",
|
||||
])
|
||||
.current_dir(project_root)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
} else {
|
||||
// ── 生产模式 ── 使用 PyInstaller 打包的独立可执行文件 ──
|
||||
let resource_dir = app
|
||||
.path()
|
||||
.resource_dir()
|
||||
.expect("无法获取资源目录");
|
||||
|
||||
let exe_name = if cfg!(target_os = "windows") {
|
||||
"geo_tool_app.exe"
|
||||
} else {
|
||||
"geo_tool_app"
|
||||
};
|
||||
let exe_path = resource_dir.join(exe_name);
|
||||
|
||||
log::info!(
|
||||
"[生产模式] 启动打包应用: {:?}",
|
||||
exe_path
|
||||
);
|
||||
|
||||
let mut cmd = Command::new(&exe_path);
|
||||
cmd.current_dir(&resource_dir)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
|
||||
// Windows: 不显示控制台窗口
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000u32;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
cmd.spawn()
|
||||
};
|
||||
|
||||
match child {
|
||||
Ok(child) => {
|
||||
log::info!("Streamlit 进程已启动 (PID: {})", child.id());
|
||||
app.manage(StreamlitProcess(Mutex::new(Some(child))));
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("启动 Streamlit 失败: {}", e);
|
||||
eprintln!(
|
||||
"错误:无法启动 Streamlit 服务。请确保已安装 streamlit。\n {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 等待 Streamlit 就绪(最多 15 秒)
|
||||
if !wait_for_port("127.0.0.1", 8501, 15) {
|
||||
log::warn!("Streamlit 在 15 秒内未能就绪");
|
||||
} else {
|
||||
log::info!("Streamlit 已就绪 (127.0.0.1:8501)");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while building tauri application");
|
||||
|
||||
app.run(|app_handle, event| {
|
||||
// 窗口关闭 → 停止子进程 → 退出应用
|
||||
if let tauri::RunEvent::ExitRequested { .. } = event {
|
||||
if let Some(state) = app_handle.try_state::<StreamlitProcess>() {
|
||||
if let Ok(mut guard) = state.0.lock() {
|
||||
if let Some(ref mut child) = *guard {
|
||||
log::info!("正在停止 Streamlit 进程...");
|
||||
kill_streamlit(child);
|
||||
log::info!("Streamlit 进程已停止");
|
||||
}
|
||||
}
|
||||
}
|
||||
// 确保应用进程完全退出
|
||||
std::process::exit(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
app_lib::run();
|
||||
}
|
||||
Reference in New Issue
Block a user