refactor(core): type notice statuses behind the frozen wire contract

This commit is contained in:
Tunglies
2026-09-30 17:42:39 +08:00
committed by GitHub
parent e0e97a920e
commit fcbf8a985a
24 changed files with 387 additions and 81 deletions
+4 -3
View File
@@ -1,4 +1,5 @@
use super::{CmdResult, CommandFailure, WithErrorCode as _, proxy_aware_coded_error, proxy_aware_error};
use crate::core::notify::NoticeStatus;
use crate::feat;
use crate::utils::{dirs, yaml_emitter};
use crate::{
@@ -56,14 +57,14 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<CommandFa
match CoreManager::global().restart_core().await {
Ok(_) => {
handle::Handle::notice_message("config_core::change_success", clash_core);
handle::Handle::notice(NoticeStatus::ConfigCoreChangeSuccess, clash_core.as_str());
handle::Handle::refresh_clash();
Ok(None)
}
Err(err) => {
let failed = err.context("core changed but failed to restart");
let error_msg: String = format!("{failed:#}").into();
handle::Handle::notice_message("config_core::change_error", error_msg.clone());
handle::Handle::notice(NoticeStatus::ConfigCoreChangeError, error_msg.as_str());
logging!(error, Type::Core, "core changed but failed to restart: {error_msg}");
Ok(Some(proxy_aware_coded_error(&failed, "CORE_CHANGE_FAILED")))
}
@@ -72,7 +73,7 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<CommandFa
Err(err) => {
let error_msg: String = format!("{err:#}").into();
logging!(error, Type::Core, "failed to change core: {error_msg}");
handle::Handle::notice_message("config_core::change_error", error_msg);
handle::Handle::notice(NoticeStatus::ConfigCoreChangeError, error_msg.as_str());
Ok(Some(proxy_aware_coded_error(&err, "CORE_CHANGE_FAILED")))
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
use super::{CmdResult, StringifyErr as _, WithErrorCode as _, coded_error};
use crate::cmd::validate::{ValidationNoticeTarget, handle_validation_notice};
use crate::config::profiles;
use crate::core::notify::NoticeStatus;
use crate::utils::window_manager::WindowManager;
use crate::{
config::{
@@ -282,7 +283,7 @@ async fn handle_update_error<E: std::fmt::Display>(
logging!(warn, Type::Cmd, "更新过程发生错误: {}", e,);
discard_and_restore(current_profile).await?;
let message: String = e.to_string().into();
handle::Handle::notice_message("config_validate::boot_error", message.clone());
handle::Handle::notice(NoticeStatus::ConfigValidateBootError, message.as_str());
Ok(ValidationOutcome::invalid_from_message(message))
}
+17 -16
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
use crate::core::{
handle,
validate::{ValidationErrorKind, ValidationOutcome},
@@ -11,27 +12,27 @@ pub enum ValidationNoticeTarget {
Script,
}
const fn notice_key(kind: ValidationErrorKind, target: ValidationNoticeTarget) -> &'static str {
const fn notice_key(kind: ValidationErrorKind, target: ValidationNoticeTarget) -> NoticeStatus {
match kind {
ValidationErrorKind::FileMissing => "config_validate::file_not_found",
ValidationErrorKind::FileMissing => NoticeStatus::ConfigValidateFileNotFound,
ValidationErrorKind::FileRead => match target {
ValidationNoticeTarget::Script => "config_validate::script_error",
_ => "config_validate::yaml_read_error",
ValidationNoticeTarget::Script => NoticeStatus::ConfigValidateScriptError,
_ => NoticeStatus::ConfigValidateYamlReadError,
},
ValidationErrorKind::YamlSyntax => match target {
ValidationNoticeTarget::Merge => "config_validate::merge_syntax_error",
ValidationNoticeTarget::Script => "config_validate::script_error",
ValidationNoticeTarget::Runtime => "config_validate::yaml_syntax_error",
ValidationNoticeTarget::Merge => NoticeStatus::ConfigValidateMergeSyntaxError,
ValidationNoticeTarget::Script => NoticeStatus::ConfigValidateScriptError,
ValidationNoticeTarget::Runtime => NoticeStatus::ConfigValidateYamlSyntaxError,
},
ValidationErrorKind::YamlMapping => match target {
ValidationNoticeTarget::Merge => "config_validate::merge_mapping_error",
ValidationNoticeTarget::Script => "config_validate::script_error",
ValidationNoticeTarget::Runtime => "config_validate::yaml_mapping_error",
ValidationNoticeTarget::Merge => NoticeStatus::ConfigValidateMergeMappingError,
ValidationNoticeTarget::Script => NoticeStatus::ConfigValidateScriptError,
ValidationNoticeTarget::Runtime => NoticeStatus::ConfigValidateYamlMappingError,
},
ValidationErrorKind::ScriptSyntax => "config_validate::script_syntax_error",
ValidationErrorKind::ScriptMissingMain => "config_validate::script_missing_main",
ValidationErrorKind::ProcessTerminated => "config_validate::process_terminated",
ValidationErrorKind::CoreRejected | ValidationErrorKind::Timeout => "config_validate::error",
ValidationErrorKind::ScriptSyntax => NoticeStatus::ConfigValidateScriptSyntaxError,
ValidationErrorKind::ScriptMissingMain => NoticeStatus::ConfigValidateScriptMissingMain,
ValidationErrorKind::ProcessTerminated => NoticeStatus::ConfigValidateProcessTerminated,
ValidationErrorKind::CoreRejected | ValidationErrorKind::Timeout => NoticeStatus::ConfigValidateError,
}
}
@@ -40,12 +41,12 @@ pub fn handle_validation_notice(outcome: &ValidationOutcome, target: ValidationN
ValidationOutcome::Invalid { kind, message } => {
let status = notice_key(*kind, target);
logging!(warn, Type::Config, "{} 验证失败: {}", file_type, message);
handle::Handle::notice_message(status, message.to_owned());
handle::Handle::notice(status, message.as_str());
}
ValidationOutcome::Busy | ValidationOutcome::Skipped { .. } => {
let message = outcome.to_string();
logging!(warn, Type::Config, "{} 验证跳过: {}", file_type, message);
handle::Handle::notice_message("config_validate::error", message);
handle::Handle::notice(NoticeStatus::ConfigValidateError, message.as_str());
}
ValidationOutcome::Valid => {}
}
+11 -10
View File
@@ -1,4 +1,5 @@
use super::{IClashTemp, IProfiles, IVerge, MixedPort};
use crate::core::notify::NoticeStatus;
use crate::{
config::{PrfItem, profiles_append_item_to_safe, runtime::IRuntime},
constants::{files, timing},
@@ -106,7 +107,7 @@ impl Config {
if let Some((msg_type, msg_content)) = validation_result {
sleep(timing::STARTUP_ERROR_DELAY).await;
handle::Handle::notice_message(msg_type, msg_content);
handle::Handle::notice(msg_type, msg_content.as_str());
}
Self::runtime().await.apply();
@@ -137,14 +138,14 @@ impl Config {
Ok(())
}
async fn generate_and_validate() -> Result<Option<(&'static str, String)>> {
async fn generate_and_validate() -> Result<Option<(NoticeStatus, String)>> {
if let Err(err) = Self::generate().await {
let error_msg: String = err.to_string().into();
logging!(error, Type::Config, "生成运行时配置失败: {}", error_msg);
CoreManager::global()
.use_default_config("config_validate::boot_error", &error_msg)
.use_default_config(NoticeStatus::ConfigValidateBootError, &error_msg)
.await?;
return Ok(Some(("config_validate::boot_error", error_msg)));
return Ok(Some((NoticeStatus::ConfigValidateBootError, error_msg)));
}
logging!(debug, Type::Config, "生成运行时配置成功");
@@ -171,25 +172,25 @@ impl Config {
error_msg
);
CoreManager::global()
.use_default_config("config_validate::boot_error", &error_msg)
.use_default_config(NoticeStatus::ConfigValidateBootError, &error_msg)
.await?;
Ok(Some(("config_validate::boot_error", error_msg)))
Ok(Some((NoticeStatus::ConfigValidateBootError, error_msg)))
}
Err(err) => {
logging!(warn, Type::Config, "验证过程执行失败: {err:#}");
CoreManager::global()
.use_default_config("config_validate::process_terminated", "")
.use_default_config(NoticeStatus::ConfigValidateProcessTerminated, "")
.await?;
Ok(Some(("config_validate::process_terminated", String::new())))
Ok(Some((NoticeStatus::ConfigValidateProcessTerminated, String::new())))
}
}
} else {
let error_msg = config_result.err().map(|err| err.to_string()).unwrap_or_default();
logging!(warn, Type::Config, "生成配置文件失败,使用默认配置: {error_msg}");
CoreManager::global()
.use_default_config("config_validate::error", "")
.use_default_config(NoticeStatus::ConfigValidateError, "")
.await?;
Ok(Some(("config_validate::error", String::new())))
Ok(Some((NoticeStatus::ConfigValidateError, String::new())))
}
}
+2 -1
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
use anyhow::Result;
use clash_verge_draft::DraftTransaction;
use serde::{Deserialize, Serialize};
@@ -141,7 +142,7 @@ impl Config {
Handle::refresh_verge();
if state.requested && !state.enabled {
PENDING_DNS_OVERRIDE_NOTICE.store(true, Ordering::Relaxed);
Handle::notice_message("dns_override::auto_disabled", "");
Handle::notice(NoticeStatus::DnsOverrideAutoDisabled, "");
}
verge.data_arc().save_file().await
}
+4 -3
View File
@@ -1,4 +1,5 @@
use super::{Config, IClashTemp, IVerge, MixedPort};
use crate::core::notify::NoticeStatus;
use crate::{
constants::timing,
core::{
@@ -166,8 +167,8 @@ impl Config {
};
AsyncHandler::spawn(move || async move {
tokio::time::sleep(timing::STARTUP_ERROR_DELAY).await;
Handle::notice_message(
"mixed_port::fallback",
Handle::notice(
NoticeStatus::MixedPortFallback,
format!("{},{}", change.original, change.current),
);
});
@@ -196,7 +197,7 @@ fn report_fallback_error(message: String) {
);
AsyncHandler::spawn(move || async move {
tokio::time::sleep(timing::STARTUP_ERROR_DELAY).await;
Handle::notice_message("mixed_port::fallback_error", message);
Handle::notice(NoticeStatus::MixedPortFallbackError, message);
});
}
+3 -2
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
use crate::process::AsyncHandler;
use crate::singleton;
use crate::utils::notification::{NotificationEvent, notify_event};
@@ -177,7 +178,7 @@ impl Hotkey {
"Hotkey profile reactivation failed validation: {}",
message.as_str()
);
handle::Handle::notice_message("reactivate_profiles::error", message);
handle::Handle::notice(NoticeStatus::ReactivateProfilesError, message);
}
Err(err) => {
logging!(
@@ -186,7 +187,7 @@ impl Hotkey {
"Failed to reactivate subscriptions via hotkey: {}",
err
);
handle::Handle::notice_message("reactivate_profiles::error", err.to_string());
handle::Handle::notice(NoticeStatus::ReactivateProfilesError, err.to_string());
}
});
}
+3 -2
View File
@@ -1,4 +1,5 @@
use super::{CoreManager, PROFILE_SELECTIONS_PENDING_COMMIT, RunningMode};
use crate::core::notify::NoticeStatus;
use crate::core::service::StageRequest;
use crate::{
config::{Config, IProfiles, runtime::IRuntime},
@@ -129,7 +130,7 @@ where
}
impl CoreManager {
pub async fn use_default_config(&self, error_key: &str, error_msg: &str) -> Result<()> {
pub async fn use_default_config(&self, status: NoticeStatus, message: &str) -> Result<()> {
use crate::constants::files::RUNTIME_CONFIG;
let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG);
@@ -145,7 +146,7 @@ impl CoreManager {
});
help::save_yaml(&runtime_path, &clash_config, Some("# Clash Verge Runtime")).await?;
handle::Handle::notice_message(error_key, error_msg);
handle::Handle::notice(status, message);
Ok(())
}
+2 -2
View File
@@ -1,13 +1,12 @@
pub mod autostart;
pub mod backup;
pub mod handle;
pub mod hotkey;
pub mod listener;
pub mod logger;
pub mod manager;
#[cfg(target_os = "macos")]
pub mod network_watch;
pub mod notification;
pub mod notify;
pub(crate) mod owner_identity;
pub mod proxy_control;
pub mod proxy_view;
@@ -22,3 +21,4 @@ pub mod validate;
pub mod win_uwp;
pub use self::{manager::CoreManager, timer::Timer, updater::SilentUpdater};
pub use notify::{handle, notification};
@@ -1,6 +1,10 @@
use super::NoticeStatus;
use crate::{APP_HANDLE, singleton};
use smartstring::alias::String;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use tauri::AppHandle;
use tauri_plugin_mihomo::{Mihomo, MihomoExt as _};
@@ -80,13 +84,10 @@ impl Handle {
Self::send_event(FrontendEvent::ProfileUpdateCompleted { uid });
}
pub fn notice_message<S: AsRef<str>, M: Into<String>>(status: S, msg: M) {
let status_str = status.as_ref();
let msg_str = msg.into();
pub fn notice(status: NoticeStatus, message: impl Into<Arc<str>>) {
Self::send_event(FrontendEvent::NoticeMessage {
status: status_str,
message: msg_str,
status,
message: message.into(),
});
}
+206
View File
@@ -0,0 +1,206 @@
pub mod desktop;
pub mod handle;
pub mod notification;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
pub enum NoticeStatus {
// dead send: no frontend handler
#[serde(rename = "info")]
Info,
#[serde(rename = "import_sub_url::ok")]
ImportSubUrlOk,
#[serde(rename = "import_sub_url::error")]
ImportSubUrlError,
#[serde(rename = "set_config::error")]
SetConfigError,
#[serde(rename = "core_start::error")]
CoreStartError,
#[serde(rename = "service_core::repair_required")]
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
ServiceCoreRepairRequired,
#[serde(rename = "service_core::app_data_not_owned")]
ServiceCoreAppDataNotOwned,
#[serde(rename = "service_core::sidecar_fallback")]
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
ServiceCoreSidecarFallback,
#[serde(rename = "dns_override::auto_disabled")]
DnsOverrideAutoDisabled,
#[serde(rename = "enhance::discarded_keys")]
EnhanceDiscardedKeys,
#[serde(rename = "tun_mode::auto_disabled")]
TunModeAutoDisabled,
#[serde(rename = "tun_mode::auto_disable_failed")]
TunModeAutoDisableFailed,
#[serde(rename = "app_restart::core_stop_failed")]
AppRestartCoreStopFailed,
#[serde(rename = "app_quit::core_stop_failed")]
AppQuitCoreStopFailed,
#[serde(rename = "update_with_clash_proxy")]
UpdateWithClashProxy,
#[serde(rename = "update_failed_even_with_clash")]
UpdateFailedEvenWithClash,
#[serde(rename = "reactivate_profiles::error")]
ReactivateProfilesError,
#[serde(rename = "update_failed")]
UpdateFailed,
#[serde(rename = "config_validate::boot_error")]
ConfigValidateBootError,
#[serde(rename = "config_validate::error")]
ConfigValidateError,
#[serde(rename = "config_validate::process_terminated")]
ConfigValidateProcessTerminated,
#[serde(rename = "config_validate::script_error")]
ConfigValidateScriptError,
#[serde(rename = "config_validate::script_syntax_error")]
ConfigValidateScriptSyntaxError,
#[serde(rename = "config_validate::script_missing_main")]
ConfigValidateScriptMissingMain,
#[serde(rename = "config_validate::file_not_found")]
ConfigValidateFileNotFound,
#[serde(rename = "config_validate::yaml_syntax_error")]
ConfigValidateYamlSyntaxError,
#[serde(rename = "config_validate::yaml_read_error")]
ConfigValidateYamlReadError,
#[serde(rename = "config_validate::yaml_mapping_error")]
ConfigValidateYamlMappingError,
#[serde(rename = "config_validate::merge_syntax_error")]
ConfigValidateMergeSyntaxError,
#[serde(rename = "config_validate::merge_mapping_error")]
ConfigValidateMergeMappingError,
#[serde(rename = "config_core::change_success")]
ConfigCoreChangeSuccess,
#[serde(rename = "config_core::change_error")]
ConfigCoreChangeError,
#[serde(rename = "mixed_port::fallback")]
MixedPortFallback,
#[serde(rename = "mixed_port::fallback_error")]
MixedPortFallbackError,
// dead listener: frontend handler retained
#[allow(dead_code)]
#[serde(rename = "config_validate::core_change")]
ConfigValidateCoreChange,
// dead listener: frontend handler retained
#[allow(dead_code)]
#[serde(rename = "config_validate::stdout_error")]
ConfigValidateStdoutError,
// dead listener: frontend handler retained
#[allow(dead_code)]
#[serde(rename = "config_validate::yaml_key_error")]
ConfigValidateYamlKeyError,
// dead listener: frontend handler retained
#[allow(dead_code)]
#[serde(rename = "config_validate::yaml_error")]
ConfigValidateYamlError,
// dead listener: frontend handler retained
#[allow(dead_code)]
#[serde(rename = "config_validate::merge_key_error")]
ConfigValidateMergeKeyError,
// dead listener: frontend handler retained
#[allow(dead_code)]
#[serde(rename = "config_validate::merge_error")]
ConfigValidateMergeError,
// dead send: no frontend handler
#[serde(rename = "set_config::ok")]
SetConfigOk,
}
#[cfg(test)]
mod tests {
use super::NoticeStatus;
#[test]
fn notice_status_wire_contract() {
let statuses = [
NoticeStatus::Info,
NoticeStatus::ImportSubUrlOk,
NoticeStatus::ImportSubUrlError,
NoticeStatus::SetConfigError,
NoticeStatus::CoreStartError,
NoticeStatus::ServiceCoreRepairRequired,
NoticeStatus::ServiceCoreAppDataNotOwned,
NoticeStatus::ServiceCoreSidecarFallback,
NoticeStatus::DnsOverrideAutoDisabled,
NoticeStatus::EnhanceDiscardedKeys,
NoticeStatus::TunModeAutoDisabled,
NoticeStatus::TunModeAutoDisableFailed,
NoticeStatus::AppRestartCoreStopFailed,
NoticeStatus::AppQuitCoreStopFailed,
NoticeStatus::UpdateWithClashProxy,
NoticeStatus::UpdateFailedEvenWithClash,
NoticeStatus::ReactivateProfilesError,
NoticeStatus::UpdateFailed,
NoticeStatus::ConfigValidateBootError,
NoticeStatus::ConfigValidateError,
NoticeStatus::ConfigValidateProcessTerminated,
NoticeStatus::ConfigValidateScriptError,
NoticeStatus::ConfigValidateScriptSyntaxError,
NoticeStatus::ConfigValidateScriptMissingMain,
NoticeStatus::ConfigValidateFileNotFound,
NoticeStatus::ConfigValidateYamlSyntaxError,
NoticeStatus::ConfigValidateYamlReadError,
NoticeStatus::ConfigValidateYamlMappingError,
NoticeStatus::ConfigValidateMergeSyntaxError,
NoticeStatus::ConfigValidateMergeMappingError,
NoticeStatus::ConfigCoreChangeSuccess,
NoticeStatus::ConfigCoreChangeError,
NoticeStatus::MixedPortFallback,
NoticeStatus::MixedPortFallbackError,
NoticeStatus::ConfigValidateCoreChange,
NoticeStatus::ConfigValidateStdoutError,
NoticeStatus::ConfigValidateYamlKeyError,
NoticeStatus::ConfigValidateYamlError,
NoticeStatus::ConfigValidateMergeKeyError,
NoticeStatus::ConfigValidateMergeError,
NoticeStatus::SetConfigOk,
];
let expected = [
"info",
"import_sub_url::ok",
"import_sub_url::error",
"set_config::error",
"core_start::error",
"service_core::repair_required",
"service_core::app_data_not_owned",
"service_core::sidecar_fallback",
"dns_override::auto_disabled",
"enhance::discarded_keys",
"tun_mode::auto_disabled",
"tun_mode::auto_disable_failed",
"app_restart::core_stop_failed",
"app_quit::core_stop_failed",
"update_with_clash_proxy",
"update_failed_even_with_clash",
"reactivate_profiles::error",
"update_failed",
"config_validate::boot_error",
"config_validate::error",
"config_validate::process_terminated",
"config_validate::script_error",
"config_validate::script_syntax_error",
"config_validate::script_missing_main",
"config_validate::file_not_found",
"config_validate::yaml_syntax_error",
"config_validate::yaml_read_error",
"config_validate::yaml_mapping_error",
"config_validate::merge_syntax_error",
"config_validate::merge_mapping_error",
"config_core::change_success",
"config_core::change_error",
"mixed_port::fallback",
"mixed_port::fallback_error",
"config_validate::core_change",
"config_validate::stdout_error",
"config_validate::yaml_key_error",
"config_validate::yaml_error",
"config_validate::merge_key_error",
"config_validate::merge_error",
"set_config::ok",
];
let actual: Vec<_> = statuses
.into_iter()
.map(serde_json::to_value)
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(actual, expected.map(serde_json::Value::from));
}
}
@@ -1,8 +1,10 @@
use super::NoticeStatus;
use clash_verge_logging::{Type, logging};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use serde_json::json;
use smartstring::alias::String;
use std::sync::Arc;
use std::{
collections::HashMap,
future::Future,
@@ -17,8 +19,8 @@ pub enum FrontendEvent<'a> {
RefreshProfiles,
RefreshProxyConfig,
NoticeMessage {
status: &'a str,
message: String,
status: NoticeStatus,
message: Arc<str>,
},
ProfileChanged {
current_profile_id: &'a String,
@@ -283,9 +285,10 @@ impl NotificationSystem {
FrontendEvent::RefreshVerge => ("verge://refresh-verge-config", Ok(json!("yes"))),
FrontendEvent::RefreshProfiles => ("verge://refresh-profiles", Ok(json!("yes"))),
FrontendEvent::RefreshProxyConfig => ("verge://refresh-proxy-config", Ok(serde_json::Value::Null)),
FrontendEvent::NoticeMessage { status, message } => {
("verge://notice-message", serde_json::to_value((status, message)))
}
FrontendEvent::NoticeMessage { status, message } => (
"verge://notice-message",
serde_json::to_value((status, message.as_ref())),
),
FrontendEvent::ProfileChanged { current_profile_id } => ("profile-changed", Ok(json!(current_profile_id))),
FrontendEvent::TimerUpdated { profile_index } => ("verge://timer-updated", Ok(json!(profile_index))),
FrontendEvent::ProfileUpdateStarted { uid } => ("profile-update-started", Ok(json!({ "uid": uid }))),
@@ -313,3 +316,78 @@ impl NotificationSystem {
}
}
}
#[cfg(test)]
mod wire_tests {
use super::*;
#[test]
fn frontend_event_wire_contract() {
let uid = String::from("profile");
let events = [
(
FrontendEvent::RefreshClash,
"verge://refresh-clash-config",
json!("yes"),
),
(
FrontendEvent::RefreshVerge,
"verge://refresh-verge-config",
json!("yes"),
),
(FrontendEvent::RefreshProfiles, "verge://refresh-profiles", json!("yes")),
(
FrontendEvent::RefreshProxyConfig,
"verge://refresh-proxy-config",
json!(null),
),
(
FrontendEvent::NoticeMessage {
status: NoticeStatus::SetConfigOk,
message: Arc::from("ok"),
},
"verge://notice-message",
json!(["set_config::ok", "ok"]),
),
(
FrontendEvent::TimerUpdated { profile_index: &uid },
"verge://timer-updated",
json!("profile"),
),
(
FrontendEvent::RunStateChanged {
state: json!({"running": true}),
},
"verge://run-state-changed",
json!({"running": true}),
),
(
FrontendEvent::PendingFailuresChanged,
"verge://pending-failures-changed",
json!(null),
),
(
FrontendEvent::ProfileChanged {
current_profile_id: &uid,
},
"profile-changed",
json!("profile"),
),
(
FrontendEvent::ProfileUpdateStarted { uid: &uid },
"profile-update-started",
json!({"uid": "profile"}),
),
(
FrontendEvent::ProfileUpdateCompleted { uid: &uid },
"profile-update-completed",
json!({"uid": "profile"}),
),
];
for (event, name, payload) in events {
let (actual_name, actual_payload) = NotificationSystem::serialize_event(event);
assert_eq!(actual_name, name);
assert_eq!(actual_payload.unwrap(), payload);
}
}
}
+5 -4
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
use crate::utils::dirs;
use crate::{
config::{Config, runtime::IRuntime},
@@ -46,7 +47,7 @@ static PENDING_SERVICE_OWNER_NOTICE: Mutex<Option<String>> = Mutex::new(None);
#[cfg(target_os = "windows")]
pub(crate) fn notify_service_fallback() {
PENDING_SERVICE_FALLBACK_NOTICE.store(true, Ordering::Relaxed);
Handle::notice_message("service_core::sidecar_fallback", "");
Handle::notice(NoticeStatus::ServiceCoreSidecarFallback, "");
}
pub(crate) fn take_service_fallback_notice() -> bool {
@@ -886,11 +887,11 @@ pub(super) async fn start_with_existing_service(config_file: &Path) -> Result<()
#[cfg(target_os = "windows")]
if response.code == ServiceErrorCode::InvalidInstallLocation as u16 {
PENDING_SERVICE_REPAIR_NOTICE.store(true, Ordering::Relaxed);
Handle::notice_message("service_core::repair_required", "");
Handle::notice(NoticeStatus::ServiceCoreRepairRequired, "");
}
if response.code == ServiceErrorCode::AppDataRootNotOwned as u16 {
*PENDING_SERVICE_OWNER_NOTICE.lock() = app_data_owner_command(&credentials);
Handle::notice_message("service_core::app_data_not_owned", "");
Handle::notice(NoticeStatus::ServiceCoreAppDataNotOwned, "");
}
start_owner_monitor();
return Err(record_service_start_refusal(
@@ -1610,7 +1611,7 @@ fn report_service_core_stopped(status: &ServiceStatusSnapshot) {
);
logging!(error, Type::Service, "service core stopped: {detail}");
CoreManager::global().record_startup_error(CoreFailure::ServiceCoreStopped(detail));
Handle::notice_message("core_start::error", "");
Handle::notice(NoticeStatus::CoreStartError, "");
}
/// Samples ownership, treating every unusable reply as unreadable.
+2 -2
View File
@@ -447,8 +447,8 @@ impl SilentUpdater {
Type::System,
"Silent updater: breaking change detected in v{version}, notifying frontend"
);
super::handle::Handle::notice_message(
"info",
super::handle::Handle::notice(
super::notify::NoticeStatus::Info,
format!("New version v{version} contains breaking changes. Please update manually."),
);
return Ok(());
+2 -1
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
mod chain;
pub mod field;
mod merge;
@@ -436,7 +437,7 @@ fn notify_discarded_keys(keys: Vec<String>) {
drop(notice);
if should_notify {
Handle::notice_message("enhance::discarded_keys", "");
Handle::notice(NoticeStatus::EnhanceDiscardedKeys, "");
}
}
+5 -4
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
use crate::{
config::{Config, MixedPort},
core::{CoreManager, handle, tray},
@@ -27,10 +28,10 @@ pub async fn restart_clash_core() {
match CoreManager::global().restart_core().await {
Ok(_) => {
handle::Handle::refresh_clash();
handle::Handle::notice_message("set_config::ok", "ok");
handle::Handle::notice(NoticeStatus::SetConfigOk, "ok");
}
Err(err) => {
handle::Handle::notice_message("set_config::error", format!("{err:#}"));
handle::Handle::notice(NoticeStatus::SetConfigError, format!("{err:#}"));
logging!(error, Type::Core, "restart core failed: {err:#}");
}
}
@@ -53,8 +54,8 @@ pub async fn restart_app() {
if !cleanup_result.core_stopped {
handle::Handle::global().clear_is_exiting();
handle::Handle::notice_message(
"app_restart::core_stop_failed",
handle::Handle::notice(
NoticeStatus::AppRestartCoreStopFailed,
cleanup_result.stop_error.unwrap_or_default(),
);
return;
+8 -4
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
use crate::{
cmd,
config::{Config, PrfItem, PrfOption, profiles::profiles_update_item_safe},
@@ -140,7 +141,7 @@ async fn perform_profile_update(
Ok(mut item) => {
logging!(info, Type::Config, "[订阅更新] 使用 Clash代理 更新订阅配置成功");
profiles_update_item_safe(uid, &mut item).await?;
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
handle::Handle::notice(NoticeStatus::UpdateWithClashProxy, profile_name.as_str());
return Ok(());
}
Err(err) => {
@@ -160,7 +161,7 @@ async fn perform_profile_update(
Ok(mut item) => {
logging!(info, Type::Config, "[订阅更新] 使用 系统代理 更新订阅配置成功");
profiles_update_item_safe(uid, &mut item).await?;
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
handle::Handle::notice(NoticeStatus::UpdateWithClashProxy, profile_name.as_str());
return Ok(());
}
Err(err) => {
@@ -176,7 +177,10 @@ async fn perform_profile_update(
let last_err = mask_err(&last_err.to_string());
if is_mannual_trigger {
handle::Handle::notice_message("update_failed_even_with_clash", format!("{profile_name} - {last_err}"));
handle::Handle::notice(
NoticeStatus::UpdateFailedEvenWithClash,
format!("{profile_name} - {last_err}"),
);
}
bail!(last_err)
}
@@ -218,7 +222,7 @@ pub async fn update_profile(uid: &String, option: Option<&PrfOption>, is_mannual
message
};
logging!(error, Type::Config, "[订阅更新] 更新失败: {}", message);
handle::Handle::notice_message("update_failed", &message);
handle::Handle::notice(NoticeStatus::UpdateFailed, message.as_str());
bail!(message);
}
}
+4 -2
View File
@@ -1,5 +1,7 @@
//! Reconciles TUN with current Core and Service availability.
use crate::core::notify::NoticeStatus;
use std::sync::atomic::{AtomicBool, Ordering};
use clash_verge_logging::{Type, logging};
@@ -75,10 +77,10 @@ pub async fn reconcile_tun_availability() {
// Skip recursive reconciliation for this internal patch.
match super::apply_verge_patch_locked(&config_write, &patch, false).await {
Ok(()) => Handle::notice_message("tun_mode::auto_disabled", ""),
Ok(()) => Handle::notice(NoticeStatus::TunModeAutoDisabled, ""),
Err(error) => {
logging!(error, Type::Core, "failed to turn TUN mode off: {error:#}");
Handle::notice_message("tun_mode::auto_disable_failed", "");
Handle::notice(NoticeStatus::TunModeAutoDisableFailed, "");
}
}
}
+3 -2
View File
@@ -1,4 +1,5 @@
use crate::config::Config;
use crate::core::notify::NoticeStatus;
use crate::core::{CoreManager, handle};
use crate::module::lightweight;
use crate::utils;
@@ -128,8 +129,8 @@ pub async fn quit() -> clash_verge_signal::ShutdownOutcome {
if should_abort_exit_after_cleanup(cleanup_result.core_stopped) {
handle::Handle::global().clear_is_exiting();
handle::Handle::notice_message(
"app_quit::core_stop_failed",
handle::Handle::notice(
NoticeStatus::AppQuitCoreStopFailed,
cleanup_result.stop_error.unwrap_or_default(),
);
return clash_verge_signal::ShutdownOutcome::Canceled;
+2 -1
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
use crate::{config::with_encryption, process::AsyncHandler};
use anyhow::{Context as _, Result, anyhow, bail};
use clash_verge_logging::{Type, logging};
@@ -58,7 +59,7 @@ fn parse_mapping(yaml_str: String, path: PathBuf) -> Result<Mapping> {
let error_msg = format!("YAML syntax error in {}: {}", path.display(), err);
logging!(error, Type::Config, "{}", error_msg);
crate::core::handle::Handle::notice_message("config_validate::yaml_syntax_error", &error_msg);
crate::core::handle::Handle::notice(NoticeStatus::ConfigValidateYamlSyntaxError, error_msg.as_str());
bail!("YAML syntax error: {}", err)
}
+1 -1
View File
@@ -8,7 +8,7 @@ pub mod linux;
#[cfg(target_os = "macos")]
pub mod macos_launch_guard;
pub(crate) mod network;
pub mod notification;
pub use crate::core::notify::desktop as notification;
pub mod port;
pub mod resolve;
#[cfg(target_os = "windows")]
+2 -1
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
use std::sync::atomic::{AtomicBool, Ordering};
use anyhow::Result;
@@ -223,7 +224,7 @@ async fn init_core_manager() -> bool {
Err(error) => {
logging!(error, Type::Setup, "core manager initialization failed: {error:#}");
CoreManager::global().record_startup_error(CoreFailure::StartFailed(format!("{error:#}")));
Handle::notice_message("core_start::error", "");
Handle::notice(NoticeStatus::CoreStartError, "");
false
}
}
+8 -7
View File
@@ -1,3 +1,4 @@
use crate::core::notify::NoticeStatus;
use anyhow::Result;
use percent_encoding::percent_decode_str;
use smartstring::alias::String;
@@ -93,18 +94,18 @@ async fn import_subscription(url: &str, name: Option<&String>) {
if let Err(e) = profiles::profiles_append_item_safe(&mut item).await {
logging!(error, Type::Config, "failed to import subscription url: {:?}", e);
Config::profiles().await.discard();
handle::Handle::notice_message("import_sub_url::error", e.to_string());
handle::Handle::notice(NoticeStatus::ImportSubUrlError, e.to_string());
return;
}
if let Err(e) = profiles::profiles_save_file_safe().await {
logging!(error, Type::Config, "failed to save imported subscription: {e:#}");
handle::Handle::notice_message("import_sub_url::error", e.to_string());
handle::Handle::notice(NoticeStatus::ImportSubUrlError, e.to_string());
return;
}
logging_error!(Type::Timer, Timer::global().refresh().await);
handle::Handle::notice_message(
"import_sub_url::ok",
handle::Handle::notice(
NoticeStatus::ImportSubUrlOk,
"", // 空 msg 传入,我们不希望导致 后端-前端-后端 死循环,这里只做提醒。
);
@@ -116,7 +117,7 @@ async fn fetch_profile_item(url: &str, name: Option<&String>) -> Option<PrfItem>
Ok(item) => Some(item),
Err(e) => {
logging!(error, Type::Config, "failed to parse profile from url: {:?}", e);
handle::Handle::notice_message("import_sub_url::error", e.to_string());
handle::Handle::notice(NoticeStatus::ImportSubUrlError, e.to_string());
None
}
}
@@ -149,11 +150,11 @@ async fn refresh_core_config() {
Ok(outcome) => {
let message = outcome.to_string();
logging!(warn, Type::Config, "Apply config failed: {}", message);
handle::Handle::notice_message("config_validate::error", message);
handle::Handle::notice(NoticeStatus::ConfigValidateError, message);
}
Err(err) => {
logging!(error, Type::Config, "Apply config error: {err:#}");
handle::Handle::notice_message("update_failed", format!("{err}"));
handle::Handle::notice(NoticeStatus::UpdateFailed, format!("{err}"));
}
}
}