mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-26 14:12:10 +08:00
refactor(core): type notice statuses behind the frozen wire contract
This commit is contained in:
@@ -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")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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 => {}
|
||||
}
|
||||
|
||||
@@ -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())))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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(());
|
||||
|
||||
@@ -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, "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user