use crate::core::notify::NoticeStatus; use crate::{ cmd, config::{Config, PrfItem, PrfOption, profiles::profiles_update_item_safe}, core::{CoreManager, handle, tray, validate::ValidationOutcome}, utils::help::{mask_err, mask_url}, }; use anyhow::{Result, bail}; use clash_verge_logging::{Type, logging, logging_error}; use smartstring::alias::String; /// Toggle proxy profile pub async fn toggle_proxy_profile(profile_index: String) { if let Err(err) = cmd::patch_profiles_config_by_profile_index(profile_index.clone()).await { logging!( error, Type::Config, "toggle proxy profile {profile_index} failed: {err}" ); } } /// Tell the profile which node this group is on now. /// /// The core is already switched by the time this runs, so failing to record is not a reason to /// report the switch as failed — but it is worth a log line, because what the profile holds is /// what gets re-applied the next time a core starts. async fn record_switched_node(group_name: &str, proxy_name: &str) { if let Err(error) = crate::config::profiles::record_selected_node(group_name, proxy_name).await { logging!( warn, Type::Config, "切换代理成功但未能记录到配置: {} -> {}, 错误: {error:#}", group_name, proxy_name ); } } pub async fn switch_proxy_node(group_name: &str, proxy_name: &str) { match handle::Handle::mihomo() .select_node_for_group(group_name, proxy_name) .await { Ok(_) => { record_switched_node(group_name, proxy_name).await; handle::Handle::refresh_proxy_config(); let _ = tray::Tray::global().update_menu().await; return; } Err(err) => { logging!(error, Type::Tray, "切换代理失败: {err:?}"); } } match handle::Handle::mihomo() .select_node_for_group(group_name, proxy_name) .await { Ok(_) => { logging!(info, Type::Tray, "代理切换回退成功: {} -> {}", group_name, proxy_name); record_switched_node(group_name, proxy_name).await; let _ = tray::Tray::global().update_menu().await; } Err(err) => { logging!(error, Type::Tray, "代理切换最终失败: {err:?}"); } } } async fn should_update_profile(uid: &String, ignore_auto_update: bool) -> Result)>> { let profiles = Config::profiles().await; let profiles = profiles.latest_arc(); let item = profiles.get_item(uid)?; let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote"); if !is_remote { logging!(info, Type::Config, "[订阅更新] 不是远程订阅,跳过更新"); Ok(None) } else if item.url.is_none() { logging!(warn, Type::Config, "[订阅更新] 缺少URL,无法更新"); bail!("failed to get the profile item url"); } else if !ignore_auto_update && !item.option.as_ref().and_then(|o| o.allow_auto_update).unwrap_or(true) { logging!(info, Type::Config, "[订阅更新] 禁止自动更新,跳过更新"); Ok(None) } else { logging!( info, Type::Config, "[订阅更新] 远程订阅,URL: {}", mask_url( item.url .as_ref() .ok_or_else(|| anyhow::anyhow!("Profile URL is None"))? ) ); Ok(Some(( item.url.clone().ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?, item.option.clone(), ))) } } #[tracing::instrument(skip_all, level = "info", fields(uid = %uid, strategy = tracing::field::Empty))] async fn perform_profile_update( uid: &String, url: &String, opt: Option<&PrfOption>, option: Option<&PrfOption>, is_mannual_trigger: bool, ) -> Result<()> { let mut merged_opt = PrfOption::merge(opt, option); let profiles = Config::profiles().await; let profiles_arc = profiles.latest_arc(); let profile_name = profiles_arc .get_item(uid) .ok() .and_then(|item| item.name.clone()) .unwrap_or_else(|| String::from("UnKnown Profile")); match PrfItem::from_url(url, None, None, merged_opt.as_ref()).await { Ok(mut item) => { logging!(info, Type::Config, "[订阅更新] 更新订阅配置成功"); profiles_update_item_safe(uid, &mut item).await?; return Ok(()); } Err(err) => { logging!( warn, Type::Config, "[订阅更新] 直接更新失败: {},尝试使用Clash代理更新", mask_err(&format!("{err:#}")) ); } } merged_opt.get_or_insert_with(PrfOption::default).self_proxy = Some(true); merged_opt.get_or_insert_with(PrfOption::default).with_proxy = Some(false); match PrfItem::from_url(url, None, None, merged_opt.as_ref()).await { Ok(mut item) => { logging!(info, Type::Config, "[订阅更新] 使用 Clash代理 更新订阅配置成功"); profiles_update_item_safe(uid, &mut item).await?; handle::Handle::notice(NoticeStatus::UpdateWithClashProxy, profile_name.as_str()); return Ok(()); } Err(err) => { logging!( warn, Type::Config, "[订阅更新] Clash代理更新失败: {},尝试使用系统代理更新", mask_err(&format!("{err:#}")) ); } } merged_opt.get_or_insert_with(PrfOption::default).self_proxy = Some(false); merged_opt.get_or_insert_with(PrfOption::default).with_proxy = Some(true); let last_err = match PrfItem::from_url(url, None, None, merged_opt.as_ref()).await { Ok(mut item) => { logging!(info, Type::Config, "[订阅更新] 使用 系统代理 更新订阅配置成功"); profiles_update_item_safe(uid, &mut item).await?; handle::Handle::notice(NoticeStatus::UpdateWithClashProxy, profile_name.as_str()); return Ok(()); } Err(err) => { logging!( warn, Type::Config, "[订阅更新] 系统代理更新失败: {},所有重试均已失败", mask_err(&format!("{err:#}")) ); err } }; let last_err = mask_err(&last_err.to_string()); if is_mannual_trigger { handle::Handle::notice( NoticeStatus::UpdateFailedEvenWithClash, format!("{profile_name} - {last_err}"), ); } bail!(last_err) } #[tracing::instrument(skip_all, level = "info", fields(uid = %uid, manual = is_mannual_trigger))] pub async fn update_profile(uid: &String, option: Option<&PrfOption>, is_mannual_trigger: bool) -> Result<()> { let url_opt = should_update_profile(uid, is_mannual_trigger).await?; let profile_persisted = match url_opt { Some((url, opt)) => { perform_profile_update(uid, &url, opt.as_ref(), option, is_mannual_trigger).await?; true } None => false, }; let profiles = Config::profiles().await; let is_current = profiles.latest_arc().current.as_ref() == Some(uid); let should_refresh = is_current || (!profile_persisted && is_mannual_trigger); if should_refresh { logging!(debug, Type::Config, "[订阅更新] 更新内核配置"); match CoreManager::global().update_config_with_force(is_mannual_trigger).await { Ok(outcome) if outcome.is_valid() => { logging_error!(Type::Config, Config::sync_dns_override().await); handle::Handle::refresh_clash(); } Ok(outcome @ (ValidationOutcome::Skipped { .. } | ValidationOutcome::Busy)) if !is_mannual_trigger => { logging!(info, Type::Config, "[订阅更新] 本次配置刷新已跳过: {}", outcome); } result => { let message = match result { Ok(outcome) => outcome.to_string(), Err(err) => err.to_string(), }; let message = mask_err(&message); let message = if profile_persisted { format!("订阅已保存,但未能确认已应用到内核: {message}") } else { message }; logging!(error, Type::Config, "[订阅更新] 更新失败: {}", message); handle::Handle::notice(NoticeStatus::UpdateFailed, message.as_str()); bail!(message); } } } Ok(()) } /// 增强配置 pub async fn enhance_profiles() -> Result { let outcome = CoreManager::global().update_config_forced().await?; if outcome.is_valid() { logging_error!(Type::Config, Config::sync_dns_override().await); } Ok(outcome) }