use crate::singleton; use parking_lot::RwLock; use std::{ sync::{ atomic::{AtomicU64, Ordering}, mpsc, Arc, }, thread, time::{Duration, Instant}, }; use tauri::{AppHandle, Emitter, Manager, WebviewWindow}; use crate::{logging, utils::logging::Type}; /// 不同类型的前端通知 #[derive(Debug, Clone)] enum FrontendEvent { RefreshClash, RefreshVerge, NoticeMessage { status: String, message: String }, ProfileChanged { current_profile_id: String }, TimerUpdated { profile_index: String }, StartupCompleted, ProfileUpdateStarted { uid: String }, ProfileUpdateCompleted { uid: String }, } /// 事件发送统计和监控 #[derive(Debug, Default)] struct EventStats { total_sent: AtomicU64, total_errors: AtomicU64, last_error_time: RwLock>, } /// 存储启动期间的错误消息 #[derive(Debug, Clone)] struct ErrorMessage { status: String, message: String, } /// 全局前端通知系统 #[derive(Debug)] struct NotificationSystem { sender: Option>, worker_handle: Option>, is_running: bool, stats: EventStats, last_emit_time: RwLock, /// 当通知系统失败超过阈值时,进入紧急模式 emergency_mode: RwLock, } impl Default for NotificationSystem { fn default() -> Self { Self::new() } } impl NotificationSystem { fn new() -> Self { Self { sender: None, worker_handle: None, is_running: false, stats: EventStats::default(), last_emit_time: RwLock::new(Instant::now()), emergency_mode: RwLock::new(false), } } /// 启动通知处理线程 fn start(&mut self) { if self.is_running { return; } let (tx, rx) = mpsc::channel(); self.sender = Some(tx); self.is_running = true; *self.last_emit_time.write() = Instant::now(); match thread::Builder::new() .name("frontend-notifier".into()) .spawn(move || { let handle = Handle::global(); while !handle.is_exiting() { match rx.recv_timeout(Duration::from_millis(100)) { Ok(event) => { let system_guard = handle.notification_system.read(); let Some(system) = system_guard.as_ref() else { log::warn!("NotificationSystem not found in handle while processing event."); continue; }; let is_emergency = *system.emergency_mode.read(); if is_emergency { if let FrontendEvent::NoticeMessage { ref status, .. } = event { if status == "info" { log::warn!( "Emergency mode active, skipping info message" ); continue; } } } if let Some(window) = handle.get_window() { *system.last_emit_time.write() = Instant::now(); let (event_name_str, payload_result) = match event { FrontendEvent::RefreshClash => { ("verge://refresh-clash-config", Ok(serde_json::json!("yes"))) } FrontendEvent::RefreshVerge => { ("verge://refresh-verge-config", Ok(serde_json::json!("yes"))) } FrontendEvent::NoticeMessage { status, message } => { match serde_json::to_value((status, message)) { Ok(p) => ("verge://notice-message", Ok(p)), Err(e) => { log::error!("Failed to serialize NoticeMessage payload: {e}"); ("verge://notice-message", Err(e)) } } } FrontendEvent::ProfileChanged { current_profile_id } => { ("profile-changed", Ok(serde_json::json!(current_profile_id))) } FrontendEvent::TimerUpdated { profile_index } => { ("verge://timer-updated", Ok(serde_json::json!(profile_index))) } FrontendEvent::StartupCompleted => { ("verge://startup-completed", Ok(serde_json::json!(null))) } FrontendEvent::ProfileUpdateStarted { uid } => { ("profile-update-started", Ok(serde_json::json!({ "uid": uid }))) } FrontendEvent::ProfileUpdateCompleted { uid } => { ("profile-update-completed", Ok(serde_json::json!({ "uid": uid }))) } }; if let Ok(payload) = payload_result { match window.emit(event_name_str, payload) { Ok(_) => { system.stats.total_sent.fetch_add(1, Ordering::SeqCst); // 记录成功发送的事件 if log::log_enabled!(log::Level::Debug) { log::debug!("Successfully emitted event: {event_name_str}"); } } Err(e) => { log::warn!("Failed to emit event {event_name_str}: {e}"); system.stats.total_errors.fetch_add(1, Ordering::SeqCst); *system.stats.last_error_time.write() = Some(Instant::now()); let errors = system.stats.total_errors.load(Ordering::SeqCst); const EMIT_ERROR_THRESHOLD: u64 = 10; if errors > EMIT_ERROR_THRESHOLD && !*system.emergency_mode.read() { log::warn!( "Reached {EMIT_ERROR_THRESHOLD} emit errors, entering emergency mode" ); *system.emergency_mode.write() = true; } } } } else { system.stats.total_errors.fetch_add(1, Ordering::SeqCst); *system.stats.last_error_time.write() = Some(Instant::now()); log::warn!("Skipped emitting event due to payload serialization error for {event_name_str}"); } } else { log::warn!("No window found, skipping event emit."); } thread::sleep(Duration::from_millis(20)); } Err(mpsc::RecvTimeoutError::Timeout) => { } Err(mpsc::RecvTimeoutError::Disconnected) => { log::info!( "Notification channel disconnected, exiting worker thread" ); break; } } } log::info!("Notification worker thread exiting"); }) { Ok(handle) => { self.worker_handle = Some(handle); } Err(e) => { log::error!("Failed to start notification worker thread: {e}"); } } } /// 发送事件到队列 fn send_event(&self, event: FrontendEvent) -> bool { if *self.emergency_mode.read() { if let FrontendEvent::NoticeMessage { ref status, .. } = event { if status == "info" { log::info!("Skipping info message in emergency mode"); return false; } } } if let Some(sender) = &self.sender { match sender.send(event) { Ok(_) => true, Err(e) => { log::warn!("Failed to send event to notification queue: {e:?}"); self.stats.total_errors.fetch_add(1, Ordering::SeqCst); *self.stats.last_error_time.write() = Some(Instant::now()); false } } } else { log::warn!("Notification system not started, can't send event"); false } } fn shutdown(&mut self) { log::info!("NotificationSystem shutdown initiated"); self.is_running = false; // 先关闭发送端,让接收端知道不会再有新消息 if let Some(sender) = self.sender.take() { drop(sender); } // 设置超时避免无限等待 if let Some(handle) = self.worker_handle.take() { match handle.join() { Ok(_) => { log::info!("NotificationSystem worker thread joined successfully"); } Err(e) => { log::error!("NotificationSystem worker thread join failed: {e:?}"); } } } log::info!("NotificationSystem shutdown completed"); } } #[derive(Debug, Clone)] pub struct Handle { pub app_handle: Arc>>, pub is_exiting: Arc>, startup_errors: Arc>>, startup_completed: Arc>, notification_system: Arc>>, } impl Default for Handle { fn default() -> Self { Self { app_handle: Arc::new(RwLock::new(None)), is_exiting: Arc::new(RwLock::new(false)), startup_errors: Arc::new(RwLock::new(Vec::new())), startup_completed: Arc::new(RwLock::new(false)), notification_system: Arc::new(RwLock::new(Some(NotificationSystem::new()))), } } } // Use singleton macro singleton!(Handle, HANDLE); impl Handle { pub fn new() -> Self { Self::default() } pub fn init(&self, app_handle: AppHandle) { { let mut handle = self.app_handle.write(); *handle = Some(app_handle); } let mut system_opt = self.notification_system.write(); if let Some(system) = system_opt.as_mut() { system.start(); } } /// 获取 AppHandle pub fn app_handle(&self) -> Option { self.app_handle.read().clone() } pub fn get_window(&self) -> Option { let app_handle = self.app_handle()?; let window: Option = app_handle.get_webview_window("main"); if window.is_none() { log::debug!(target:"app", "main window not found"); } window } 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); } } 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); } } pub fn notify_profile_changed(profile_id: String) { 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::ProfileChanged { current_profile_id: profile_id, }); } else { log::warn!( "Notification system not initialized when trying to send ProfileChanged event." ); } } pub fn notify_timer_updated(profile_index: String) { 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::TimerUpdated { profile_index }); } else { log::warn!( "Notification system not initialized when trying to send TimerUpdated event." ); } } pub fn notify_startup_completed() { 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::StartupCompleted); } else { log::warn!( "Notification system not initialized when trying to send StartupCompleted event." ); } } pub fn notify_profile_update_started(uid: String) { 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::ProfileUpdateStarted { uid }); } else { log::warn!("Notification system not initialized when trying to send ProfileUpdateStarted event."); } } pub fn notify_profile_update_completed(uid: String) { 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::ProfileUpdateCompleted { uid }); } else { log::warn!("Notification system not initialized when trying to send ProfileUpdateCompleted event."); } } /// 通知前端显示消息队列 pub fn notice_message, M: Into>(status: S, msg: M) { let handle = Self::global(); let status_str = status.into(); let msg_str = msg.into(); if !*handle.startup_completed.read() { logging!( info, Type::Frontend, true, "启动过程中发现错误,加入消息队列: {} - {}", status_str, msg_str ); let mut errors = handle.startup_errors.write(); errors.push(ErrorMessage { status: status_str, message: msg_str, }); return; } if handle.is_exiting() { return; } let system_opt = handle.notification_system.read(); if let Some(system) = system_opt.as_ref() { system.send_event(FrontendEvent::NoticeMessage { status: status_str, message: msg_str, }); } } pub fn mark_startup_completed(&self) { { let mut completed = self.startup_completed.write(); *completed = true; } 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; } logging!( info, Type::Frontend, true, "发送{}条启动时累积的错误消息", errors.len() ); // 启动单独线程处理启动错误,避免阻塞主线程 let thread_result = thread::Builder::new() .name("startup-errors-sender".into()) .spawn(move || { thread::sleep(Duration::from_secs(2)); let handle = Handle::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(Duration::from_millis(300)); } } }); if let Err(e) = thread_result { log::error!("Failed to spawn startup errors thread: {e}"); } } pub fn set_is_exiting(&self) { let mut is_exiting = self.is_exiting.write(); *is_exiting = true; let mut system_opt = self.notification_system.write(); if let Some(system) = system_opt.as_mut() { system.shutdown(); } } pub fn is_exiting(&self) -> bool { *self.is_exiting.read() } } #[cfg(target_os = "macos")] impl Handle { pub fn set_activation_policy(&self, policy: tauri::ActivationPolicy) -> Result<(), String> { let app_handle = self.app_handle(); if let Some(app_handle) = app_handle.as_ref() { app_handle .set_activation_policy(policy) .map_err(|e| e.to_string()) } else { Err("AppHandle not initialized".to_string()) } } pub fn set_activation_policy_regular(&self) { if let Err(e) = self.set_activation_policy(tauri::ActivationPolicy::Regular) { logging!( warn, Type::Setup, true, "Failed to set regular activation policy: {}", e ); } } pub fn set_activation_policy_accessory(&self) { if let Err(e) = self.set_activation_policy(tauri::ActivationPolicy::Accessory) { logging!( warn, Type::Setup, true, "Failed to set accessory activation policy: {}", e ); } } #[allow(dead_code)] pub fn set_activation_policy_prohibited(&self) { if let Err(e) = self.set_activation_policy(tauri::ActivationPolicy::Prohibited) { logging!( warn, Type::Setup, true, "Failed to set prohibited activation policy: {}", e ); } } }