diff --git a/call-client/package.json b/call-client/package.json index f07d421..e0163c7 100644 --- a/call-client/package.json +++ b/call-client/package.json @@ -1,7 +1,7 @@ { "name": "call-client", "private": true, - "version": "0.1.3", + "version": "0.1.4", "type": "module", "scripts": { "dev": "vite", diff --git a/call-client/src-tauri/Cargo.lock b/call-client/src-tauri/Cargo.lock index 2cff125..9175349 100644 --- a/call-client/src-tauri/Cargo.lock +++ b/call-client/src-tauri/Cargo.lock @@ -392,7 +392,7 @@ dependencies = [ [[package]] name = "call-client" -version = "0.1.3" +version = "0.1.4" dependencies = [ "chrono", "fs2", diff --git a/call-client/src-tauri/Cargo.toml b/call-client/src-tauri/Cargo.toml index 24e6304..40040eb 100644 --- a/call-client/src-tauri/Cargo.toml +++ b/call-client/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "call-client" -version = "0.1.3" +version = "0.1.4" description = "A Tauri App" authors = ["you"] edition = "2021" diff --git a/call-client/src-tauri/src/commands/config.rs b/call-client/src-tauri/src/commands/config.rs index 15ad6cd..7ddd669 100644 --- a/call-client/src-tauri/src/commands/config.rs +++ b/call-client/src-tauri/src/commands/config.rs @@ -3,6 +3,7 @@ use std::{ env, fs, path::{Path, PathBuf}, + sync::{Mutex, OnceLock}, }; use serde_json::{Map, Value}; @@ -72,6 +73,15 @@ fn write_config(value: &Value) -> Result<(), String> { fs::write(path, content).map_err(|error| format!("写入配置文件失败: {error}")) } +static CONFIG_IO_MUTEX: OnceLock> = OnceLock::new(); + +fn lock_config_io() -> Result, String> { + let mutex = CONFIG_IO_MUTEX.get_or_init(|| Mutex::new(())); + mutex + .lock() + .map_err(|_| "配置文件锁异常(poisoned)".to_string()) +} + fn merge_value(target: &mut Value, patch: Value) { match (target, patch) { (Value::Object(target_map), Value::Object(patch_map)) => { @@ -91,6 +101,7 @@ fn merge_value(target: &mut Value, patch: Value) { #[tauri::command] pub fn config_get_all() -> Result, String> { + let _guard = lock_config_io()?; let value = read_config()?; let Value::Object(map) = value else { return Ok(BTreeMap::new()); @@ -99,8 +110,18 @@ pub fn config_get_all() -> Result, String> { Ok(map.into_iter().collect()) } +/// 与 `config_merge` 相同磁盘语义,供后台线程等非 invoke 路径调用;带全局锁避免与前端合并写交错。 +pub fn merge_config_disk(partial: Value) -> Result<(), String> { + let _guard = lock_config_io()?; + let mut current = read_config()?; + merge_value(&mut current, partial); + write_config(¤t)?; + Ok(()) +} + #[tauri::command] pub fn config_merge(partial: Value) -> Result, String> { + let _guard = lock_config_io()?; let mut current = read_config()?; merge_value(&mut current, partial); write_config(¤t)?; diff --git a/call-client/src-tauri/src/commands/update.rs b/call-client/src-tauri/src/commands/update.rs index 17c3552..f2e299a 100644 --- a/call-client/src-tauri/src/commands/update.rs +++ b/call-client/src-tauri/src/commands/update.rs @@ -1,9 +1,8 @@ use serde::Serialize; #[cfg(target_os = "linux")] use std::process::Command; -use tauri::AppHandle; -#[cfg(target_os = "linux")] -use tauri::Manager; +use tauri::{AppHandle, Manager}; +use serde_json::json; use super::logger::app_log; @@ -214,6 +213,14 @@ pub fn check_apt_update( _app: AppHandle, package_name: String, current_version: String, +) -> Result { + check_apt_update_internal(package_name, current_version) +} + +/// 供 `check_apt_update` 命令与启动后台线程复用。 +pub(crate) fn check_apt_update_internal( + package_name: String, + current_version: String, ) -> Result { let _ = app_log( "info".to_string(), @@ -412,6 +419,61 @@ pub fn check_apt_update( } } +/// 与前端 `listen('pending-call-client-update-synced')` 一致:后台 apt 检测已落盘 config 后广播。 +pub const PENDING_CALL_CLIENT_UPDATE_SYNCED_EVENT: &str = "pending-call-client-update-synced"; + +/// 将 `check_apt_update` 结果同步到 `pending_call_client_update`(与前端约定一致)。 +pub fn apply_pending_call_client_update_config(r: &AptUpdateCheckResult) -> Result<(), String> { + if r.has_update && r.source_available { + super::config::merge_config_disk(json!({ + "pending_call_client_update": { + "candidateVersion": r.candidate_version + } + })) + } else if r.source_available { + super::config::merge_config_disk(json!({ + "pending_call_client_update": null + })) + } else { + Ok(()) + } +} + +/// 启动后后台执行 apt 检测并写 config,不阻塞 UI;完成后 `emit_all` 便于登录页刷新角标。 +pub fn spawn_background_pending_call_client_update_sync(app: AppHandle) { + std::thread::spawn(move || { + let version = app.package_info().version.to_string(); + match check_apt_update_internal("call-client".to_string(), version) { + Ok(r) => match apply_pending_call_client_update_config(&r) { + Ok(()) => { + let _ = app_log( + "info".to_string(), + "[update-badge] 后台 apt 检查已完成并写入 config(如需)".to_string(), + ); + let payload = json!({ + "hasUpdate": r.has_update, + "sourceAvailable": r.source_available, + "candidateVersion": r.candidate_version, + }); + let _ = app.emit_all(PENDING_CALL_CLIENT_UPDATE_SYNCED_EVENT, payload); + } + Err(e) => { + let _ = app_log( + "error".to_string(), + format!("[update-badge] 后台同步 pending 配置失败: {e}"), + ); + } + }, + Err(msg) => { + let _ = app_log( + "debug".to_string(), + format!("[update-badge] 后台 apt 检查未执行: {msg}"), + ); + } + } + }); +} + /// 首次配置:写入紫云 apt 源并执行 `apt-get update`(需图形环境 `pkexec` 授权)。 #[tauri::command] pub fn setup_zyyun_apt_source(app: AppHandle, deb_line: String) -> Result<(), String> { diff --git a/call-client/src-tauri/src/lib.rs b/call-client/src-tauri/src/lib.rs index 9944253..3ddcf4d 100644 --- a/call-client/src-tauri/src/lib.rs +++ b/call-client/src-tauri/src/lib.rs @@ -12,7 +12,10 @@ use commands::{ logger::{app_log, get_log_paths}, session::{session_clear, session_get, session_set}, sync::{cleanup_screen_sync, start_screen_sync, stop_screen_sync}, - update::{check_apt_update, setup_zyyun_apt_source, upgrade_call_client_via_apt}, + update::{ + check_apt_update, setup_zyyun_apt_source, + spawn_background_pending_call_client_update_sync, upgrade_call_client_via_apt, + }, window::{ close_taxer_info_window, close_ticket_window, ensure_main_window, focus_window, open_login_window, open_main_window, open_taxer_info_window, open_ticket_window, quit_app, @@ -107,6 +110,7 @@ pub fn run() { } ensure_main_window(app.handle())?; + spawn_background_pending_call_client_update_sync(app.handle()); Ok(()) }) .invoke_handler(tauri::generate_handler![ diff --git a/call-client/src-tauri/tauri.conf.json b/call-client/src-tauri/tauri.conf.json index 501e3f5..758d6ad 100644 --- a/call-client/src-tauri/tauri.conf.json +++ b/call-client/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "../node_modules/@tauri-apps/cli/schema.json", "package": { "productName": "call-client", - "version": "0.1.3" + "version": "0.1.4" }, "build": { "beforeDevCommand": "npm run dev", diff --git a/call-client/src/host/types.ts b/call-client/src/host/types.ts index 59838b1..77c7e49 100644 --- a/call-client/src/host/types.ts +++ b/call-client/src/host/types.ts @@ -63,4 +63,12 @@ export interface TaxerTicketContextPayload { export type AppConfig = Record; +/** + * 登录页「有新版本」角标持久化项,写入 `config.json` 的 `pending_call_client_update`。 + * 后台探测到可升级候选版本时写入;无更新或升级成功后清除。 + */ +export interface PendingCallClientUpdateStored { + candidateVersion: string; +} + export type LogLevel = "debug" | "info" | "warn" | "error"; diff --git a/call-client/src/views/LoginView.vue b/call-client/src/views/LoginView.vue index ddfe9dd..580b94b 100644 --- a/call-client/src/views/LoginView.vue +++ b/call-client/src/views/LoginView.vue @@ -1,5 +1,11 @@ @@ -700,16 +860,25 @@ onUnmounted(() => {
-
版本号:V{{ appVersion }}
- - 检查更新 - +
+ 版本号:V{{ appVersion }} + + + + + 有新版本 + +
@@ -717,23 +886,23 @@ onUnmounted(() => { - - -

{{ aptSetupHint }}

-
- + + +

{{ aptSetupHint }}

+
+