From 899c544d800a836cb241bfea7f69465eecd454d0 Mon Sep 17 00:00:00 2001 From: EEEEEEEdison <476676559@qq.com> Date: Sat, 27 Jun 2026 19:25:42 +0800 Subject: [PATCH] fix: avoid implicit launcher actions before user confirmation --- src-tauri/src/app.rs | 31 +++++++---- src-tauri/src/app_service.rs | 100 ++++++++++++++--------------------- src/App.tsx | 8 ++- 3 files changed, 69 insertions(+), 70 deletions(-) diff --git a/src-tauri/src/app.rs b/src-tauri/src/app.rs index 45173cc..22a7cba 100644 --- a/src-tauri/src/app.rs +++ b/src-tauri/src/app.rs @@ -2,7 +2,7 @@ use crate::utils::defender::is_defender_excluded; use crate::utils::path; use crate::utils::path::{get_app_base_path, get_app_working_dir_path}; -use anyhow::{anyhow, Context}; +use anyhow::Context; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::fs; @@ -241,8 +241,9 @@ pub(crate) async fn load_app_config_from_json(app_name: &str) -> anyhow::Result< let json_data = tokio::fs::read_to_string(&config_path) .await .with_context(|| format!("Failed to read app.json for {}", app_name))?; + let normalized_json_data = json_data.trim_start_matches('\u{feff}'); - match serde_json::from_str::(&json_data) { + match serde_json::from_str::(normalized_json_data) { Ok(mut app) => { if app.name != app_name { warn!("App name mismatch in app.json ('{}') and directory ('{}'). Correcting to directory name: '{}'.", app.name, app_name, app_name); @@ -266,17 +267,29 @@ pub(crate) async fn load_app_config_from_json(app_name: &str) -> anyhow::Result< Ok(Some(app)) } Err(e) => { - error!( - "Failed to deserialize app.json for {}: {}. Content sample: {}", + let bad_path = config_path.with_extension("json.bad"); + warn!( + "Failed to deserialize app.json for {}: {}. Backing up invalid file to {} and rebuilding from embedded config.", app_name, e, - json_data.chars().take(200).collect::() + bad_path.display() ); - Err(anyhow!( - "Failed to deserialize app.json for {}: {}", + warn!( + "Invalid app.json content sample for {}: {}", app_name, - e - )) + normalized_json_data.chars().take(200).collect::() + ); + + if let Err(copy_err) = tokio::fs::copy(&config_path, &bad_path).await { + warn!( + "Failed to back up invalid app.json for {} to {}: {}", + app_name, + bad_path.display(), + copy_err + ); + } + + Ok(None) } } } diff --git a/src-tauri/src/app_service.rs b/src-tauri/src/app_service.rs index 71f957f..3f5bf5d 100644 --- a/src-tauri/src/app_service.rs +++ b/src-tauri/src/app_service.rs @@ -1,9 +1,6 @@ //src/app_service.rs use crate::app::App; -use crate::config_manager::{ - GLOBAL_CONFIG_STATE, UPDATE_METHOD_OPTION_AUTO, UPDATE_METHOD_OPTION_IGNORE, -}; -use crate::emitter::get_app_handle; +use crate::config_manager::{GLOBAL_CONFIG_STATE, UPDATE_METHOD_OPTION_AUTO}; use crate::git::ensure_repository; use crate::runas; use crate::utils::error::Error; @@ -340,6 +337,13 @@ pub async fn load_apps() -> Result, Error> { .find(|version| git::is_release_version(version)) .cloned(); let current_version_missing = app.current_version_missing; + let has_pending_version_change_notice = app + .app_starting_version + .as_ref() + .zip(app.current_version.as_ref()) + .is_some_and(|(starting_version, current_version)| { + starting_version != current_version && !app.update_note.is_empty() + }); let release_update_available = latest_release_version .as_ref() @@ -355,77 +359,55 @@ pub async fn load_apps() -> Result, Error> { let is_latest = !release_update_available; info!( - "First load, checking for auto-start conditions. update_method:{}, is_latest:{}, current_version_missing:{}", - update_method, is_latest, current_version_missing + "First load, checking for auto-start conditions. update_method:{}, is_latest:{}, current_version_missing:{}, has_pending_version_change_notice:{}", + update_method, is_latest, current_version_missing, has_pending_version_change_notice ); - let mut needs_autostart = false; info!("locale is {}", get_locale()); if app.installed && !app.available_versions.is_empty() { if release_update_available { - if current_version_missing { - info!( - "Current version is no longer available upstream. Forcing update to latest available release." - ); - } else { - info!("App is not the latest release version."); - } - let app_name_clone = app.name.clone(); let latest_version = latest_release_version .expect("release_update_available requires latest release"); - if current_version_missing || update_method == UPDATE_METHOD_OPTION_AUTO { + if current_version_missing { info!( - "{}", - t!( - "message.new_version_update", - version = latest_version.clone() - ) - ); - send_notification( - app_name_clone.clone(), - t!("message.new_version_update", version = latest_version), - ); - update_to_version(&app_name_clone, &latest_version).await?; - info!("Auto Update to version {} success.", &latest_version); - send_notification( - app_name_clone, - t!("message.version_update_success", version = latest_version), + "Current version is no longer available upstream. Waiting for explicit user action before updating to {}.", + latest_version ); - needs_autostart = true; } else { - send_notification( - app_name_clone.clone(), - t!("message.new_version", version = latest_version), + info!( + "App is not the latest release version. Waiting for explicit user action before updating to {}.", + latest_version ); - if update_method == UPDATE_METHOD_OPTION_IGNORE { - info!("Auto-update is UPDATE_METHOD_OPTION_IGNORE set auto_start to true."); - needs_autostart = true; - } } + + let app_name_clone = app.name.clone(); + let notification_key = + if current_version_missing || update_method == UPDATE_METHOD_OPTION_AUTO { + "message.new_version_update" + } else { + "message.new_version" + }; + send_notification( + app_name_clone, + t!(notification_key, version = latest_version), + ); + } else if has_pending_version_change_notice { + info!( + "Pending version-change notice detected for app '{}'. Waiting for user action before any further operation.", + app.name + ); } else { - needs_autostart = true; - info!("App is the latest version and installed. set auto start to true"); + info!( + "App '{}' is installed and up to date. Waiting for explicit user action instead of auto-starting.", + app.name + ); } } - if needs_autostart { - info!("Auto-starting app '{}'.", app.name); - let app_name_clone = app.name.clone(); - drop(auto_start_guard); - if let Some(app_handle) = get_app_handle() { - let app_handle_clone = app_handle.clone(); - tokio::spawn(async move { - if let Err(e) = start_app(app_handle_clone, app_name_clone.clone()).await { - error!("Auto-start for app '{}' failed: {:?}", app_name_clone, e); - } - }); - } - } else { - info!( - "Auto-start conditions not met for app '{}' (installed: {}, is_latest: {}).", - app.name, app.installed, is_latest - ); - } + info!( + "First-load automation is disabled for app '{}' (installed: {}, is_latest: {}).", + app.name, app.installed, is_latest + ); } } diff --git a/src/App.tsx b/src/App.tsx index 5564a76..3a549c2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -174,6 +174,7 @@ function App() { version: string; actionType: string; } | null>(null); + const activeVersionChangeAppRef = useRef(null); const [isVersionChangeProcessRunning, setIsVersionChangeProcessRunning] = useState(false); const [isRunningAppConsoleOpen, setIsRunningAppConsoleOpen] = useState(false); const [themeMode, setThemeMode] = useState(() => { @@ -336,6 +337,9 @@ function App() { error?: boolean; }>("app-log", (event) => { const {app_name, finished, error} = event.payload; + if (activeVersionChangeAppRef.current !== app_name) { + return; + } setInlineUpdateLogs(prev => { const entry = prev[app_name]; if (!entry || entry.completed) return prev; @@ -347,8 +351,6 @@ function App() { completedAppsRef.current.add(app_name); // mark completed immediately return {...prev, [app_name]: {...entry, isConfirming: false, completed: true, failed: false}}; } - } else if (!entry.isConfirming) { - return {...prev, [app_name]: {...entry, isConfirming: true, failed: false}}; } return prev; }); @@ -438,6 +440,7 @@ function App() { const handleConfirmVersionChange = async (params: { appName: string, version: string, actionType: string }) => { clearMessages(); setAppActionLoading(prev => ({...prev, [params.appName]: true})); + activeVersionChangeAppRef.current = params.appName; // Mark as confirming so the inline log shows a spinner setInlineUpdateLogs(prev => ({ ...prev, @@ -490,6 +493,7 @@ function App() { setStartingAppName(null); setVersionChangeConsoleData(null); setProfileChangeData(null); + activeVersionChangeAppRef.current = null; // After a version change: clear version selector and mark log as completed (shows success state) if (wasVersionChange && appNameForVersionChange) {