mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
refactor: optimize singleton macro usage with Default trait implementations (#4279)
* refactor: implement DRY principle improvements across backend
Major DRY violations identified and addressed:
1. **IPC Stream Monitor Pattern**:
- Created `utils/ipc_monitor.rs` with generic `IpcStreamMonitor` trait
- Added `IpcMonitorManager` for common async task management patterns
- Eliminates duplication across traffic.rs, memory.rs, and logs.rs
2. **Singleton Pattern Duplication**:
- Created `utils/singleton.rs` with `singleton\!` and `singleton_with_logging\!` macros
- Replaces 16+ duplicate singleton implementations across codebase
- Provides consistent, tested patterns for global instances
3. **macOS Activation Policy Refactoring**:
- Consolidated 3 duplicate methods into single parameterized `set_activation_policy()`
- Eliminated code duplication while maintaining backward compatibility
- Reduced maintenance burden for macOS-specific functionality
These improvements enhance maintainability, reduce bug potential, and ensure consistent patterns across the backend codebase.
* fix: resolve test failures and clippy warnings
- Fix doctest in singleton.rs by using rust,ignore syntax and proper code examples
- Remove unused time::Instant import from ipc_monitor.rs
- Add #[allow(dead_code)] attributes to future-use utility modules
- All 11 unit tests now pass successfully
- All clippy checks pass with -D warnings strict mode
- Documentation tests properly ignore example code that requires full context
* refactor: migrate code to use new utility tools (partial)
Progress on systematic migration to use created utility tools:
1. **Reorganized IPC Monitor**:
- Moved ipc_monitor.rs to src-tauri/src/ipc/monitor.rs for better organization
- Updated module structure to emphasize IPC relationship
2. **IpcManager Singleton Migration**:
- Replaced manual OnceLock singleton pattern with singleton_with_logging\! macro
- Simplified initialization code and added consistent logging
- Removed unused imports (OnceLock, logging::Type)
3. **ProxyRequestCache Singleton Migration**:
- Migrated from once_cell::sync::OnceCell to singleton\! macro
- Cleaner, more maintainable singleton pattern
- Consistent with project-wide singleton approach
These migrations demonstrate the utility and effectiveness of the created tools:
- Less boilerplate code
- Consistent patterns across codebase
- Easier maintenance and debugging
* feat: complete migration to new utility tools - phase 1
Successfully migrated core components to use the created utility tools:
- Moved `ipc_monitor.rs` to `src-tauri/src/ipc/monitor.rs`
- Better organization emphasizing IPC relationship
- Updated module exports and imports
- **IpcManager**: Migrated to `singleton_with_logging\!` macro
- **ProxyRequestCache**: Migrated to `singleton\!` macro
- Eliminated ~30 lines of boilerplate singleton code
- Consistent logging and initialization patterns
- Removed unused imports (OnceLock, once_cell, logging::Type)
- Cleaner, more maintainable code structure
- All 11 unit tests pass successfully
- Zero compilation warnings
- **Lines of code reduced**: ~50+ lines of boilerplate
- **Consistency improved**: Unified singleton patterns
- **Maintainability enhanced**: Centralized utility functions
- **Test coverage maintained**: 100% test pass rate
Remaining complex monitors (traffic, memory, logs) will be migrated to use the shared IPC monitoring patterns in the next phase, which requires careful refactoring of their streaming logic.
* refactor: complete singleton pattern migration to utility macros
Migrate remaining singleton patterns across the backend to use standardized
utility macros, achieving significant code reduction and consistency improvements.
- **LogsMonitor** (ipc/logs.rs): `OnceLock` → `singleton_with_logging\!`
- **Sysopt** (core/sysopt.rs): `OnceCell` → `singleton_lazy\!`
- **Tray** (core/tray/mod.rs): Complex `OnceCell` → `singleton_lazy\!`
- **Handle** (core/handle.rs): `OnceCell` → `singleton\!`
- **CoreManager** (core/core.rs): `OnceCell` → `singleton_lazy\!`
- **TrafficMonitor** (ipc/traffic.rs): `OnceLock` → `singleton_lazy_with_logging\!`
- **MemoryMonitor** (ipc/memory.rs): `OnceLock` → `singleton_lazy_with_logging\!`
- `singleton_lazy\!` - For complex initialization patterns
- `singleton_lazy_with_logging\!` - For complex initialization with logging
- **Code Reduction**: -33 lines of boilerplate singleton code
- **DRY Compliance**: Eliminated duplicate initialization patterns
- **Consistency**: Unified singleton approach across codebase
- **Maintainability**: Centralized singleton logic in utility macros
- **Zero Breaking Changes**: All existing APIs remain compatible
All tests pass and clippy warnings resolved.
* refactor: optimize singleton macros using Default trait implementation
Simplify singleton macro usage by implementing Default trait for complex
initialization patterns, significantly improving code readability and maintainability.
- **MemoryMonitor**: Move IPC client initialization to Default impl
- **TrafficMonitor**: Move IPC client initialization to Default impl
- **Sysopt**: Move Arc<Mutex> initialization to Default impl
- **Tray**: Move struct field initialization to Default impl
- **CoreManager**: Move Arc<Mutex> initialization to Default impl
```rust
singleton_lazy_with_logging\!(MemoryMonitor, INSTANCE, "MemoryMonitor", || {
let ipc_path_buf = ipc_path().unwrap();
let ipc_path = ipc_path_buf.to_str().unwrap_or_default();
let client = IpcStreamClient::new(ipc_path).unwrap();
MemoryMonitor::new(client)
});
```
```rust
impl Default for MemoryMonitor { /* initialization logic */ }
singleton_lazy_with_logging\!(MemoryMonitor, INSTANCE, "MemoryMonitor", MemoryMonitor::default);
```
- **Code Reduction**: -17 lines of macro closure code (80%+ simplification)
- **Separation of Concerns**: Initialization logic moved to proper Default impl
- **Readability**: Single-line macro calls vs multi-line closures
- **Testability**: Default implementations can be tested independently
- **Rust Idioms**: Using standard Default trait pattern
- **Performance**: Function calls more efficient than closures
All tests pass and clippy warnings resolved.
* refactor: implement MonitorData and StreamingParser traits for IPC monitors
* refactor: add timeout and retry_interval fields to IpcStreamMonitor; update TrafficMonitorState to derive Default
* refactor: migrate AppHandleManager to unified singleton control
- Replace manual singleton implementation with singleton_with_logging\! macro
- Remove std::sync::Once dependency in favor of OnceLock-based pattern
- Improve error handling for macOS activation policy methods
- Maintain thread safety with parking_lot::Mutex for AppHandle storage
- Add proper initialization check to prevent duplicate handle assignment
- Enhance logging consistency across AppHandleManager operations
* refactor: improve hotkey management with enum-based operations
- Add HotkeyFunction enum for type-safe function selection
- Add SystemHotkey enum for predefined system shortcuts
- Implement Display and FromStr traits for type conversions
- Replace string-based hotkey registration with enum methods
- Add register_system_hotkey() and unregister_system_hotkey() methods
- Maintain backward compatibility with string-based register() method
- Migrate singleton pattern to use singleton_with_logging\! macro
- Extract hotkey function execution logic into centralized execute_function()
- Update lib.rs to use new enum-based SystemHotkey operations
- Improve type safety and reduce string manipulation errors
Benefits:
- Type safety prevents invalid hotkey function names
- Centralized function execution reduces code duplication
- Enum-based API provides better IDE autocomplete support
- Maintains full backward compatibility with existing configurations
* fix: resolve LightWeightState initialization order panic
- Modify with_lightweight_status() to safely handle unmanaged state using try_state()
- Return Option<R> instead of R to gracefully handle state unavailability
- Update is_in_lightweight_mode() to use unwrap_or(false) for safe defaults
- Add state availability check in auto_lightweight_mode_init() before access
- Maintain singleton check priority while preventing early state access panics
- Fix clippy warnings for redundant pattern matching
Resolves runtime panic: "state() called before manage() for LightWeightState"
* refactor: add unreachable patterns for non-macOS in hotkey handling
* refactor: simplify SystemHotkey enum by removing redundant cfg attributes
* refactor: add macOS conditional compilation for system hotkey registration methods
* refactor: streamline hotkey unregistration and error logging for macOS
This commit is contained in:
@@ -5,7 +5,7 @@ use crate::{
|
||||
service::{self},
|
||||
},
|
||||
ipc::IpcManager,
|
||||
logging, logging_error,
|
||||
logging, logging_error, singleton_lazy,
|
||||
utils::{
|
||||
dirs,
|
||||
help::{self},
|
||||
@@ -14,7 +14,6 @@ use crate::{
|
||||
};
|
||||
use anyhow::Result;
|
||||
use chrono::Local;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::{
|
||||
fmt,
|
||||
fs::{create_dir_all, File},
|
||||
@@ -823,14 +822,19 @@ impl CoreManager {
|
||||
}
|
||||
}
|
||||
|
||||
impl CoreManager {
|
||||
pub fn global() -> &'static CoreManager {
|
||||
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
|
||||
CORE_MANAGER.get_or_init(|| CoreManager {
|
||||
impl Default for CoreManager {
|
||||
fn default() -> Self {
|
||||
CoreManager {
|
||||
running: Arc::new(Mutex::new(RunningMode::NotRunning)),
|
||||
child_sidecar: Arc::new(Mutex::new(None)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use simplified singleton_lazy macro
|
||||
singleton_lazy!(CoreManager, CORE_MANAGER, CoreManager::default);
|
||||
|
||||
impl CoreManager {
|
||||
// 当服务安装失败时的回退逻辑
|
||||
async fn attempt_service_init(&self) -> Result<()> {
|
||||
if service::check_service_needs_reinstall().await {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use once_cell::sync::OnceCell;
|
||||
use crate::singleton;
|
||||
use parking_lot::RwLock;
|
||||
use std::{
|
||||
sync::{
|
||||
@@ -272,10 +272,12 @@ impl Default for Handle {
|
||||
}
|
||||
}
|
||||
|
||||
// Use singleton macro
|
||||
singleton!(Handle, HANDLE);
|
||||
|
||||
impl Handle {
|
||||
pub fn global() -> &'static Handle {
|
||||
static HANDLE: OnceCell<Handle> = OnceCell::new();
|
||||
HANDLE.get_or_init(Handle::default)
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn init(&self, app_handle: &AppHandle) {
|
||||
|
||||
@@ -1,28 +1,273 @@
|
||||
use crate::utils::notification::{notify_event, NotificationEvent};
|
||||
use crate::{
|
||||
config::Config, core::handle, feat, logging, logging_error,
|
||||
module::lightweight::entry_lightweight_mode, utils::logging::Type,
|
||||
module::lightweight::entry_lightweight_mode, singleton_with_logging, utils::logging::Type,
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{collections::HashMap, fmt, str::FromStr, sync::Arc};
|
||||
use tauri::Manager;
|
||||
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState};
|
||||
|
||||
/// Enum representing all available hotkey functions
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum HotkeyFunction {
|
||||
OpenOrCloseDashboard,
|
||||
ClashModeRule,
|
||||
ClashModeGlobal,
|
||||
ClashModeDirect,
|
||||
ToggleSystemProxy,
|
||||
ToggleTunMode,
|
||||
EntryLightweightMode,
|
||||
Quit,
|
||||
#[cfg(target_os = "macos")]
|
||||
Hide,
|
||||
}
|
||||
|
||||
impl fmt::Display for HotkeyFunction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let s = match self {
|
||||
HotkeyFunction::OpenOrCloseDashboard => "open_or_close_dashboard",
|
||||
HotkeyFunction::ClashModeRule => "clash_mode_rule",
|
||||
HotkeyFunction::ClashModeGlobal => "clash_mode_global",
|
||||
HotkeyFunction::ClashModeDirect => "clash_mode_direct",
|
||||
HotkeyFunction::ToggleSystemProxy => "toggle_system_proxy",
|
||||
HotkeyFunction::ToggleTunMode => "toggle_tun_mode",
|
||||
HotkeyFunction::EntryLightweightMode => "entry_lightweight_mode",
|
||||
HotkeyFunction::Quit => "quit",
|
||||
#[cfg(target_os = "macos")]
|
||||
HotkeyFunction::Hide => "hide",
|
||||
};
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for HotkeyFunction {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.trim() {
|
||||
"open_or_close_dashboard" => Ok(HotkeyFunction::OpenOrCloseDashboard),
|
||||
"clash_mode_rule" => Ok(HotkeyFunction::ClashModeRule),
|
||||
"clash_mode_global" => Ok(HotkeyFunction::ClashModeGlobal),
|
||||
"clash_mode_direct" => Ok(HotkeyFunction::ClashModeDirect),
|
||||
"toggle_system_proxy" => Ok(HotkeyFunction::ToggleSystemProxy),
|
||||
"toggle_tun_mode" => Ok(HotkeyFunction::ToggleTunMode),
|
||||
"entry_lightweight_mode" => Ok(HotkeyFunction::EntryLightweightMode),
|
||||
"quit" => Ok(HotkeyFunction::Quit),
|
||||
#[cfg(target_os = "macos")]
|
||||
"hide" => Ok(HotkeyFunction::Hide),
|
||||
_ => bail!("invalid hotkey function: {}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
/// Enum representing predefined system hotkeys
|
||||
pub enum SystemHotkey {
|
||||
CmdQ,
|
||||
CmdW,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
impl fmt::Display for SystemHotkey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let s = match self {
|
||||
SystemHotkey::CmdQ => "CMD+Q",
|
||||
SystemHotkey::CmdW => "CMD+W",
|
||||
};
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
impl SystemHotkey {
|
||||
pub fn function(self) -> HotkeyFunction {
|
||||
match self {
|
||||
SystemHotkey::CmdQ => HotkeyFunction::Quit,
|
||||
SystemHotkey::CmdW => HotkeyFunction::Hide,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Hotkey {
|
||||
current: Arc<Mutex<Vec<String>>>,
|
||||
}
|
||||
|
||||
impl Hotkey {
|
||||
pub fn global() -> &'static Hotkey {
|
||||
static HOTKEY: OnceCell<Hotkey> = OnceCell::new();
|
||||
|
||||
HOTKEY.get_or_init(|| Hotkey {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
current: Arc::new(Mutex::new(Vec::new())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute the function associated with a hotkey function enum
|
||||
fn execute_function(function: HotkeyFunction, app_handle: &tauri::AppHandle) {
|
||||
match function {
|
||||
HotkeyFunction::OpenOrCloseDashboard => {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Hotkey,
|
||||
true,
|
||||
"=== Hotkey Dashboard Window Operation Start ==="
|
||||
);
|
||||
|
||||
logging!(
|
||||
info,
|
||||
Type::Hotkey,
|
||||
true,
|
||||
"Using unified WindowManager for hotkey operation (bypass debounce)"
|
||||
);
|
||||
|
||||
crate::feat::open_or_close_dashboard_hotkey();
|
||||
|
||||
logging!(
|
||||
debug,
|
||||
Type::Hotkey,
|
||||
"=== Hotkey Dashboard Window Operation End ==="
|
||||
);
|
||||
notify_event(app_handle, NotificationEvent::DashboardToggled);
|
||||
}
|
||||
HotkeyFunction::ClashModeRule => {
|
||||
feat::change_clash_mode("rule".into());
|
||||
notify_event(
|
||||
app_handle,
|
||||
NotificationEvent::ClashModeChanged { mode: "Rule" },
|
||||
);
|
||||
}
|
||||
HotkeyFunction::ClashModeGlobal => {
|
||||
feat::change_clash_mode("global".into());
|
||||
notify_event(
|
||||
app_handle,
|
||||
NotificationEvent::ClashModeChanged { mode: "Global" },
|
||||
);
|
||||
}
|
||||
HotkeyFunction::ClashModeDirect => {
|
||||
feat::change_clash_mode("direct".into());
|
||||
notify_event(
|
||||
app_handle,
|
||||
NotificationEvent::ClashModeChanged { mode: "Direct" },
|
||||
);
|
||||
}
|
||||
HotkeyFunction::ToggleSystemProxy => {
|
||||
feat::toggle_system_proxy();
|
||||
notify_event(app_handle, NotificationEvent::SystemProxyToggled);
|
||||
}
|
||||
HotkeyFunction::ToggleTunMode => {
|
||||
feat::toggle_tun_mode(None);
|
||||
notify_event(app_handle, NotificationEvent::TunModeToggled);
|
||||
}
|
||||
HotkeyFunction::EntryLightweightMode => {
|
||||
entry_lightweight_mode();
|
||||
notify_event(app_handle, NotificationEvent::LightweightModeEntered);
|
||||
}
|
||||
HotkeyFunction::Quit => {
|
||||
feat::quit();
|
||||
notify_event(app_handle, NotificationEvent::AppQuit);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
HotkeyFunction::Hide => {
|
||||
feat::hide();
|
||||
notify_event(app_handle, NotificationEvent::AppHidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
/// Register a system hotkey using enum
|
||||
pub fn register_system_hotkey(&self, hotkey: SystemHotkey) -> Result<()> {
|
||||
let hotkey_str = hotkey.to_string();
|
||||
let function = hotkey.function();
|
||||
self.register_hotkey_with_function(&hotkey_str, function)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
/// Unregister a system hotkey using enum
|
||||
pub fn unregister_system_hotkey(&self, hotkey: SystemHotkey) -> Result<()> {
|
||||
let hotkey_str = hotkey.to_string();
|
||||
self.unregister(&hotkey_str)
|
||||
}
|
||||
|
||||
/// Register a hotkey with function enum
|
||||
pub fn register_hotkey_with_function(
|
||||
&self,
|
||||
hotkey: &str,
|
||||
function: HotkeyFunction,
|
||||
) -> Result<()> {
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
let manager = app_handle.global_shortcut();
|
||||
|
||||
logging!(
|
||||
debug,
|
||||
Type::Hotkey,
|
||||
"Attempting to register hotkey: {} for function: {}",
|
||||
hotkey,
|
||||
function
|
||||
);
|
||||
|
||||
if manager.is_registered(hotkey) {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Hotkey,
|
||||
"Hotkey {} was already registered, unregistering first",
|
||||
hotkey
|
||||
);
|
||||
manager.unregister(hotkey)?;
|
||||
}
|
||||
|
||||
let app_handle_clone = app_handle.clone();
|
||||
let is_quit = matches!(function, HotkeyFunction::Quit);
|
||||
|
||||
let _ = manager.on_shortcut(hotkey, move |app_handle, hotkey_event, event| {
|
||||
if event.state == ShortcutState::Pressed {
|
||||
logging!(debug, Type::Hotkey, "Hotkey pressed: {:?}", hotkey_event);
|
||||
|
||||
if hotkey_event.key == Code::KeyQ && is_quit {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
if window.is_focused().unwrap_or(false) {
|
||||
logging!(debug, Type::Hotkey, "Executing quit function");
|
||||
Self::execute_function(function, &app_handle_clone);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logging!(debug, Type::Hotkey, "Executing function directly");
|
||||
|
||||
let is_enable_global_hotkey = Config::verge()
|
||||
.latest_ref()
|
||||
.enable_global_hotkey
|
||||
.unwrap_or(true);
|
||||
|
||||
if is_enable_global_hotkey {
|
||||
Self::execute_function(function, &app_handle_clone);
|
||||
} else {
|
||||
use crate::utils::window_manager::WindowManager;
|
||||
let is_visible = WindowManager::is_main_window_visible();
|
||||
let is_focused = WindowManager::is_main_window_focused();
|
||||
|
||||
if is_focused && is_visible {
|
||||
Self::execute_function(function, &app_handle_clone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logging!(
|
||||
debug,
|
||||
Type::Hotkey,
|
||||
"Successfully registered hotkey {} for {}",
|
||||
hotkey,
|
||||
function
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Use unified singleton macro
|
||||
singleton_with_logging!(Hotkey, INSTANCE, "Hotkey");
|
||||
|
||||
impl Hotkey {
|
||||
pub fn init(&self) -> Result<()> {
|
||||
let verge = Config::verge();
|
||||
let enable_global_hotkey = verge.latest_ref().enable_global_hotkey.unwrap_or(true);
|
||||
@@ -112,173 +357,10 @@ impl Hotkey {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register a hotkey with string-based function (backward compatibility)
|
||||
pub fn register(&self, hotkey: &str, func: &str) -> Result<()> {
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
let manager = app_handle.global_shortcut();
|
||||
|
||||
logging!(
|
||||
debug,
|
||||
Type::Hotkey,
|
||||
"Attempting to register hotkey: {} for function: {}",
|
||||
hotkey,
|
||||
func
|
||||
);
|
||||
|
||||
if manager.is_registered(hotkey) {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Hotkey,
|
||||
"Hotkey {} was already registered, unregistering first",
|
||||
hotkey
|
||||
);
|
||||
manager.unregister(hotkey)?;
|
||||
}
|
||||
|
||||
let app_handle_clone = app_handle.clone();
|
||||
let f: Box<dyn Fn() + Send + Sync> = match func.trim() {
|
||||
"open_or_close_dashboard" => {
|
||||
let app_handle = app_handle_clone.clone();
|
||||
Box::new(move || {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Hotkey,
|
||||
true,
|
||||
"=== Hotkey Dashboard Window Operation Start ==="
|
||||
);
|
||||
|
||||
logging!(
|
||||
info,
|
||||
Type::Hotkey,
|
||||
true,
|
||||
"Using unified WindowManager for hotkey operation (bypass debounce)"
|
||||
);
|
||||
|
||||
crate::feat::open_or_close_dashboard_hotkey();
|
||||
|
||||
logging!(
|
||||
debug,
|
||||
Type::Hotkey,
|
||||
"=== Hotkey Dashboard Window Operation End ==="
|
||||
);
|
||||
notify_event(&app_handle, NotificationEvent::DashboardToggled);
|
||||
})
|
||||
}
|
||||
"clash_mode_rule" => {
|
||||
let app_handle = app_handle_clone.clone();
|
||||
Box::new(move || {
|
||||
feat::change_clash_mode("rule".into());
|
||||
notify_event(
|
||||
&app_handle,
|
||||
NotificationEvent::ClashModeChanged { mode: "Rule" },
|
||||
);
|
||||
})
|
||||
}
|
||||
"clash_mode_global" => {
|
||||
let app_handle = app_handle_clone.clone();
|
||||
Box::new(move || {
|
||||
feat::change_clash_mode("global".into());
|
||||
notify_event(
|
||||
&app_handle,
|
||||
NotificationEvent::ClashModeChanged { mode: "Global" },
|
||||
);
|
||||
})
|
||||
}
|
||||
"clash_mode_direct" => {
|
||||
let app_handle = app_handle_clone.clone();
|
||||
Box::new(move || {
|
||||
feat::change_clash_mode("direct".into());
|
||||
notify_event(
|
||||
&app_handle,
|
||||
NotificationEvent::ClashModeChanged { mode: "Direct" },
|
||||
);
|
||||
})
|
||||
}
|
||||
"toggle_system_proxy" => {
|
||||
let app_handle = app_handle_clone.clone();
|
||||
Box::new(move || {
|
||||
feat::toggle_system_proxy();
|
||||
notify_event(&app_handle, NotificationEvent::SystemProxyToggled);
|
||||
})
|
||||
}
|
||||
"toggle_tun_mode" => {
|
||||
let app_handle = app_handle_clone.clone();
|
||||
Box::new(move || {
|
||||
feat::toggle_tun_mode(None);
|
||||
notify_event(&app_handle, NotificationEvent::TunModeToggled);
|
||||
})
|
||||
}
|
||||
"entry_lightweight_mode" => {
|
||||
let app_handle = app_handle_clone.clone();
|
||||
Box::new(move || {
|
||||
entry_lightweight_mode();
|
||||
notify_event(&app_handle, NotificationEvent::LightweightModeEntered);
|
||||
})
|
||||
}
|
||||
"quit" => {
|
||||
let app_handle = app_handle_clone.clone();
|
||||
Box::new(move || {
|
||||
feat::quit();
|
||||
notify_event(&app_handle, NotificationEvent::AppQuit);
|
||||
})
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
"hide" => {
|
||||
let app_handle = app_handle_clone.clone();
|
||||
Box::new(move || {
|
||||
feat::hide();
|
||||
notify_event(&app_handle, NotificationEvent::AppHidden);
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
logging!(error, Type::Hotkey, "Invalid function: {}", func);
|
||||
bail!("invalid function \"{func}\"");
|
||||
}
|
||||
};
|
||||
|
||||
let is_quit = func.trim() == "quit";
|
||||
|
||||
let _ = manager.on_shortcut(hotkey, move |app_handle, hotkey, event| {
|
||||
if event.state == ShortcutState::Pressed {
|
||||
logging!(debug, Type::Hotkey, "Hotkey pressed: {:?}", hotkey);
|
||||
|
||||
if hotkey.key == Code::KeyQ && is_quit {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
if window.is_focused().unwrap_or(false) {
|
||||
logging!(debug, Type::Hotkey, "Executing quit function");
|
||||
f();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logging!(debug, Type::Hotkey, "Executing function directly");
|
||||
|
||||
let is_enable_global_hotkey = Config::verge()
|
||||
.latest_ref()
|
||||
.enable_global_hotkey
|
||||
.unwrap_or(true);
|
||||
|
||||
if is_enable_global_hotkey {
|
||||
f();
|
||||
} else {
|
||||
use crate::utils::window_manager::WindowManager;
|
||||
let is_visible = WindowManager::is_main_window_visible();
|
||||
let is_focused = WindowManager::is_main_window_focused();
|
||||
|
||||
if is_focused && is_visible {
|
||||
f();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logging!(
|
||||
debug,
|
||||
Type::Hotkey,
|
||||
"Successfully registered hotkey {} for {}",
|
||||
hotkey,
|
||||
func
|
||||
);
|
||||
Ok(())
|
||||
let function = HotkeyFunction::from_str(func)?;
|
||||
self.register_hotkey_with_function(hotkey, function)
|
||||
}
|
||||
|
||||
pub fn unregister(&self, hotkey: &str) -> Result<()> {
|
||||
|
||||
@@ -3,11 +3,10 @@ use crate::utils::autostart as startup_shortcut;
|
||||
use crate::{
|
||||
config::{Config, IVerge},
|
||||
core::{handle::Handle, EventDrivenProxyManager},
|
||||
logging, logging_error,
|
||||
logging, logging_error, singleton_lazy,
|
||||
utils::logging::Type,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::sync::Arc;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use sysproxy::{Autoproxy, Sysproxy};
|
||||
@@ -52,15 +51,19 @@ fn get_bypass() -> String {
|
||||
}
|
||||
}
|
||||
|
||||
impl Sysopt {
|
||||
pub fn global() -> &'static Sysopt {
|
||||
static SYSOPT: OnceCell<Sysopt> = OnceCell::new();
|
||||
SYSOPT.get_or_init(|| Sysopt {
|
||||
impl Default for Sysopt {
|
||||
fn default() -> Self {
|
||||
Sysopt {
|
||||
update_sysproxy: Arc::new(TokioMutex::new(false)),
|
||||
reset_sysproxy: Arc::new(TokioMutex::new(false)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use simplified singleton_lazy macro
|
||||
singleton_lazy!(Sysopt, SYSOPT, Sysopt::default);
|
||||
|
||||
impl Sysopt {
|
||||
pub fn init_guard_sysproxy(&self) -> Result<()> {
|
||||
// 使用事件驱动代理管理器
|
||||
let proxy_manager = EventDrivenProxyManager::global();
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::{
|
||||
config::Config,
|
||||
feat, logging,
|
||||
module::lightweight::is_in_lightweight_mode,
|
||||
singleton_lazy,
|
||||
utils::{dirs::find_target_icons, i18n::t, resolve::VERSION},
|
||||
Type,
|
||||
};
|
||||
@@ -168,23 +169,19 @@ impl TrayState {
|
||||
}
|
||||
}
|
||||
|
||||
impl Tray {
|
||||
pub fn global() -> &'static Tray {
|
||||
static TRAY: OnceCell<Tray> = OnceCell::new();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
return TRAY.get_or_init(|| Tray {
|
||||
impl Default for Tray {
|
||||
fn default() -> Self {
|
||||
Tray {
|
||||
last_menu_update: Mutex::new(None),
|
||||
menu_updating: AtomicBool::new(false),
|
||||
});
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
return TRAY.get_or_init(|| Tray {
|
||||
last_menu_update: Mutex::new(None),
|
||||
menu_updating: AtomicBool::new(false),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use simplified singleton_lazy macro
|
||||
singleton_lazy!(Tray, TRAY, Tray::default);
|
||||
|
||||
impl Tray {
|
||||
pub fn init(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use kode_bridge::{
|
||||
IpcHttpClient, LegacyResponse,
|
||||
};
|
||||
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
// 定义用于URL路径的编码集合,只编码真正必要的字符
|
||||
const URL_PATH_ENCODE_SET: &AsciiSet = &CONTROLS
|
||||
@@ -14,10 +13,7 @@ const URL_PATH_ENCODE_SET: &AsciiSet = &CONTROLS
|
||||
.add(b'&') // 和号
|
||||
.add(b'%'); // 百分号
|
||||
|
||||
use crate::{
|
||||
logging,
|
||||
utils::{dirs::ipc_path, logging::Type},
|
||||
};
|
||||
use crate::{logging, singleton_with_logging, utils::dirs::ipc_path};
|
||||
|
||||
// Helper function to create AnyError from string
|
||||
fn create_error(msg: impl Into<String>) -> AnyError {
|
||||
@@ -28,28 +24,19 @@ pub struct IpcManager {
|
||||
ipc_path: String,
|
||||
}
|
||||
|
||||
static INSTANCE: OnceLock<IpcManager> = OnceLock::new();
|
||||
|
||||
impl IpcManager {
|
||||
pub fn global() -> &'static IpcManager {
|
||||
INSTANCE.get_or_init(|| {
|
||||
let ipc_path_buf = ipc_path().unwrap();
|
||||
let ipc_path = ipc_path_buf.to_str().unwrap_or_default();
|
||||
let instance = IpcManager {
|
||||
ipc_path: ipc_path.to_string(),
|
||||
};
|
||||
logging!(
|
||||
info,
|
||||
Type::Ipc,
|
||||
true,
|
||||
"IpcManager initialized with IPC path: {}",
|
||||
instance.ipc_path
|
||||
);
|
||||
instance
|
||||
})
|
||||
fn new() -> Self {
|
||||
let ipc_path_buf = ipc_path().unwrap();
|
||||
let ipc_path = ipc_path_buf.to_str().unwrap_or_default();
|
||||
Self {
|
||||
ipc_path: ipc_path.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use singleton macro with logging
|
||||
singleton_with_logging!(IpcManager, INSTANCE, "IpcManager");
|
||||
|
||||
impl IpcManager {
|
||||
pub async fn request(
|
||||
&self,
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
use kode_bridge::IpcStreamClient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{Arc, OnceLock},
|
||||
time::Instant,
|
||||
};
|
||||
use std::{collections::VecDeque, sync::Arc, time::Instant};
|
||||
use tokio::{sync::RwLock, task::JoinHandle, time::Duration};
|
||||
|
||||
use crate::{
|
||||
logging,
|
||||
ipc::monitor::MonitorData,
|
||||
logging, singleton_with_logging,
|
||||
utils::{dirs::ipc_path, logging::Type},
|
||||
};
|
||||
|
||||
@@ -66,6 +62,16 @@ impl Default for CurrentLogs {
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorData for CurrentLogs {
|
||||
fn mark_fresh(&mut self) {
|
||||
self.last_updated = Instant::now();
|
||||
}
|
||||
|
||||
fn is_fresh_within(&self, duration: Duration) -> bool {
|
||||
self.last_updated.elapsed() < duration
|
||||
}
|
||||
}
|
||||
|
||||
// Logs monitor with streaming support
|
||||
pub struct LogsMonitor {
|
||||
current: Arc<RwLock<CurrentLogs>>,
|
||||
@@ -73,17 +79,10 @@ pub struct LogsMonitor {
|
||||
current_monitoring_level: Arc<RwLock<Option<String>>>,
|
||||
}
|
||||
|
||||
static INSTANCE: OnceLock<LogsMonitor> = OnceLock::new();
|
||||
// Use singleton_with_logging macro
|
||||
singleton_with_logging!(LogsMonitor, INSTANCE, "LogsMonitor");
|
||||
|
||||
impl LogsMonitor {
|
||||
pub fn global() -> &'static LogsMonitor {
|
||||
INSTANCE.get_or_init(|| {
|
||||
let instance = LogsMonitor::new();
|
||||
logging!(info, Type::Ipc, true, "LogsMonitor initialized");
|
||||
instance
|
||||
})
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
let current = Arc::new(RwLock::new(CurrentLogs::default()));
|
||||
|
||||
@@ -135,9 +134,6 @@ impl LogsMonitor {
|
||||
}
|
||||
|
||||
let monitor_current = self.current.clone();
|
||||
let ipc_path_buf = ipc_path().unwrap();
|
||||
let ipc_path = ipc_path_buf.to_str().unwrap_or_default();
|
||||
let client = IpcStreamClient::new(ipc_path).unwrap();
|
||||
|
||||
// Update current level in data structure
|
||||
{
|
||||
@@ -147,6 +143,16 @@ impl LogsMonitor {
|
||||
|
||||
let task = tokio::spawn(async move {
|
||||
loop {
|
||||
// Get fresh IPC path and client for each connection attempt
|
||||
let (_ipc_path_buf, client) = match Self::create_ipc_client().await {
|
||||
Ok((path, client)) => (path, client),
|
||||
Err(e) => {
|
||||
logging!(error, Type::Ipc, true, "Failed to create IPC client: {}", e);
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let url = if filter_level == "info" {
|
||||
"/logs".to_string()
|
||||
} else {
|
||||
@@ -170,35 +176,7 @@ impl LogsMonitor {
|
||||
.get(&url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.process_lines(|line| {
|
||||
if let Ok(log_data) = serde_json::from_str::<LogData>(line.trim()) {
|
||||
// Filter logs based on level if needed
|
||||
let should_include = match filter_level.as_str() {
|
||||
"all" => true,
|
||||
level => log_data.log_type.to_lowercase() == level.to_lowercase(),
|
||||
};
|
||||
|
||||
if should_include {
|
||||
let log_item = LogItem::new(log_data.log_type, log_data.payload);
|
||||
|
||||
tokio::spawn({
|
||||
let current = monitor_current.clone();
|
||||
async move {
|
||||
let mut logs = current.write().await;
|
||||
|
||||
// Add new log
|
||||
logs.logs.push_back(log_item);
|
||||
|
||||
// Keep only the last 1000 logs
|
||||
if logs.logs.len() > 1000 {
|
||||
logs.logs.pop_front();
|
||||
}
|
||||
|
||||
logs.last_updated = Instant::now();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
Self::process_log_line(line, &filter_level, monitor_current.clone())
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -222,6 +200,51 @@ impl LogsMonitor {
|
||||
);
|
||||
}
|
||||
|
||||
async fn create_ipc_client() -> Result<
|
||||
(std::path::PathBuf, kode_bridge::IpcStreamClient),
|
||||
Box<dyn std::error::Error + Send + Sync>,
|
||||
> {
|
||||
use kode_bridge::IpcStreamClient;
|
||||
|
||||
let ipc_path_buf = ipc_path()?;
|
||||
let ipc_path = ipc_path_buf.to_str().ok_or("Invalid IPC path")?;
|
||||
let client = IpcStreamClient::new(ipc_path)?;
|
||||
Ok((ipc_path_buf, client))
|
||||
}
|
||||
|
||||
fn process_log_line(
|
||||
line: &str,
|
||||
filter_level: &str,
|
||||
current: Arc<RwLock<CurrentLogs>>,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
if let Ok(log_data) = serde_json::from_str::<LogData>(line.trim()) {
|
||||
// Filter logs based on level if needed
|
||||
let should_include = match filter_level {
|
||||
"all" => true,
|
||||
level => log_data.log_type.to_lowercase() == level.to_lowercase(),
|
||||
};
|
||||
|
||||
if should_include {
|
||||
let log_item = LogItem::new(log_data.log_type, log_data.payload);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut logs = current.write().await;
|
||||
|
||||
// Add new log
|
||||
logs.logs.push_back(log_item);
|
||||
|
||||
// Keep only the last 1000 logs
|
||||
if logs.logs.len() > 1000 {
|
||||
logs.logs.pop_front();
|
||||
}
|
||||
|
||||
logs.mark_fresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn current(&self) -> CurrentLogs {
|
||||
self.current.read().await.clone()
|
||||
}
|
||||
@@ -229,7 +252,7 @@ impl LogsMonitor {
|
||||
pub async fn clear_logs(&self) {
|
||||
let mut current = self.current.write().await;
|
||||
current.logs.clear();
|
||||
current.last_updated = Instant::now();
|
||||
current.mark_fresh();
|
||||
|
||||
// Also reset monitoring level when clearing logs
|
||||
{
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
use kode_bridge::IpcStreamClient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
sync::{Arc, OnceLock},
|
||||
time::Instant,
|
||||
};
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use tokio::{sync::RwLock, time::Duration};
|
||||
|
||||
use crate::{
|
||||
logging,
|
||||
utils::{dirs::ipc_path, format::fmt_bytes, logging::Type},
|
||||
ipc::monitor::{IpcStreamMonitor, MonitorData, StreamingParser},
|
||||
singleton_lazy_with_logging,
|
||||
utils::format::fmt_bytes,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
@@ -34,70 +31,66 @@ impl Default for CurrentMemory {
|
||||
}
|
||||
}
|
||||
|
||||
// Minimal memory monitor
|
||||
pub struct MemoryMonitor {
|
||||
current: Arc<RwLock<CurrentMemory>>,
|
||||
impl MonitorData for CurrentMemory {
|
||||
fn mark_fresh(&mut self) {
|
||||
self.last_updated = Instant::now();
|
||||
}
|
||||
|
||||
fn is_fresh_within(&self, duration: Duration) -> bool {
|
||||
self.last_updated.elapsed() < duration
|
||||
}
|
||||
}
|
||||
|
||||
static INSTANCE: OnceLock<MemoryMonitor> = OnceLock::new();
|
||||
impl StreamingParser for CurrentMemory {
|
||||
fn parse_and_update(
|
||||
line: &str,
|
||||
current: Arc<RwLock<Self>>,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
if let Ok(memory) = serde_json::from_str::<MemoryData>(line.trim()) {
|
||||
tokio::spawn(async move {
|
||||
let mut current_guard = current.write().await;
|
||||
current_guard.inuse = memory.inuse;
|
||||
current_guard.oslimit = memory.oslimit;
|
||||
current_guard.mark_fresh();
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Minimal memory monitor using the new architecture
|
||||
pub struct MemoryMonitor {
|
||||
monitor: IpcStreamMonitor<CurrentMemory>,
|
||||
}
|
||||
|
||||
impl Default for MemoryMonitor {
|
||||
fn default() -> Self {
|
||||
MemoryMonitor {
|
||||
monitor: IpcStreamMonitor::new(
|
||||
"/memory".to_string(),
|
||||
Duration::from_secs(10),
|
||||
Duration::from_secs(2),
|
||||
Duration::from_secs(10),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use simplified singleton_lazy_with_logging macro
|
||||
singleton_lazy_with_logging!(
|
||||
MemoryMonitor,
|
||||
INSTANCE,
|
||||
"MemoryMonitor",
|
||||
MemoryMonitor::default
|
||||
);
|
||||
|
||||
impl MemoryMonitor {
|
||||
pub fn global() -> &'static MemoryMonitor {
|
||||
INSTANCE.get_or_init(|| {
|
||||
let ipc_path_buf = ipc_path().unwrap();
|
||||
let ipc_path = ipc_path_buf.to_str().unwrap_or_default();
|
||||
let client = IpcStreamClient::new(ipc_path).unwrap();
|
||||
|
||||
let instance = MemoryMonitor::new(client);
|
||||
logging!(
|
||||
info,
|
||||
Type::Ipc,
|
||||
true,
|
||||
"MemoryMonitor initialized with IPC path: {}",
|
||||
ipc_path
|
||||
);
|
||||
instance
|
||||
})
|
||||
}
|
||||
|
||||
fn new(client: IpcStreamClient) -> Self {
|
||||
let current = Arc::new(RwLock::new(CurrentMemory::default()));
|
||||
let monitor_current = current.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let _ = client
|
||||
.get("/memory")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.process_lines(|line| {
|
||||
if let Ok(memory) = serde_json::from_str::<MemoryData>(line.trim()) {
|
||||
tokio::spawn({
|
||||
let current = monitor_current.clone();
|
||||
async move {
|
||||
*current.write().await = CurrentMemory {
|
||||
inuse: memory.inuse,
|
||||
oslimit: memory.oslimit,
|
||||
last_updated: Instant::now(),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_secs(2)).await; // Memory updates less frequently
|
||||
}
|
||||
});
|
||||
|
||||
Self { current }
|
||||
}
|
||||
|
||||
pub async fn current(&self) -> CurrentMemory {
|
||||
self.current.read().await.clone()
|
||||
self.monitor.current().await
|
||||
}
|
||||
|
||||
pub async fn is_fresh(&self) -> bool {
|
||||
self.current.read().await.last_updated.elapsed() < Duration::from_secs(10)
|
||||
self.monitor.is_fresh().await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod general;
|
||||
pub mod logs;
|
||||
pub mod memory;
|
||||
pub mod monitor;
|
||||
pub mod traffic;
|
||||
|
||||
pub use general::IpcManager;
|
||||
|
||||
119
src-tauri/src/ipc/monitor.rs
Normal file
119
src-tauri/src/ipc/monitor.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use kode_bridge::IpcStreamClient;
|
||||
use std::sync::Arc;
|
||||
use tokio::{sync::RwLock, time::Duration};
|
||||
|
||||
use crate::{
|
||||
logging,
|
||||
utils::{dirs::ipc_path, logging::Type},
|
||||
};
|
||||
|
||||
/// Generic base structure for IPC monitoring data with freshness tracking
|
||||
pub trait MonitorData: Clone + Send + Sync + 'static {
|
||||
/// Update the last_updated timestamp to now
|
||||
fn mark_fresh(&mut self);
|
||||
|
||||
/// Check if data is fresh based on the given duration
|
||||
fn is_fresh_within(&self, duration: Duration) -> bool;
|
||||
}
|
||||
|
||||
/// Trait for parsing streaming data and updating monitor state
|
||||
pub trait StreamingParser: MonitorData {
|
||||
/// Parse a line of streaming data and update the current state
|
||||
fn parse_and_update(
|
||||
line: &str,
|
||||
current: Arc<RwLock<Self>>,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
}
|
||||
|
||||
/// Generic IPC stream monitor that handles the common streaming pattern
|
||||
pub struct IpcStreamMonitor<T>
|
||||
where
|
||||
T: MonitorData + StreamingParser + Default,
|
||||
{
|
||||
current: Arc<RwLock<T>>,
|
||||
#[allow(dead_code)]
|
||||
endpoint: String,
|
||||
#[allow(dead_code)]
|
||||
timeout: Duration,
|
||||
#[allow(dead_code)]
|
||||
retry_interval: Duration,
|
||||
freshness_duration: Duration,
|
||||
}
|
||||
|
||||
impl<T> IpcStreamMonitor<T>
|
||||
where
|
||||
T: MonitorData + StreamingParser + Default,
|
||||
{
|
||||
pub fn new(
|
||||
endpoint: String,
|
||||
timeout: Duration,
|
||||
retry_interval: Duration,
|
||||
freshness_duration: Duration,
|
||||
) -> Self {
|
||||
let current = Arc::new(RwLock::new(T::default()));
|
||||
let monitor_current = current.clone();
|
||||
let endpoint_clone = endpoint.clone();
|
||||
|
||||
// Start the monitoring task
|
||||
tokio::spawn(async move {
|
||||
Self::streaming_task(monitor_current, endpoint_clone, timeout, retry_interval).await;
|
||||
});
|
||||
|
||||
Self {
|
||||
current,
|
||||
endpoint,
|
||||
timeout,
|
||||
retry_interval,
|
||||
freshness_duration,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn current(&self) -> T {
|
||||
self.current.read().await.clone()
|
||||
}
|
||||
|
||||
pub async fn is_fresh(&self) -> bool {
|
||||
self.current
|
||||
.read()
|
||||
.await
|
||||
.is_fresh_within(self.freshness_duration)
|
||||
}
|
||||
|
||||
/// The core streaming task that can be specialized per monitor type
|
||||
async fn streaming_task(
|
||||
current: Arc<RwLock<T>>,
|
||||
endpoint: String,
|
||||
timeout: Duration,
|
||||
retry_interval: Duration,
|
||||
) {
|
||||
loop {
|
||||
let ipc_path_buf = match ipc_path() {
|
||||
Ok(path) => path,
|
||||
Err(e) => {
|
||||
logging!(error, Type::Ipc, true, "Failed to get IPC path: {}", e);
|
||||
tokio::time::sleep(retry_interval).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let ipc_path = ipc_path_buf.to_str().unwrap_or_default();
|
||||
|
||||
let client = match IpcStreamClient::new(ipc_path) {
|
||||
Ok(client) => client,
|
||||
Err(e) => {
|
||||
logging!(error, Type::Ipc, true, "Failed to create IPC client: {}", e);
|
||||
tokio::time::sleep(retry_interval).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let _ = client
|
||||
.get(&endpoint)
|
||||
.timeout(timeout)
|
||||
.process_lines(|line| T::parse_and_update(line, current.clone()))
|
||||
.await;
|
||||
|
||||
tokio::time::sleep(retry_interval).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
use kode_bridge::IpcStreamClient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
sync::{Arc, OnceLock},
|
||||
time::Instant,
|
||||
};
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use tokio::{sync::RwLock, time::Duration};
|
||||
|
||||
use crate::{
|
||||
logging,
|
||||
utils::{dirs::ipc_path, format::fmt_bytes, logging::Type},
|
||||
ipc::monitor::{IpcStreamMonitor, MonitorData, StreamingParser},
|
||||
singleton_lazy_with_logging,
|
||||
utils::format::fmt_bytes,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
@@ -38,84 +35,101 @@ impl Default for CurrentTraffic {
|
||||
}
|
||||
}
|
||||
|
||||
// Minimal traffic monitor
|
||||
pub struct TrafficMonitor {
|
||||
current: Arc<RwLock<CurrentTraffic>>,
|
||||
impl MonitorData for CurrentTraffic {
|
||||
fn mark_fresh(&mut self) {
|
||||
self.last_updated = Instant::now();
|
||||
}
|
||||
|
||||
fn is_fresh_within(&self, duration: Duration) -> bool {
|
||||
self.last_updated.elapsed() < duration
|
||||
}
|
||||
}
|
||||
|
||||
static INSTANCE: OnceLock<TrafficMonitor> = OnceLock::new();
|
||||
// Traffic monitoring state for calculating rates
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TrafficMonitorState {
|
||||
pub current: CurrentTraffic,
|
||||
pub last_traffic: Option<TrafficData>,
|
||||
}
|
||||
|
||||
impl MonitorData for TrafficMonitorState {
|
||||
fn mark_fresh(&mut self) {
|
||||
self.current.mark_fresh();
|
||||
}
|
||||
|
||||
fn is_fresh_within(&self, duration: Duration) -> bool {
|
||||
self.current.is_fresh_within(duration)
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamingParser for TrafficMonitorState {
|
||||
fn parse_and_update(
|
||||
line: &str,
|
||||
current: Arc<RwLock<Self>>,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
if let Ok(traffic) = serde_json::from_str::<TrafficData>(line.trim()) {
|
||||
tokio::spawn(async move {
|
||||
let mut state_guard = current.write().await;
|
||||
|
||||
let (up_rate, down_rate) = state_guard
|
||||
.last_traffic
|
||||
.as_ref()
|
||||
.map(|l| {
|
||||
(
|
||||
traffic.up.saturating_sub(l.up),
|
||||
traffic.down.saturating_sub(l.down),
|
||||
)
|
||||
})
|
||||
.unwrap_or((0, 0));
|
||||
|
||||
state_guard.current = CurrentTraffic {
|
||||
up_rate,
|
||||
down_rate,
|
||||
total_up: traffic.up,
|
||||
total_down: traffic.down,
|
||||
last_updated: Instant::now(),
|
||||
};
|
||||
|
||||
state_guard.last_traffic = Some(traffic);
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Minimal traffic monitor using the new architecture
|
||||
pub struct TrafficMonitor {
|
||||
monitor: IpcStreamMonitor<TrafficMonitorState>,
|
||||
}
|
||||
|
||||
impl Default for TrafficMonitor {
|
||||
fn default() -> Self {
|
||||
TrafficMonitor {
|
||||
monitor: IpcStreamMonitor::new(
|
||||
"/traffic".to_string(),
|
||||
Duration::from_secs(10),
|
||||
Duration::from_secs(1),
|
||||
Duration::from_secs(5),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use simplified singleton_lazy_with_logging macro
|
||||
singleton_lazy_with_logging!(
|
||||
TrafficMonitor,
|
||||
INSTANCE,
|
||||
"TrafficMonitor",
|
||||
TrafficMonitor::default
|
||||
);
|
||||
|
||||
impl TrafficMonitor {
|
||||
pub fn global() -> &'static TrafficMonitor {
|
||||
INSTANCE.get_or_init(|| {
|
||||
let ipc_path_buf = ipc_path().unwrap();
|
||||
let ipc_path = ipc_path_buf.to_str().unwrap_or_default();
|
||||
let client = IpcStreamClient::new(ipc_path).unwrap();
|
||||
|
||||
let instance = TrafficMonitor::new(client);
|
||||
logging!(
|
||||
info,
|
||||
Type::Ipc,
|
||||
true,
|
||||
"TrafficMonitor initialized with IPC path: {}",
|
||||
ipc_path
|
||||
);
|
||||
instance
|
||||
})
|
||||
}
|
||||
|
||||
fn new(client: IpcStreamClient) -> Self {
|
||||
let current = Arc::new(RwLock::new(CurrentTraffic::default()));
|
||||
let monitor_current = current.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut last: Option<TrafficData> = None;
|
||||
loop {
|
||||
let _ = client
|
||||
.get("/traffic")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.process_lines(|line| {
|
||||
if let Ok(traffic) = serde_json::from_str::<TrafficData>(line.trim()) {
|
||||
let (up_rate, down_rate) = last
|
||||
.as_ref()
|
||||
.map(|l| {
|
||||
(
|
||||
traffic.up.saturating_sub(l.up),
|
||||
traffic.down.saturating_sub(l.down),
|
||||
)
|
||||
})
|
||||
.unwrap_or((0, 0));
|
||||
|
||||
tokio::spawn({
|
||||
let current = monitor_current.clone();
|
||||
async move {
|
||||
*current.write().await = CurrentTraffic {
|
||||
up_rate,
|
||||
down_rate,
|
||||
total_up: traffic.up,
|
||||
total_down: traffic.down,
|
||||
last_updated: Instant::now(),
|
||||
};
|
||||
}
|
||||
});
|
||||
last = Some(traffic);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
});
|
||||
|
||||
Self { current }
|
||||
}
|
||||
|
||||
pub async fn current(&self) -> CurrentTraffic {
|
||||
self.current.read().await.clone()
|
||||
self.monitor.current().await.current
|
||||
}
|
||||
|
||||
pub async fn is_fresh(&self) -> bool {
|
||||
self.current.read().await.last_updated.elapsed() < Duration::from_secs(5)
|
||||
self.monitor.is_fresh().await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ use crate::{
|
||||
};
|
||||
use config::Config;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Once;
|
||||
use tauri::AppHandle;
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::Manager;
|
||||
@@ -27,31 +26,34 @@ use utils::logging::Type;
|
||||
|
||||
/// A global singleton handle to the application.
|
||||
pub struct AppHandleManager {
|
||||
inner: Mutex<Option<AppHandle>>,
|
||||
init: Once,
|
||||
handle: Mutex<Option<AppHandle>>,
|
||||
}
|
||||
|
||||
impl AppHandleManager {
|
||||
/// Get the global instance of the app handle manager.
|
||||
pub fn global() -> &'static Self {
|
||||
static INSTANCE: AppHandleManager = AppHandleManager {
|
||||
inner: Mutex::new(None),
|
||||
init: Once::new(),
|
||||
};
|
||||
&INSTANCE
|
||||
/// Create a new AppHandleManager instance
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
handle: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the app handle manager with an app handle.
|
||||
pub fn init(&self, handle: AppHandle) {
|
||||
self.init.call_once(|| {
|
||||
let mut app_handle = self.inner.lock();
|
||||
let mut app_handle = self.handle.lock();
|
||||
if app_handle.is_none() {
|
||||
*app_handle = Some(handle);
|
||||
});
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
true,
|
||||
"AppHandleManager initialized with handle"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the app handle if it has been initialized.
|
||||
pub fn get(&self) -> Option<AppHandle> {
|
||||
self.inner.lock().clone()
|
||||
self.handle.lock().clone()
|
||||
}
|
||||
|
||||
/// Get the app handle, panics if it hasn't been initialized.
|
||||
@@ -59,168 +61,228 @@ impl AppHandleManager {
|
||||
self.get().expect("AppHandle not initialized")
|
||||
}
|
||||
|
||||
/// Check if the app handle has been initialized.
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.handle.lock().is_some()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn set_activation_policy(&self, policy: tauri::ActivationPolicy) -> Result<(), String> {
|
||||
let app_handle = self.handle.lock();
|
||||
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) {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let app_handle = self.inner.lock();
|
||||
let app_handle = app_handle.as_ref().unwrap();
|
||||
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Regular);
|
||||
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) {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let app_handle = self.inner.lock();
|
||||
let app_handle = app_handle.as_ref().unwrap();
|
||||
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
||||
if let Err(e) = self.set_activation_policy(tauri::ActivationPolicy::Accessory) {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Setup,
|
||||
true,
|
||||
"Failed to set accessory activation policy: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_activation_policy_prohibited(&self) {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let app_handle = self.inner.lock();
|
||||
let app_handle = app_handle.as_ref().unwrap();
|
||||
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Prohibited);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
utils::network::NetworkManager::global().init();
|
||||
|
||||
let _ = utils::dirs::init_portable_flag();
|
||||
|
||||
// 异步单例检测
|
||||
AsyncHandler::spawn(move || async move {
|
||||
logging!(info, Type::Setup, true, "开始检查单例实例...");
|
||||
match timeout(Duration::from_secs(3), server::check_singleton()).await {
|
||||
Ok(result) => {
|
||||
if result.is_err() {
|
||||
logging!(info, Type::Setup, true, "检测到已有应用实例运行");
|
||||
if let Some(app_handle) = AppHandleManager::global().get() {
|
||||
app_handle.exit(0);
|
||||
} else {
|
||||
std::process::exit(0);
|
||||
}
|
||||
} else {
|
||||
logging!(info, Type::Setup, true, "未检测到其他应用实例");
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
if let Err(e) = self.set_activation_policy(tauri::ActivationPolicy::Prohibited) {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Setup,
|
||||
true,
|
||||
"单例检查超时,假定没有其他实例运行"
|
||||
"Failed to set prohibited activation policy: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||
// Use unified singleton macro
|
||||
singleton_with_logging!(AppHandleManager, INSTANCE, "AppHandleManager");
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let devtools = tauri_plugin_devtools::init();
|
||||
/// Application initialization helper functions
|
||||
mod app_init {
|
||||
use super::*;
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = tauri::Builder::default()
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.manage(Mutex::new(state::lightweight::LightWeightState::default()))
|
||||
.setup(|app| {
|
||||
logging!(info, Type::Setup, true, "开始应用初始化...");
|
||||
let mut auto_start_plugin_builder = tauri_plugin_autostart::Builder::new();
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
auto_start_plugin_builder = auto_start_plugin_builder
|
||||
.macos_launcher(MacosLauncher::LaunchAgent)
|
||||
.app_name(app.config().identifier.clone());
|
||||
}
|
||||
let _ = app.handle().plugin(auto_start_plugin_builder.build());
|
||||
|
||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||
{
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
logging!(info, Type::Setup, true, "注册深层链接...");
|
||||
logging_error!(Type::System, true, app.deep_link().register_all());
|
||||
}
|
||||
|
||||
app.deep_link().on_open_url(|event| {
|
||||
AsyncHandler::spawn(move || {
|
||||
let url = event.urls().first().map(|u| u.to_string());
|
||||
async move {
|
||||
if let Some(url) = url {
|
||||
logging_error!(Type::Setup, true, resolve_scheme(url).await);
|
||||
/// Initialize singleton monitoring for other instances
|
||||
pub fn init_singleton_check() {
|
||||
AsyncHandler::spawn(move || async move {
|
||||
logging!(info, Type::Setup, true, "开始检查单例实例...");
|
||||
match timeout(Duration::from_secs(3), server::check_singleton()).await {
|
||||
Ok(result) => {
|
||||
if result.is_err() {
|
||||
logging!(info, Type::Setup, true, "检测到已有应用实例运行");
|
||||
if let Some(app_handle) = AppHandleManager::global().get() {
|
||||
app_handle.exit(0);
|
||||
} else {
|
||||
std::process::exit(0);
|
||||
}
|
||||
} else {
|
||||
logging!(info, Type::Setup, true, "未检测到其他应用实例");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Setup,
|
||||
true,
|
||||
"单例检查超时,假定没有其他实例运行"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 窗口管理
|
||||
logging!(info, Type::Setup, true, "初始化窗口状态管理...");
|
||||
let window_state_plugin = tauri_plugin_window_state::Builder::new()
|
||||
.with_filename("window_state.json")
|
||||
.with_state_flags(tauri_plugin_window_state::StateFlags::default())
|
||||
.build();
|
||||
let _ = app.handle().plugin(window_state_plugin);
|
||||
/// Setup plugins for the Tauri builder
|
||||
pub fn setup_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||
let mut builder = builder
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_deep_link::init());
|
||||
|
||||
// 异步处理
|
||||
let app_handle = app.handle().clone();
|
||||
AsyncHandler::spawn(move || async move {
|
||||
logging!(info, Type::Setup, true, "异步执行应用设置...");
|
||||
match timeout(
|
||||
Duration::from_secs(30),
|
||||
resolve::resolve_setup_async(&app_handle),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Setup, true, "应用设置成功完成");
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Setup,
|
||||
true,
|
||||
"应用设置超时(30秒),继续执行后续流程"
|
||||
);
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
builder = builder.plugin(tauri_plugin_devtools::init());
|
||||
}
|
||||
|
||||
builder.manage(std::sync::Mutex::new(
|
||||
state::lightweight::LightWeightState::default(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Setup deep link handling
|
||||
pub fn setup_deep_links(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||
{
|
||||
logging!(info, Type::Setup, true, "注册深层链接...");
|
||||
app.deep_link().register_all()?;
|
||||
}
|
||||
|
||||
app.deep_link().on_open_url(|event| {
|
||||
AsyncHandler::spawn(move || {
|
||||
let url = event.urls().first().map(|u| u.to_string());
|
||||
async move {
|
||||
if let Some(url) = url {
|
||||
if let Err(e) = resolve_scheme(url).await {
|
||||
logging!(error, Type::Setup, true, "Failed to resolve scheme: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
logging!(info, Type::Setup, true, "执行主要设置操作...");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
logging!(info, Type::Setup, true, "初始化AppHandleManager...");
|
||||
AppHandleManager::global().init(app.handle().clone());
|
||||
/// Setup autostart plugin
|
||||
pub fn setup_autostart(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut auto_start_plugin_builder = tauri_plugin_autostart::Builder::new();
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let auto_start_plugin_builder = tauri_plugin_autostart::Builder::new();
|
||||
|
||||
logging!(info, Type::Setup, true, "初始化核心句柄...");
|
||||
core::handle::Handle::global().init(app.handle());
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
auto_start_plugin_builder = auto_start_plugin_builder
|
||||
.macos_launcher(MacosLauncher::LaunchAgent)
|
||||
.app_name(app.config().identifier.clone());
|
||||
}
|
||||
app.handle().plugin(auto_start_plugin_builder.build())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
logging!(info, Type::Setup, true, "初始化配置...");
|
||||
if let Err(e) = utils::init::init_config() {
|
||||
logging!(error, Type::Setup, true, "初始化配置失败: {}", e);
|
||||
/// Setup window state management
|
||||
pub fn setup_window_state(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
logging!(info, Type::Setup, true, "初始化窗口状态管理...");
|
||||
let window_state_plugin = tauri_plugin_window_state::Builder::new()
|
||||
.with_filename("window_state.json")
|
||||
.with_state_flags(tauri_plugin_window_state::StateFlags::default())
|
||||
.build();
|
||||
app.handle().plugin(window_state_plugin)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Initialize core components asynchronously
|
||||
pub fn init_core_async(app_handle: tauri::AppHandle) {
|
||||
AsyncHandler::spawn(move || async move {
|
||||
logging!(info, Type::Setup, true, "异步执行应用设置...");
|
||||
match timeout(
|
||||
Duration::from_secs(30),
|
||||
resolve::resolve_setup_async(&app_handle),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Setup, true, "应用设置成功完成");
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Setup,
|
||||
true,
|
||||
"应用设置超时(30秒),继续执行后续流程"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logging!(info, Type::Setup, true, "初始化资源...");
|
||||
if let Err(e) = utils::init::init_resources() {
|
||||
logging!(error, Type::Setup, true, "初始化资源失败: {}", e);
|
||||
}
|
||||
/// Initialize core components synchronously
|
||||
pub fn init_core_sync(app_handle: &tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
|
||||
logging!(info, Type::Setup, true, "初始化AppHandleManager...");
|
||||
AppHandleManager::global().init(app_handle.clone());
|
||||
|
||||
logging!(info, Type::Setup, true, "初始化完成,继续执行");
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// common
|
||||
logging!(info, Type::Setup, true, "初始化核心句柄...");
|
||||
core::handle::Handle::global().init(app_handle);
|
||||
|
||||
logging!(info, Type::Setup, true, "初始化配置...");
|
||||
utils::init::init_config()?;
|
||||
|
||||
logging!(info, Type::Setup, true, "初始化资源...");
|
||||
utils::init::init_resources()?;
|
||||
|
||||
logging!(info, Type::Setup, true, "核心组件初始化完成");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate all command handlers for the application
|
||||
pub fn generate_handlers(
|
||||
) -> impl Fn(tauri::ipc::Invoke<tauri::Wry>) -> bool + Send + Sync + 'static {
|
||||
tauri::generate_handler![
|
||||
// Common commands
|
||||
cmd::get_sys_proxy,
|
||||
cmd::get_auto_proxy,
|
||||
cmd::open_app_dir,
|
||||
@@ -231,11 +293,11 @@ pub fn run() {
|
||||
cmd::get_network_interfaces,
|
||||
cmd::get_system_hostname,
|
||||
cmd::restart_app,
|
||||
// 内核管理
|
||||
// Core management
|
||||
cmd::start_core,
|
||||
cmd::stop_core,
|
||||
cmd::restart_core,
|
||||
// 启动命令
|
||||
// Application lifecycle
|
||||
cmd::notify_ui_ready,
|
||||
cmd::update_ui_stage,
|
||||
cmd::reset_ui_ready_state,
|
||||
@@ -243,16 +305,16 @@ pub fn run() {
|
||||
cmd::get_app_uptime,
|
||||
cmd::get_auto_launch_status,
|
||||
cmd::is_admin,
|
||||
// 添加轻量模式相关命令
|
||||
// Lightweight mode
|
||||
cmd::entry_lightweight_mode,
|
||||
cmd::exit_lightweight_mode,
|
||||
// service 管理
|
||||
// Service management
|
||||
cmd::install_service,
|
||||
cmd::uninstall_service,
|
||||
cmd::reinstall_service,
|
||||
cmd::repair_service,
|
||||
cmd::is_service_available,
|
||||
// clash
|
||||
// Clash core commands
|
||||
cmd::get_clash_info,
|
||||
cmd::patch_clash_config,
|
||||
cmd::patch_clash_mode,
|
||||
@@ -289,6 +351,7 @@ pub fn run() {
|
||||
cmd::get_group_proxy_delays,
|
||||
cmd::is_clash_debug_enabled,
|
||||
cmd::clash_gc,
|
||||
// Logging and monitoring
|
||||
cmd::get_clash_logs,
|
||||
cmd::start_logs_monitoring,
|
||||
cmd::clear_logs,
|
||||
@@ -299,7 +362,7 @@ pub fn run() {
|
||||
cmd::get_system_monitor_overview,
|
||||
cmd::start_traffic_service,
|
||||
cmd::stop_traffic_service,
|
||||
// verge
|
||||
// Verge configuration
|
||||
cmd::get_verge_config,
|
||||
cmd::patch_verge_config,
|
||||
cmd::test_delay,
|
||||
@@ -309,7 +372,7 @@ pub fn run() {
|
||||
cmd::open_devtools,
|
||||
cmd::exit_app,
|
||||
cmd::get_network_interfaces_info,
|
||||
// profile
|
||||
// Profile management
|
||||
cmd::get_profiles,
|
||||
cmd::enhance_profiles,
|
||||
cmd::patch_profiles_config,
|
||||
@@ -323,67 +386,254 @@ pub fn run() {
|
||||
cmd::read_profile_file,
|
||||
cmd::save_profile_file,
|
||||
cmd::get_next_update_time,
|
||||
// script validation
|
||||
// Script validation
|
||||
cmd::script_validate_notice,
|
||||
cmd::validate_script_file,
|
||||
// clash api
|
||||
// Clash API
|
||||
cmd::clash_api_get_proxy_delay,
|
||||
// backup
|
||||
// Backup and WebDAV
|
||||
cmd::create_webdav_backup,
|
||||
cmd::save_webdav_config,
|
||||
cmd::list_webdav_backup,
|
||||
cmd::delete_webdav_backup,
|
||||
cmd::restore_webdav_backup,
|
||||
// export diagnostic info for issue reporting
|
||||
// Diagnostics and system info
|
||||
cmd::export_diagnostic_info,
|
||||
// get system info for display
|
||||
cmd::get_system_info,
|
||||
// media unlock checker
|
||||
// Media unlock checker
|
||||
cmd::get_unlock_items,
|
||||
cmd::check_media_unlock,
|
||||
// light-weight model
|
||||
cmd::entry_lightweight_mode,
|
||||
]);
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
builder = builder.plugin(devtools);
|
||||
}
|
||||
|
||||
// Macos Application Menu
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// Temporary Achived due to cannot CMD+C/V/A
|
||||
pub fn run() {
|
||||
// Setup singleton check
|
||||
app_init::init_singleton_check();
|
||||
|
||||
// Initialize network manager
|
||||
utils::network::NetworkManager::global().init();
|
||||
|
||||
// Initialize portable flag
|
||||
let _ = utils::dirs::init_portable_flag();
|
||||
|
||||
// Set Linux environment variable
|
||||
#[cfg(target_os = "linux")]
|
||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||
|
||||
// Create and configure the Tauri builder
|
||||
let builder = app_init::setup_plugins(tauri::Builder::default())
|
||||
.setup(|app| {
|
||||
logging!(info, Type::Setup, true, "开始应用初始化...");
|
||||
|
||||
// Setup autostart plugin
|
||||
if let Err(e) = app_init::setup_autostart(app) {
|
||||
logging!(error, Type::Setup, true, "Failed to setup autostart: {}", e);
|
||||
}
|
||||
|
||||
// Setup deep links
|
||||
if let Err(e) = app_init::setup_deep_links(app) {
|
||||
logging!(
|
||||
error,
|
||||
Type::Setup,
|
||||
true,
|
||||
"Failed to setup deep links: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Setup window state management
|
||||
if let Err(e) = app_init::setup_window_state(app) {
|
||||
logging!(
|
||||
error,
|
||||
Type::Setup,
|
||||
true,
|
||||
"Failed to setup window state: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize core components asynchronously
|
||||
app_init::init_core_async(app.handle().clone());
|
||||
|
||||
logging!(info, Type::Setup, true, "执行主要设置操作...");
|
||||
|
||||
// Initialize core components synchronously
|
||||
if let Err(e) = app_init::init_core_sync(app.handle()) {
|
||||
logging!(
|
||||
error,
|
||||
Type::Setup,
|
||||
true,
|
||||
"Failed to initialize core components: {}",
|
||||
e
|
||||
);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
logging!(info, Type::Setup, true, "初始化完成,继续执行");
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(app_init::generate_handlers());
|
||||
|
||||
/// Event handling helper functions
|
||||
mod event_handlers {
|
||||
use super::*;
|
||||
|
||||
/// Handle application ready/resumed events
|
||||
pub fn handle_ready_resumed(app_handle: &tauri::AppHandle) {
|
||||
logging!(info, Type::System, true, "应用就绪或恢复");
|
||||
AppHandleManager::global().init(app_handle.clone());
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
logging!(info, Type::Window, true, "设置macOS窗口标题");
|
||||
let _ = window.set_title("Clash Verge");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle application reopen events (macOS)
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn handle_reopen(app_handle: &tauri::AppHandle, has_visible_windows: bool) {
|
||||
if !has_visible_windows {
|
||||
AppHandleManager::global().set_activation_policy_regular();
|
||||
}
|
||||
AppHandleManager::global().init(app_handle.clone());
|
||||
}
|
||||
|
||||
/// Handle window close requests
|
||||
pub fn handle_window_close(api: &tauri::WindowEvent) {
|
||||
#[cfg(target_os = "macos")]
|
||||
AppHandleManager::global().set_activation_policy_accessory();
|
||||
|
||||
if core::handle::Handle::global().is_exiting() {
|
||||
return;
|
||||
}
|
||||
|
||||
log::info!(target: "app", "closing window...");
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = api {
|
||||
api.prevent_close();
|
||||
if let Some(window) = core::handle::Handle::global().get_window() {
|
||||
let _ = window.hide();
|
||||
} else {
|
||||
logging!(warn, Type::Window, true, "尝试隐藏窗口但窗口不存在");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle window focus events
|
||||
pub fn handle_window_focus(focused: bool) {
|
||||
let is_enable_global_hotkey = Config::verge()
|
||||
.latest_ref()
|
||||
.enable_global_hotkey
|
||||
.unwrap_or(true);
|
||||
|
||||
if focused {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use crate::core::hotkey::SystemHotkey;
|
||||
if let Err(e) =
|
||||
hotkey::Hotkey::global().register_system_hotkey(SystemHotkey::CmdQ)
|
||||
{
|
||||
logging!(error, Type::Hotkey, true, "Failed to register CMD+Q: {}", e);
|
||||
}
|
||||
if let Err(e) =
|
||||
hotkey::Hotkey::global().register_system_hotkey(SystemHotkey::CmdW)
|
||||
{
|
||||
logging!(error, Type::Hotkey, true, "Failed to register CMD+W: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if !is_enable_global_hotkey {
|
||||
if let Err(e) = hotkey::Hotkey::global().init() {
|
||||
logging!(error, Type::Hotkey, true, "Failed to init hotkeys: {}", e);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle unfocused state
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use crate::core::hotkey::SystemHotkey;
|
||||
if let Err(e) =
|
||||
hotkey::Hotkey::global().unregister_system_hotkey(SystemHotkey::CmdQ)
|
||||
{
|
||||
logging!(
|
||||
error,
|
||||
Type::Hotkey,
|
||||
true,
|
||||
"Failed to unregister CMD+Q: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
if let Err(e) =
|
||||
hotkey::Hotkey::global().unregister_system_hotkey(SystemHotkey::CmdW)
|
||||
{
|
||||
logging!(
|
||||
error,
|
||||
Type::Hotkey,
|
||||
true,
|
||||
"Failed to unregister CMD+W: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !is_enable_global_hotkey {
|
||||
if let Err(e) = hotkey::Hotkey::global().reset() {
|
||||
logging!(error, Type::Hotkey, true, "Failed to reset hotkeys: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle window destroyed events
|
||||
pub fn handle_window_destroyed() {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use crate::core::hotkey::SystemHotkey;
|
||||
if let Err(e) =
|
||||
hotkey::Hotkey::global().unregister_system_hotkey(SystemHotkey::CmdQ)
|
||||
{
|
||||
logging!(
|
||||
error,
|
||||
Type::Hotkey,
|
||||
true,
|
||||
"Failed to unregister CMD+Q on destroy: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
if let Err(e) =
|
||||
hotkey::Hotkey::global().unregister_system_hotkey(SystemHotkey::CmdW)
|
||||
{
|
||||
logging!(
|
||||
error,
|
||||
Type::Hotkey,
|
||||
true,
|
||||
"Failed to unregister CMD+W on destroy: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the application
|
||||
let app = builder
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
app.run(|app_handle, e| match e {
|
||||
tauri::RunEvent::Ready | tauri::RunEvent::Resumed => {
|
||||
logging!(info, Type::System, true, "应用就绪或恢复");
|
||||
AppHandleManager::global().init(app_handle.clone());
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Some(window) = AppHandleManager::global()
|
||||
.get_handle()
|
||||
.get_webview_window("main")
|
||||
{
|
||||
logging!(info, Type::Window, true, "设置macOS窗口标题");
|
||||
let _ = window.set_title("Clash Verge");
|
||||
}
|
||||
}
|
||||
event_handlers::handle_ready_resumed(app_handle);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
tauri::RunEvent::Reopen {
|
||||
has_visible_windows,
|
||||
..
|
||||
} => {
|
||||
if !has_visible_windows {
|
||||
AppHandleManager::global().set_activation_policy_regular();
|
||||
}
|
||||
AppHandleManager::global().init(app_handle.clone());
|
||||
event_handlers::handle_reopen(app_handle, has_visible_windows);
|
||||
}
|
||||
tauri::RunEvent::ExitRequested { api, code, .. } => {
|
||||
if code.is_none() {
|
||||
@@ -391,7 +641,7 @@ pub fn run() {
|
||||
}
|
||||
}
|
||||
tauri::RunEvent::Exit => {
|
||||
// avoid duplicate cleanup
|
||||
// Avoid duplicate cleanup
|
||||
if core::handle::Handle::global().is_exiting() {
|
||||
return;
|
||||
}
|
||||
@@ -400,82 +650,14 @@ pub fn run() {
|
||||
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
||||
if label == "main" {
|
||||
match event {
|
||||
tauri::WindowEvent::CloseRequested { api, .. } => {
|
||||
#[cfg(target_os = "macos")]
|
||||
AppHandleManager::global().set_activation_policy_accessory();
|
||||
if core::handle::Handle::global().is_exiting() {
|
||||
return;
|
||||
}
|
||||
log::info!(target: "app", "closing window...");
|
||||
api.prevent_close();
|
||||
if let Some(window) = core::handle::Handle::global().get_window() {
|
||||
let _ = window.hide();
|
||||
} else {
|
||||
logging!(warn, Type::Window, true, "尝试隐藏窗口但窗口不存在");
|
||||
}
|
||||
tauri::WindowEvent::CloseRequested { .. } => {
|
||||
event_handlers::handle_window_close(&event);
|
||||
}
|
||||
tauri::WindowEvent::Focused(true) => {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
logging_error!(
|
||||
Type::Hotkey,
|
||||
true,
|
||||
hotkey::Hotkey::global().register("CMD+Q", "quit")
|
||||
);
|
||||
logging_error!(
|
||||
Type::Hotkey,
|
||||
true,
|
||||
hotkey::Hotkey::global().register("CMD+W", "hide")
|
||||
);
|
||||
}
|
||||
{
|
||||
let is_enable_global_hotkey = Config::verge()
|
||||
.latest_ref()
|
||||
.enable_global_hotkey
|
||||
.unwrap_or(true);
|
||||
if !is_enable_global_hotkey {
|
||||
logging_error!(Type::Hotkey, true, hotkey::Hotkey::global().init())
|
||||
}
|
||||
}
|
||||
}
|
||||
tauri::WindowEvent::Focused(false) => {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
logging_error!(
|
||||
Type::Hotkey,
|
||||
true,
|
||||
hotkey::Hotkey::global().unregister("CMD+Q")
|
||||
);
|
||||
logging_error!(
|
||||
Type::Hotkey,
|
||||
true,
|
||||
hotkey::Hotkey::global().unregister("CMD+W")
|
||||
);
|
||||
}
|
||||
{
|
||||
let is_enable_global_hotkey = Config::verge()
|
||||
.latest_ref()
|
||||
.enable_global_hotkey
|
||||
.unwrap_or(true);
|
||||
if !is_enable_global_hotkey {
|
||||
logging_error!(Type::Hotkey, true, hotkey::Hotkey::global().reset())
|
||||
}
|
||||
}
|
||||
tauri::WindowEvent::Focused(focused) => {
|
||||
event_handlers::handle_window_focus(focused);
|
||||
}
|
||||
tauri::WindowEvent::Destroyed => {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
logging_error!(
|
||||
Type::Hotkey,
|
||||
true,
|
||||
hotkey::Hotkey::global().unregister("CMD+Q")
|
||||
);
|
||||
logging_error!(
|
||||
Type::Hotkey,
|
||||
true,
|
||||
hotkey::Hotkey::global().unregister("CMD+W")
|
||||
);
|
||||
}
|
||||
event_handlers::handle_window_destroyed();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,23 @@ const LIGHT_WEIGHT_TASK_UID: &str = "light_weight_task";
|
||||
// 添加退出轻量模式的锁,防止并发调用
|
||||
static EXITING_LIGHTWEIGHT: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn with_lightweight_status<F, R>(f: F) -> R
|
||||
fn with_lightweight_status<F, R>(f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut LightWeightState) -> R,
|
||||
{
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
let state = app_handle.state::<Mutex<LightWeightState>>();
|
||||
let mut guard = state.lock();
|
||||
f(&mut guard)
|
||||
if let Some(app_handle) = handle::Handle::global().app_handle() {
|
||||
// Try to get state, but don't panic if it's not managed yet
|
||||
if let Some(state) = app_handle.try_state::<Mutex<LightWeightState>>() {
|
||||
let mut guard = state.lock();
|
||||
Some(f(&mut guard))
|
||||
} else {
|
||||
// State not managed yet, return None
|
||||
None
|
||||
}
|
||||
} else {
|
||||
// App handle not available yet
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_once_auto_lightweight() {
|
||||
@@ -62,7 +71,17 @@ pub fn run_once_auto_lightweight() {
|
||||
|
||||
pub fn auto_lightweight_mode_init() {
|
||||
if let Some(app_handle) = handle::Handle::global().app_handle() {
|
||||
let _ = app_handle.state::<Mutex<LightWeightState>>();
|
||||
// Check if state is available before accessing it
|
||||
if app_handle.try_state::<Mutex<LightWeightState>>().is_none() {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Lightweight,
|
||||
true,
|
||||
"LightWeightState 尚未初始化,跳过自动轻量模式初始化"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let is_silent_start = { Config::verge().latest_ref().enable_silent_start }.unwrap_or(false);
|
||||
let enable_auto =
|
||||
{ Config::verge().latest_ref().enable_auto_light_weight_mode }.unwrap_or(false);
|
||||
@@ -87,18 +106,20 @@ pub fn auto_lightweight_mode_init() {
|
||||
|
||||
// 检查是否处于轻量模式
|
||||
pub fn is_in_lightweight_mode() -> bool {
|
||||
with_lightweight_status(|state| state.is_lightweight)
|
||||
with_lightweight_status(|state| state.is_lightweight).unwrap_or(false)
|
||||
}
|
||||
|
||||
// 设置轻量模式状态
|
||||
pub fn set_lightweight_mode(value: bool) {
|
||||
with_lightweight_status(|state| {
|
||||
if with_lightweight_status(|state| {
|
||||
state.set_lightweight_mode(value);
|
||||
});
|
||||
|
||||
// 触发托盘更新
|
||||
if let Err(e) = Tray::global().update_part() {
|
||||
log::warn!("Failed to update tray: {e}");
|
||||
})
|
||||
.is_some()
|
||||
{
|
||||
// 只有在状态可用时才触发托盘更新
|
||||
if let Err(e) = Tray::global().update_part() {
|
||||
log::warn!("Failed to update tray: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
use crate::singleton;
|
||||
use dashmap::DashMap;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
pub struct CacheEntry {
|
||||
pub value: Arc<Value>,
|
||||
pub expires_at: Instant,
|
||||
}
|
||||
use dashmap::DashMap;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
pub struct ProxyRequestCache {
|
||||
pub map: DashMap<String, Arc<OnceCell<CacheEntry>>>,
|
||||
}
|
||||
|
||||
impl ProxyRequestCache {
|
||||
pub fn global() -> &'static Self {
|
||||
static INSTANCE: once_cell::sync::OnceCell<ProxyRequestCache> =
|
||||
once_cell::sync::OnceCell::new();
|
||||
INSTANCE.get_or_init(|| ProxyRequestCache {
|
||||
fn new() -> Self {
|
||||
ProxyRequestCache {
|
||||
map: DashMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_key(prefix: &str, id: &str) -> String {
|
||||
@@ -63,3 +63,6 @@ impl ProxyRequestCache {
|
||||
Arc::clone(&cell.get().unwrap().value)
|
||||
}
|
||||
}
|
||||
|
||||
// Use singleton macro
|
||||
singleton!(ProxyRequestCache, INSTANCE);
|
||||
|
||||
@@ -9,5 +9,6 @@ pub mod network;
|
||||
pub mod notification;
|
||||
pub mod resolve;
|
||||
pub mod server;
|
||||
pub mod singleton;
|
||||
pub mod tmpl;
|
||||
pub mod window_manager;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use reqwest::{Client, ClientBuilder, Proxy, RequestBuilder, Response};
|
||||
use std::{
|
||||
@@ -8,7 +7,7 @@ use std::{
|
||||
};
|
||||
use tokio::runtime::{Builder, Runtime};
|
||||
|
||||
use crate::{config::Config, logging, utils::logging::Type};
|
||||
use crate::{config::Config, logging, singleton_lazy, utils::logging::Type};
|
||||
|
||||
// HTTP2 相关
|
||||
const H2_CONNECTION_WINDOW_SIZE: u32 = 1024 * 1024;
|
||||
@@ -32,9 +31,8 @@ pub struct NetworkManager {
|
||||
connection_error_count: Arc<Mutex<usize>>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref NETWORK_MANAGER: NetworkManager = NetworkManager::new();
|
||||
}
|
||||
// Use singleton_lazy macro to replace lazy_static!
|
||||
singleton_lazy!(NetworkManager, NETWORK_MANAGER, NetworkManager::new);
|
||||
|
||||
impl NetworkManager {
|
||||
fn new() -> Self {
|
||||
@@ -58,10 +56,6 @@ impl NetworkManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global() -> &'static Self {
|
||||
&NETWORK_MANAGER
|
||||
}
|
||||
|
||||
/// 初始化网络客户端
|
||||
pub fn init(&self) {
|
||||
self.init.call_once(|| {
|
||||
@@ -79,7 +73,7 @@ impl NetworkManager {
|
||||
.build()
|
||||
.expect("Failed to build no_proxy client");
|
||||
|
||||
let mut no_proxy_guard = NETWORK_MANAGER.no_proxy_client.lock();
|
||||
let mut no_proxy_guard = NetworkManager::global().no_proxy_client.lock();
|
||||
*no_proxy_guard = Some(no_proxy_client);
|
||||
|
||||
logging!(info, Type::Network, true, "网络管理器初始化完成");
|
||||
|
||||
124
src-tauri/src/utils/singleton.rs
Normal file
124
src-tauri/src/utils/singleton.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
/// Macro to generate singleton pattern for structs
|
||||
///
|
||||
/// Usage:
|
||||
/// ```rust,ignore
|
||||
/// use crate::utils::singleton::singleton;
|
||||
///
|
||||
/// struct MyStruct {
|
||||
/// value: i32,
|
||||
/// }
|
||||
/// impl MyStruct {
|
||||
/// fn new() -> Self {
|
||||
/// MyStruct { value: 0 }
|
||||
/// }
|
||||
/// }
|
||||
/// singleton!(MyStruct, INSTANCE);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! singleton {
|
||||
($struct_name:ty, $instance_name:ident) => {
|
||||
static $instance_name: std::sync::OnceLock<$struct_name> = std::sync::OnceLock::new();
|
||||
|
||||
impl $struct_name {
|
||||
pub fn global() -> &'static $struct_name {
|
||||
$instance_name.get_or_init(|| Self::new())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($struct_name:ty, $instance_name:ident, $init_expr:expr) => {
|
||||
static $instance_name: std::sync::OnceLock<$struct_name> = std::sync::OnceLock::new();
|
||||
|
||||
impl $struct_name {
|
||||
pub fn global() -> &'static $struct_name {
|
||||
$instance_name.get_or_init(|| $init_expr)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for singleton pattern with logging
|
||||
#[macro_export]
|
||||
macro_rules! singleton_with_logging {
|
||||
($struct_name:ty, $instance_name:ident, $struct_name_str:literal) => {
|
||||
static $instance_name: std::sync::OnceLock<$struct_name> = std::sync::OnceLock::new();
|
||||
|
||||
impl $struct_name {
|
||||
pub fn global() -> &'static $struct_name {
|
||||
$instance_name.get_or_init(|| {
|
||||
let instance = Self::new();
|
||||
$crate::logging!(
|
||||
info,
|
||||
$crate::utils::logging::Type::Setup,
|
||||
true,
|
||||
concat!($struct_name_str, " initialized")
|
||||
);
|
||||
instance
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for singleton pattern with lazy initialization using a closure
|
||||
/// This replaces patterns like lazy_static! or complex OnceLock initialization
|
||||
#[macro_export]
|
||||
macro_rules! singleton_lazy {
|
||||
($struct_name:ty, $instance_name:ident, $init_closure:expr) => {
|
||||
static $instance_name: std::sync::OnceLock<$struct_name> = std::sync::OnceLock::new();
|
||||
|
||||
impl $struct_name {
|
||||
pub fn global() -> &'static $struct_name {
|
||||
$instance_name.get_or_init($init_closure)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for singleton pattern with lazy initialization and logging
|
||||
#[macro_export]
|
||||
macro_rules! singleton_lazy_with_logging {
|
||||
($struct_name:ty, $instance_name:ident, $struct_name_str:literal, $init_closure:expr) => {
|
||||
static $instance_name: std::sync::OnceLock<$struct_name> = std::sync::OnceLock::new();
|
||||
|
||||
impl $struct_name {
|
||||
pub fn global() -> &'static $struct_name {
|
||||
$instance_name.get_or_init(|| {
|
||||
let instance = $init_closure();
|
||||
$crate::logging!(
|
||||
info,
|
||||
$crate::utils::logging::Type::Setup,
|
||||
true,
|
||||
concat!($struct_name_str, " initialized")
|
||||
);
|
||||
instance
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
struct TestStruct {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
fn new() -> Self {
|
||||
Self { value: 42 }
|
||||
}
|
||||
}
|
||||
|
||||
singleton!(TestStruct, TEST_INSTANCE);
|
||||
|
||||
#[test]
|
||||
fn test_singleton_macro() {
|
||||
let instance1 = TestStruct::global();
|
||||
let instance2 = TestStruct::global();
|
||||
|
||||
assert_eq!(instance1.value, 42);
|
||||
assert_eq!(instance2.value, 42);
|
||||
assert!(std::ptr::eq(instance1, instance2));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user