diff --git a/src-tauri/src/server.rs b/src-tauri/src/server.rs index 5ecfad3..c4d8614 100644 --- a/src-tauri/src/server.rs +++ b/src-tauri/src/server.rs @@ -546,57 +546,84 @@ async fn sse_handler() -> Sse>> { } // ===================== NPC 内网穿透客户端 ===================== +/// 多平台兼容: +/// - Windows: 二进制名为 npc.exe +/// - macOS/Linux: 二进制名为 npc +/// 每次启动检查持久化目录(与 data.json 同一目录下的 npc/)中是否存在, +/// 不存在则从嵌入式 npc.zip 解压。 pub fn setup_npc() { - let zip_data = match Asset::get("npc.zip") { - Some(data) => data, - None => { - eprintln!("[dpm] 警告: npc.zip 未嵌入到程序中,跳过"); - return; - } - }; - - let data_dir = dirs::home_dir() + let data_dir = dirs::data_dir() .unwrap_or_else(|| PathBuf::from(".")) - .join(".dpm") + .join("dpm") .join("npc"); if let Err(e) = fs::create_dir_all(&data_dir) { eprintln!("[dpm] 无法创建 npc 目录 ({}): {}", data_dir.display(), e); return; } - let exe_path = data_dir.join("npc.exe"); - - // 解压 npc.exe - let cursor = Cursor::new(zip_data.data); - let mut archive = match zip::ZipArchive::new(cursor) { - Ok(a) => a, - Err(e) => { - eprintln!("[dpm] npc.zip 解压失败: {}", e); - return; - } + // 根据平台确定二进制文件名 + let exe_name = if cfg!(target_os = "windows") { + "npc.exe" + } else { + "npc" }; + let exe_path = data_dir.join(exe_name); - let mut extracted = false; - for i in 0..archive.len() { - let mut file = match archive.by_index(i) { - Ok(f) => f, - Err(_) => continue, - }; - if file.name() == "npc.exe" { - if let Err(e) = fs::File::create(&exe_path) - .and_then(|mut out| std::io::copy(&mut file, &mut out).map(|_| ())) - { - eprintln!("[dpm] npc.exe 写入失败: {}", e); + // 检查文件是否已存在,不存在则从嵌入式 zip 解压 + if !exe_path.exists() { + let zip_data = match Asset::get("npc.zip") { + Some(data) => data, + None => { + eprintln!("[dpm] 警告: npc.zip 未嵌入到程序中,跳过"); return; } - extracted = true; - break; - } - } + }; - if !extracted { - eprintln!("[dpm] npc.zip 中未找到 npc.exe"); - return; + let cursor = Cursor::new(zip_data.data); + let mut archive = match zip::ZipArchive::new(cursor) { + Ok(a) => a, + Err(e) => { + eprintln!("[dpm] npc.zip 解压失败: {}", e); + return; + } + }; + + let mut extracted = false; + for i in 0..archive.len() { + let mut file = match archive.by_index(i) { + Ok(f) => f, + Err(_) => continue, + }; + // 兼容 zip 中二进制名为 npc.exe 或 npc 的情况 + let name = file.name().trim_end_matches('/'); + let is_match = name == "npc.exe" || name == "npc" || name == exe_name; + if is_match { + if let Err(e) = fs::File::create(&exe_path) + .and_then(|mut out| std::io::copy(&mut file, &mut out).map(|_| ())) + { + eprintln!("[dpm] {} 写入失败: {}", exe_name, e); + return; + } + extracted = true; + break; + } + } + + if !extracted { + eprintln!("[dpm] npc.zip 中未找到 npc/npc.exe"); + return; + } + + // Unix 平台设置可执行权限 + #[cfg(not(target_os = "windows"))] + { + use std::os::unix::fs::PermissionsExt; + if let Err(e) = fs::set_permissions(&exe_path, fs::Permissions::from_mode(0o755)) { + eprintln!("[dpm] 设置执行权限失败: {}", e); + } + } + + println!("[dpm] npc 客户端已解压到 {}", exe_path.display()); } println!("[dpm] 启动 npc 客户端...");