mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 07:14:40 +08:00
Compare commits
5 Commits
9d81a13c58
...
refactor/n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86c2952ea2 | ||
|
|
ccb9770c48 | ||
|
|
d7d9f64aad | ||
|
|
16749268cf | ||
|
|
1ea707699c |
@@ -1,7 +1,7 @@
|
||||
use super::{IClashTemp, IProfiles, IVerge};
|
||||
use crate::{
|
||||
config::{PrfItem, profiles_append_item_safe},
|
||||
constants::{files, timing},
|
||||
constants::files,
|
||||
core::{
|
||||
CoreManager,
|
||||
handle::{self, Handle},
|
||||
@@ -21,7 +21,6 @@ use smartstring::alias::String;
|
||||
use std::path::PathBuf;
|
||||
use tauri_plugin_clash_verge_sysinfo::is_current_app_handle_admin;
|
||||
use tokio::sync::OnceCell;
|
||||
use tokio::time::sleep;
|
||||
|
||||
pub struct Config {
|
||||
clash_config: Draft<IClashTemp>,
|
||||
@@ -88,7 +87,6 @@ impl Config {
|
||||
let validation_result = Self::generate_and_validate().await?;
|
||||
|
||||
if let Some((msg_type, msg_content)) = validation_result {
|
||||
sleep(timing::STARTUP_ERROR_DELAY).await;
|
||||
handle::Handle::notice_message(msg_type, msg_content);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,6 @@ pub mod timing {
|
||||
use super::Duration;
|
||||
|
||||
pub const CONFIG_UPDATE_DEBOUNCE: Duration = Duration::from_millis(300);
|
||||
pub const EVENT_EMIT_DELAY: Duration = Duration::from_millis(20);
|
||||
pub const STARTUP_ERROR_DELAY: Duration = Duration::from_secs(2);
|
||||
pub const ERROR_BATCH_DELAY: Duration = Duration::from_millis(300);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const SERVICE_WAIT_MAX: Duration = Duration::from_millis(3000);
|
||||
@@ -33,10 +30,6 @@ pub mod timing {
|
||||
pub const SERVICE_WAIT_INTERVAL: Duration = Duration::from_millis(200);
|
||||
}
|
||||
|
||||
pub mod retry {
|
||||
pub const EVENT_EMIT_THRESHOLD: u64 = 10;
|
||||
}
|
||||
|
||||
pub mod files {
|
||||
pub const RUNTIME_CONFIG: &str = "clash-verge.yaml";
|
||||
pub const CHECK_CONFIG: &str = "clash-verge-check.yaml";
|
||||
|
||||
@@ -1,36 +1,15 @@
|
||||
use crate::{APP_HANDLE, constants::timing, singleton};
|
||||
use parking_lot::RwLock;
|
||||
use crate::{APP_HANDLE, singleton};
|
||||
use smartstring::alias::String;
|
||||
use std::{
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
thread,
|
||||
};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tauri::{AppHandle, Manager as _, WebviewWindow};
|
||||
use tauri_plugin_mihomo::{Mihomo, MihomoExt as _};
|
||||
use tokio::sync::RwLockReadGuard;
|
||||
|
||||
use super::notification::{ErrorMessage, FrontendEvent, NotificationSystem};
|
||||
use super::notification::{FrontendEvent, NotificationSystem};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Handle {
|
||||
is_exiting: AtomicBool,
|
||||
startup_errors: Arc<RwLock<Vec<ErrorMessage>>>,
|
||||
startup_completed: AtomicBool,
|
||||
pub(crate) notification_system: Arc<RwLock<Option<NotificationSystem>>>,
|
||||
}
|
||||
|
||||
impl Default for Handle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_exiting: AtomicBool::new(false),
|
||||
startup_errors: Arc::new(RwLock::new(Vec::new())),
|
||||
startup_completed: AtomicBool::new(false),
|
||||
notification_system: Arc::new(RwLock::new(Some(NotificationSystem::new()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
singleton!(Handle, HANDLE);
|
||||
@@ -40,19 +19,6 @@ impl Handle {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn init(&self) {
|
||||
if self.is_exiting() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut system_opt = self.notification_system.write();
|
||||
if let Some(system) = system_opt.as_mut()
|
||||
&& !system.is_running
|
||||
{
|
||||
system.start();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn app_handle() -> &'static AppHandle {
|
||||
#[allow(clippy::expect_used)]
|
||||
APP_HANDLE.get().expect("App handle not initialized")
|
||||
@@ -67,27 +33,11 @@ impl Handle {
|
||||
}
|
||||
|
||||
pub fn refresh_clash() {
|
||||
let handle = Self::global();
|
||||
if handle.is_exiting() {
|
||||
return;
|
||||
}
|
||||
|
||||
let system_opt = handle.notification_system.read();
|
||||
if let Some(system) = system_opt.as_ref() {
|
||||
system.send_event(FrontendEvent::RefreshClash);
|
||||
}
|
||||
Self::send_event(FrontendEvent::RefreshClash);
|
||||
}
|
||||
|
||||
pub fn refresh_verge() {
|
||||
let handle = Self::global();
|
||||
if handle.is_exiting() {
|
||||
return;
|
||||
}
|
||||
|
||||
let system_opt = handle.notification_system.read();
|
||||
if let Some(system) = system_opt.as_ref() {
|
||||
system.send_event(FrontendEvent::RefreshVerge);
|
||||
}
|
||||
Self::send_event(FrontendEvent::RefreshVerge);
|
||||
}
|
||||
|
||||
pub fn notify_profile_changed(profile_id: String) {
|
||||
@@ -111,21 +61,13 @@ impl Handle {
|
||||
// TODO 利用 &str 等缩短 Clone
|
||||
pub fn notice_message<S: Into<String>, M: Into<String>>(status: S, msg: M) {
|
||||
let handle = Self::global();
|
||||
let status_str = status.into();
|
||||
let msg_str = msg.into();
|
||||
|
||||
if !handle.startup_completed.load(Ordering::Acquire) {
|
||||
handle.startup_errors.write().push(ErrorMessage {
|
||||
status: status_str,
|
||||
message: msg_str,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if handle.is_exiting() {
|
||||
return;
|
||||
}
|
||||
|
||||
let status_str = status.into();
|
||||
let msg_str = msg.into();
|
||||
|
||||
Self::send_event(FrontendEvent::NoticeMessage {
|
||||
status: status_str,
|
||||
message: msg_str,
|
||||
@@ -137,63 +79,12 @@ impl Handle {
|
||||
if handle.is_exiting() {
|
||||
return;
|
||||
}
|
||||
|
||||
let system_opt = handle.notification_system.read();
|
||||
if let Some(system) = system_opt.as_ref() {
|
||||
system.send_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_startup_completed(&self) {
|
||||
self.startup_completed.store(true, Ordering::Release);
|
||||
self.send_startup_errors();
|
||||
}
|
||||
|
||||
fn send_startup_errors(&self) {
|
||||
let errors = {
|
||||
let mut errors = self.startup_errors.write();
|
||||
std::mem::take(&mut *errors)
|
||||
};
|
||||
|
||||
if errors.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let _ = thread::Builder::new()
|
||||
.name("startup-errors-sender".into())
|
||||
.spawn(move || {
|
||||
thread::sleep(timing::STARTUP_ERROR_DELAY);
|
||||
|
||||
let handle = Self::global();
|
||||
if handle.is_exiting() {
|
||||
return;
|
||||
}
|
||||
|
||||
let system_opt = handle.notification_system.read();
|
||||
if let Some(system) = system_opt.as_ref() {
|
||||
for error in errors {
|
||||
if handle.is_exiting() {
|
||||
break;
|
||||
}
|
||||
|
||||
system.send_event(FrontendEvent::NoticeMessage {
|
||||
status: error.status,
|
||||
message: error.message,
|
||||
});
|
||||
|
||||
thread::sleep(timing::ERROR_BATCH_DELAY);
|
||||
}
|
||||
}
|
||||
});
|
||||
let webview = Self::get_window();
|
||||
NotificationSystem::send_event(webview, event);
|
||||
}
|
||||
|
||||
pub fn set_is_exiting(&self) {
|
||||
self.is_exiting.store(true, Ordering::Release);
|
||||
|
||||
let mut system_opt = self.notification_system.write();
|
||||
if let Some(system) = system_opt.as_mut() {
|
||||
system.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_exiting(&self) -> bool {
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
use super::handle::Handle;
|
||||
use crate::constants::{retry, timing};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use parking_lot::RwLock;
|
||||
use serde_json::json;
|
||||
use smartstring::alias::String;
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
mpsc,
|
||||
},
|
||||
thread,
|
||||
time::Instant,
|
||||
};
|
||||
use tauri::{Emitter as _, WebviewWindow};
|
||||
|
||||
use crate::process::AsyncHandler;
|
||||
|
||||
// TODO 重构或优化,避免 Clone 过多
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FrontendEvent {
|
||||
pub(super) enum FrontendEvent {
|
||||
RefreshClash,
|
||||
RefreshVerge,
|
||||
NoticeMessage { status: String, message: String },
|
||||
@@ -25,129 +17,28 @@ pub enum FrontendEvent {
|
||||
ProfileUpdateCompleted { uid: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct EventStats {
|
||||
total_sent: AtomicU64,
|
||||
total_errors: AtomicU64,
|
||||
last_error_time: RwLock<Option<Instant>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ErrorMessage {
|
||||
pub status: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NotificationSystem {
|
||||
sender: Option<mpsc::Sender<FrontendEvent>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
worker_handle: Option<thread::JoinHandle<()>>,
|
||||
pub(super) is_running: bool,
|
||||
stats: EventStats,
|
||||
emergency_mode: AtomicBool,
|
||||
}
|
||||
|
||||
impl Default for NotificationSystem {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
pub(super) struct NotificationSystem;
|
||||
|
||||
impl NotificationSystem {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sender: None,
|
||||
worker_handle: None,
|
||||
is_running: false,
|
||||
stats: EventStats::default(),
|
||||
emergency_mode: AtomicBool::new(false),
|
||||
pub(super) fn send_event(window: Option<WebviewWindow>, event: FrontendEvent) {
|
||||
if let Some(window) = window {
|
||||
AsyncHandler::spawn_blocking(move || {
|
||||
Self::emit_to_window(window, event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
if self.is_running {
|
||||
return;
|
||||
}
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
self.sender = Some(tx);
|
||||
self.is_running = true;
|
||||
|
||||
let result = thread::Builder::new()
|
||||
.name("frontend-notifier".into())
|
||||
.spawn(move || Self::worker_loop(rx));
|
||||
|
||||
match result {
|
||||
Ok(handle) => self.worker_handle = Some(handle),
|
||||
Err(e) => logging!(error, Type::System, "Failed to start notification worker: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_loop(rx: mpsc::Receiver<FrontendEvent>) {
|
||||
let handle = Handle::global();
|
||||
loop {
|
||||
if handle.is_exiting() {
|
||||
break;
|
||||
}
|
||||
match rx.recv() {
|
||||
Ok(event) => Self::process_event(handle, event),
|
||||
Err(e) => {
|
||||
logging!(error, Type::System, "Notification System will exit, recv error: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_event(handle: &super::handle::Handle, event: FrontendEvent) {
|
||||
let binding = handle.notification_system.read();
|
||||
let system = match binding.as_ref() {
|
||||
Some(s) => s,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if system.should_skip_event(&event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(window) = super::handle::Handle::get_window() {
|
||||
system.emit_to_window(&window, event);
|
||||
drop(binding);
|
||||
thread::sleep(timing::EVENT_EMIT_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
fn should_skip_event(&self, event: &FrontendEvent) -> bool {
|
||||
let is_emergency = self.emergency_mode.load(Ordering::Acquire);
|
||||
matches!(
|
||||
(is_emergency, event),
|
||||
(true, FrontendEvent::NoticeMessage { status, .. }) if status == "info"
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_to_window(&self, window: &WebviewWindow, event: FrontendEvent) {
|
||||
let (event_name, payload) = self.serialize_event(event);
|
||||
|
||||
fn emit_to_window(window: WebviewWindow, event: FrontendEvent) {
|
||||
let (event_name, payload) = Self::serialize_event(event);
|
||||
let Ok(payload) = payload else {
|
||||
self.stats.total_errors.fetch_add(1, Ordering::Relaxed);
|
||||
return;
|
||||
};
|
||||
|
||||
match window.emit(event_name, payload) {
|
||||
Ok(_) => {
|
||||
self.stats.total_sent.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(warn, Type::Frontend, "Event emit failed: {}", e);
|
||||
self.handle_emit_error();
|
||||
}
|
||||
if let Err(e) = window.emit(event_name, payload) {
|
||||
logging!(warn, Type::Frontend, "Event emit failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_event(&self, event: FrontendEvent) -> (&'static str, Result<serde_json::Value, serde_json::Error>) {
|
||||
use serde_json::json;
|
||||
|
||||
fn serialize_event(event: FrontendEvent) -> (&'static str, Result<serde_json::Value, serde_json::Error>) {
|
||||
match event {
|
||||
FrontendEvent::RefreshClash => ("verge://refresh-clash-config", Ok(json!("yes"))),
|
||||
FrontendEvent::RefreshVerge => ("verge://refresh-verge-config", Ok(json!("yes"))),
|
||||
@@ -160,39 +51,4 @@ impl NotificationSystem {
|
||||
FrontendEvent::ProfileUpdateCompleted { uid } => ("profile-update-completed", Ok(json!({ "uid": uid }))),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_emit_error(&self) {
|
||||
self.stats.total_errors.fetch_add(1, Ordering::Relaxed);
|
||||
*self.stats.last_error_time.write() = Some(Instant::now());
|
||||
|
||||
let errors = self.stats.total_errors.load(Ordering::Relaxed);
|
||||
if errors > retry::EVENT_EMIT_THRESHOLD && !self.emergency_mode.load(Ordering::Acquire) {
|
||||
logging!(warn, Type::Frontend, "Entering emergency mode after {} errors", errors);
|
||||
self.emergency_mode.store(true, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_event(&self, event: FrontendEvent) -> bool {
|
||||
if self.should_skip_event(&event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(sender) = &self.sender {
|
||||
sender.send(event).is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown(&mut self) {
|
||||
self.is_running = false;
|
||||
|
||||
if let Some(sender) = self.sender.take() {
|
||||
drop(sender);
|
||||
}
|
||||
|
||||
if let Some(handle) = self.worker_handle.take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +251,6 @@ pub fn run() {
|
||||
logging!(error, Type::Setup, "Failed to setup window state: {}", e);
|
||||
}
|
||||
|
||||
resolve::resolve_setup_handle();
|
||||
resolve::resolve_setup_async();
|
||||
resolve::resolve_setup_sync();
|
||||
resolve::init_signal();
|
||||
@@ -284,7 +283,6 @@ pub fn run() {
|
||||
}
|
||||
|
||||
logging!(info, Type::System, "应用就绪");
|
||||
handle::Handle::global().init();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(window) = _app_handle.get_webview_window("main") {
|
||||
@@ -294,8 +292,6 @@ pub fn run() {
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn handle_reopen(has_visible_windows: bool) {
|
||||
handle::Handle::global().init();
|
||||
|
||||
if lightweight::is_in_lightweight_mode() {
|
||||
lightweight::exit_lightweight_mode().await;
|
||||
return;
|
||||
|
||||
@@ -6,7 +6,7 @@ use flexi_logger::LoggerHandle;
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::{
|
||||
CoreManager, Timer, handle,
|
||||
CoreManager, Timer,
|
||||
hotkey::Hotkey,
|
||||
service::{SERVICE_MANAGER, ServiceManager, is_service_ipc_path_exists},
|
||||
sysopt,
|
||||
@@ -45,10 +45,6 @@ pub fn init_work_dir_and_logger() -> Option<LoggerHandle> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resolve_setup_handle() {
|
||||
init_handle();
|
||||
}
|
||||
|
||||
pub fn resolve_setup_sync() {
|
||||
AsyncHandler::spawn(|| async {
|
||||
AsyncHandler::spawn_blocking(init_scheme);
|
||||
@@ -101,10 +97,6 @@ pub async fn resolve_reset_async() -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init_handle() {
|
||||
handle::Handle::global().init();
|
||||
}
|
||||
|
||||
pub(super) fn init_scheme() {
|
||||
logging_error!(Type::Setup, init::init_scheme());
|
||||
}
|
||||
|
||||
@@ -110,7 +110,6 @@ impl WindowManager {
|
||||
logging!(info, Type::Window, "窗口不存在,创建新窗口");
|
||||
if Self::create_window(true).await {
|
||||
logging!(info, Type::Window, "窗口创建成功");
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
WindowOperationResult::Created
|
||||
} else {
|
||||
logging!(warn, Type::Window, "窗口创建失败");
|
||||
@@ -303,8 +302,6 @@ impl WindowManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
handle::Handle::global().mark_startup_completed();
|
||||
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user