mirror of
https://github.com/JefferyHcool/BiliNote.git
synced 2026-05-12 11:30:44 +08:00
feat(desktop): 启动期路径诊断 + 顶端横幅,主动暴露已知失败因素
桌面端历史痛点:PyInstaller sidecar 在含非 ASCII / 含空格 / 不可写的安装路径下
直接炸(README:36 仅文字警告"不要中文路径",无主动防御)。Sidecar 退出事件 lib.rs
已 emit 但前端没 listener 消化,用户看到的是空白主页。
- src-tauri/src/lib.rs:
· setup 中 env::current_exe() 之后做 InstallPathDiagnostics(is_ascii / 空格 /
父目录 write_probe),命中任一异常就 emit 'backend-warning' 给前端
· 用 std::thread::spawn + 1500ms sleep 等首屏 listener 挂上再 emit,避免事件丢失
· 新增 Tauri command get_install_path_diagnostics,前端可主动重查(用户卸载到
新目录后首次启动)
- BillNote_frontend/src/components/SystemDiagnostic/StartupBanner.tsx(新建):
· 监听 backend-warning(路径警告,可关闭)+ backend-terminated(致命,常驻)
· 纯 web 环境(无 __TAURI_INTERNALS__)下静默不挂载,对桌面端定向起效
· backend-error(stderr 噪音)暂不展示,留给后续 P2 日志面板
- App.tsx:StartupBanner 在 BackendInitDialog 之前就挂载,让"后端还在初始化时"
也能看见路径警告(正是该场景容易炸)
不改任何业务逻辑;纯桌面端 UX 加固。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@ use tauri_plugin_shell::ShellExt;
|
||||
use tauri_plugin_shell::process::CommandEvent;
|
||||
use std::env;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use serde::Serialize;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
@@ -20,6 +22,21 @@ pub fn run() {
|
||||
let exe_path = env::current_exe().expect("无法获取当前可执行文件路径");
|
||||
let sidecar_dir = exe_path.parent().expect("无法获取可执行文件的父目录");
|
||||
|
||||
// 安装路径诊断:PyInstaller sidecar 在含非 ASCII / 空格的路径下经常炸(README 已警告但缺主动防御)
|
||||
// 命中时把诊断信息 emit 给前端,由顶端横幅展示,不阻断启动
|
||||
let diag = analyze_install_path(&exe_path);
|
||||
if diag.path_has_non_ascii || diag.path_has_space || !diag.parent_writable {
|
||||
let app_handle = app.handle().clone();
|
||||
// 等前端首屏挂载好 listener;setup 阶段 window 已存在但 React 还没 render
|
||||
// 用独立线程 + 标准 sleep,不引入 tokio 依赖
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(std::time::Duration::from_millis(1500));
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.emit("backend-warning", &diag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 收集所有系统环境变量
|
||||
let mut all_env_vars = HashMap::new();
|
||||
for (key, value) in env::vars() {
|
||||
@@ -96,7 +113,8 @@ pub fn run() {
|
||||
get_system_env_vars,
|
||||
find_executable_path,
|
||||
run_command_with_env,
|
||||
test_ffmpeg_access
|
||||
test_ffmpeg_access,
|
||||
get_install_path_diagnostics
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
@@ -268,6 +286,54 @@ async fn test_ffmpeg_access() -> Result<String, String> {
|
||||
run_command_with_env("ffmpeg".to_string(), vec!["-version".to_string()]).await
|
||||
}
|
||||
|
||||
// 安装路径诊断:PyInstaller 在含非 ASCII / 空格的路径下加载 _internal/* 经常炸;
|
||||
// 父目录不可写时模型 / 配置 / 日志也无法落盘
|
||||
#[derive(Serialize, Clone)]
|
||||
struct InstallPathDiagnostics {
|
||||
exe_path: String,
|
||||
path_has_non_ascii: bool,
|
||||
path_has_space: bool,
|
||||
parent_writable: bool,
|
||||
platform: String,
|
||||
}
|
||||
|
||||
fn analyze_install_path(exe_path: &Path) -> InstallPathDiagnostics {
|
||||
let path_str = exe_path.to_string_lossy().to_string();
|
||||
// 不在 ASCII 范围内的字符(中文 / 日文 / 西里尔等都会命中 PyInstaller 路径解析坑)
|
||||
let has_non_ascii = path_str.chars().any(|c| !c.is_ascii());
|
||||
// 空格本身在 Windows shell 引号场景偶尔出问题,且 macOS path 里也偶尔触发 sidecar 启动失败
|
||||
let has_space = path_str.contains(' ');
|
||||
// 父目录可写:PyInstaller 解压 _internal/、写日志、写配置都需要这个
|
||||
let parent = exe_path.parent();
|
||||
let parent_writable = parent
|
||||
.and_then(|p| {
|
||||
let probe = p.join(".bilinote_write_probe");
|
||||
match std::fs::write(&probe, b"x") {
|
||||
Ok(_) => {
|
||||
let _ = std::fs::remove_file(&probe);
|
||||
Some(true)
|
||||
}
|
||||
Err(_) => Some(false),
|
||||
}
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
InstallPathDiagnostics {
|
||||
exe_path: path_str,
|
||||
path_has_non_ascii: has_non_ascii,
|
||||
path_has_space: has_space,
|
||||
parent_writable,
|
||||
platform: std::env::consts::OS.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
// Tauri 命令:让前端按需重新查询诊断结果(比如用户卸载到新目录后重启)
|
||||
#[tauri::command]
|
||||
fn get_install_path_diagnostics() -> InstallPathDiagnostics {
|
||||
let exe_path = env::current_exe().unwrap_or_default();
|
||||
analyze_install_path(&exe_path)
|
||||
}
|
||||
|
||||
// 可选:添加一个函数来动态更新 sidecar 的环境变量
|
||||
#[tauri::command]
|
||||
async fn update_sidecar_environment(
|
||||
|
||||
Reference in New Issue
Block a user