From fc10fff9205f7ab44f9958838f1203c9c15c84bd Mon Sep 17 00:00:00 2001 From: Slinetrac Date: Sun, 26 Oct 2025 15:40:46 +0800 Subject: [PATCH] fix(profile): unify error string types in validation helper --- src-tauri/src/cmd/profile.rs | 94 ++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index 36e0f101..7db1b290 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -17,9 +17,11 @@ use crate::{ }; use futures::FutureExt; use once_cell::sync::OnceCell; +use serde_yaml_ng as serde_yaml; use smartstring::alias::String; use std::{ collections::VecDeque, + fs, panic::AssertUnwindSafe, sync::atomic::{AtomicBool, AtomicU64, Ordering}, time::Duration, @@ -30,6 +32,73 @@ use tokio::sync::{ oneshot, }; +async fn validate_switch_request(task_id: u64, profile_id: &str) -> Result<(), String> { + logging!( + info, + Type::Cmd, + "Validating profile switch task {} -> {}", + task_id, + profile_id + ); + + let profile_key: String = profile_id.into(); + let (file_path, profile_type, is_current, remote_url) = { + let profiles_guard = Config::profiles().await; + let latest = profiles_guard.latest_ref(); + let item = latest.get_item(&profile_key).map_err(|err| -> String { + format!("Target profile {} not found: {}", profile_id, err).into() + })?; + ( + item.file.clone().map(|f| f.to_string()), + item.itype.clone().map(|t| t.to_string()), + latest + .current + .as_ref() + .map(|current| current.as_str() == profile_id) + .unwrap_or(false), + item.url.clone().map(|u| u.to_string()), + ) + }; + + if is_current { + logging!( + info, + Type::Cmd, + "Switch task {} is targeting the current profile {}; skipping validation", + task_id, + profile_id + ); + return Ok(()); + } + + if matches!(profile_type.as_deref(), Some("remote")) { + let has_url = remote_url.as_ref().map(|u| !u.is_empty()).unwrap_or(false); + if !has_url { + return Err({ + let msg = format!("Remote profile {} is missing a download URL", profile_id); + msg.into() + }); + } + } + + if let Some(file) = file_path { + let profiles_dir = dirs::app_profiles_dir().map_err(|err| -> String { + format!("Failed to resolve profiles directory: {}", err).into() + })?; + let path = profiles_dir.join(&file); + + let contents = fs::read_to_string(&path).map_err(|err| -> String { + format!("Failed to read profile file {}: {}", path.display(), err).into() + })?; + + serde_yaml::from_str::(&contents).map_err(|err| -> String { + format!("Profile YAML parse failed for {}: {}", path.display(), err).into() + })?; + } + + Ok(()) +} + static SWITCH_MUTEX: OnceCell> = OnceCell::new(); static SWITCH_QUEUE: OnceCell> = OnceCell::new(); const SWITCH_QUEUE_CAPACITY: usize = 32; @@ -164,6 +233,31 @@ fn start_switch_job( tokio::spawn(async move { let profile_id = request.profile_id.clone(); let notify = request.notify; + if let Err(err) = validate_switch_request(request.task_id, &profile_id).await { + logging!( + warn, + Type::Cmd, + "Validation failed for switch task {} -> {}: {}", + request.task_id, + profile_id, + err + ); + handle::Handle::notice_message("config_validate::error", err); + handle::Handle::notify_profile_switch_finished( + profile_id.clone(), + false, + notify, + request.task_id, + ); + let _ = driver_tx + .send(SwitchDriverMessage::Completion { + request, + success: false, + }) + .await; + return; + } + logging!( info, Type::Cmd,