Files
clash-verge-rev/src-tauri/src/utils/help.rs
Tunglies 355a18e5eb refactor(async): migrate from sync-blocking async execution to true async with unified AsyncHandler::spawn (#4502)
* feat: replace all tokio::spawn with unified AsyncHandler::spawn

- 🚀 Core Improvements:
  * Replace all tokio::spawn calls with AsyncHandler::spawn for unified Tauri async task management
  * Prioritize converting sync functions to async functions to reduce spawn usage
  * Use .await directly in async contexts instead of spawn

- 🔧 Major Changes:
  * core/hotkey.rs: Use AsyncHandler::spawn for hotkey callback functions
  * module/lightweight.rs: Async lightweight mode switching
  * feat/window.rs: Convert window operation functions to async, use .await internally
  * feat/proxy.rs, feat/clash.rs: Async proxy and mode switching functions
  * lib.rs: Window focus handling with AsyncHandler::spawn
  * core/tray/mod.rs: Complete async tray event handling

-  Technical Advantages:
  * Unified task tracking and debugging capabilities (via tokio-trace feature)
  * Better error handling and task management
  * Consistency with Tauri runtime
  * Reduced async boundaries for better performance

- 🧪 Verification:
  * Compilation successful with 0 errors, 0 warnings
  * Maintains complete original functionality
  * Optimized async execution flow

* feat: complete tokio fs migration and replace tokio::spawn with AsyncHandler

🚀 Major achievements:
- Migrate 8 core modules from std::fs to tokio::fs
- Create 6 Send-safe wrapper functions using spawn_blocking pattern
- Replace all tokio::spawn calls with AsyncHandler::spawn for unified async task management
- Solve all 19 Send trait compilation errors through innovative spawn_blocking architecture

🔧 Core changes:
- config/profiles.rs: Add profiles_*_safe functions to handle Send trait constraints
- cmd/profile.rs: Update all Tauri commands to use Send-safe operations
- config/prfitem.rs: Replace append_item calls with profiles_append_item_safe
- utils/help.rs: Convert YAML operations to async (read_yaml, save_yaml)
- Multiple modules: Replace tokio::task::spawn_blocking with AsyncHandler::spawn_blocking

 Technical innovations:
- spawn_blocking wrapper pattern resolves parking_lot RwLock Send trait conflicts
- Maintain parking_lot performance while achieving Tauri async command compatibility
- Preserve backwards compatibility with gradual migration strategy

🎯 Results:
- Zero compilation errors
- Zero warnings
- All async file operations working correctly
- Complete Send trait compliance for Tauri commands

* feat: refactor app handle and command functions to use async/await for improved performance

* feat: update async handling in profiles and logging functions for improved error handling and performance

* fix: update TRACE_MINI_SIZE constant to improve task logging threshold

* fix(windows): convert service management functions to async for improved performance

* fix: convert service management functions to async for improved responsiveness

* fix(ubuntu): convert install and reinstall service functions to async for improved performance

* fix(linux): convert uninstall_service function to async for improved performance

* fix: convert uninstall_service call to async for improved performance

* fix: convert file and directory creation calls to async for improved performance

* fix: convert hotkey functions to async for improved responsiveness

* chore: update UPDATELOG.md for v2.4.1 with major improvements and performance optimizations
2025-08-26 01:49:51 +08:00

164 lines
4.8 KiB
Rust

use crate::{enhance::seq::SeqMap, logging, utils::logging::Type};
use anyhow::{anyhow, bail, Context, Result};
use nanoid::nanoid;
use serde::{de::DeserializeOwned, Serialize};
use serde_yaml::Mapping;
use std::{path::PathBuf, str::FromStr};
/// read data from yaml as struct T
pub async fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
if !tokio::fs::try_exists(path).await.unwrap_or(false) {
bail!("file not found \"{}\"", path.display());
}
let yaml_str = tokio::fs::read_to_string(path).await?;
Ok(serde_yaml::from_str::<T>(&yaml_str)?)
}
/// read mapping from yaml
pub async fn read_mapping(path: &PathBuf) -> Result<Mapping> {
if !tokio::fs::try_exists(path).await.unwrap_or(false) {
bail!("file not found \"{}\"", path.display());
}
let yaml_str = tokio::fs::read_to_string(path)
.await
.with_context(|| format!("failed to read the file \"{}\"", path.display()))?;
// YAML语法检查
match serde_yaml::from_str::<serde_yaml::Value>(&yaml_str) {
Ok(mut val) => {
val.apply_merge()
.with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
Ok(val
.as_mapping()
.ok_or(anyhow!(
"failed to transform to yaml mapping \"{}\"",
path.display()
))?
.to_owned())
}
Err(err) => {
let error_msg = format!("YAML syntax error in {}: {}", path.display(), err);
logging!(error, Type::Config, true, "{}", error_msg);
crate::core::handle::Handle::notice_message(
"config_validate::yaml_syntax_error",
&error_msg,
);
bail!("YAML syntax error: {}", err)
}
}
}
/// read mapping from yaml fix #165
pub async fn read_seq_map(path: &PathBuf) -> Result<SeqMap> {
read_yaml(path).await
}
/// save the data to the file
/// can set `prefix` string to add some comments
pub async fn save_yaml<T: Serialize + Sync>(
path: &PathBuf,
data: &T,
prefix: Option<&str>,
) -> Result<()> {
let data_str = serde_yaml::to_string(data)?;
let yaml_str = match prefix {
Some(prefix) => format!("{prefix}\n\n{data_str}"),
None => data_str,
};
let path_str = path.as_os_str().to_string_lossy().to_string();
tokio::fs::write(path, yaml_str.as_bytes())
.await
.with_context(|| format!("failed to save file \"{path_str}\""))
}
const ALPHABET: [char; 62] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z',
];
/// generate the uid
pub fn get_uid(prefix: &str) -> String {
let id = nanoid!(11, &ALPHABET);
format!("{prefix}{id}")
}
/// parse the string
/// xxx=123123; => 123123
pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
target.split(';').map(str::trim).find_map(|s| {
let mut parts = s.splitn(2, '=');
match (parts.next(), parts.next()) {
(Some(k), Some(v)) if k == key => v.parse::<T>().ok(),
_ => None,
}
})
}
/// get the last part of the url, if not found, return empty string
pub fn get_last_part_and_decode(url: &str) -> Option<String> {
let path = url.split('?').next().unwrap_or(""); // Splits URL and takes the path part
let segments: Vec<&str> = path.split('/').collect();
let last_segment = segments.last()?;
Some(
percent_encoding::percent_decode_str(last_segment)
.decode_utf8_lossy()
.to_string(),
)
}
/// open file
pub fn open_file(path: PathBuf) -> Result<()> {
open::that_detached(path.as_os_str())?;
Ok(())
}
#[cfg(target_os = "linux")]
pub fn linux_elevator() -> String {
use std::process::Command;
match Command::new("which").arg("pkexec").output() {
Ok(output) => {
if !output.stdout.is_empty() {
// Convert the output to a string slice
if let Ok(path) = std::str::from_utf8(&output.stdout) {
path.trim().to_string()
} else {
"sudo".to_string()
}
} else {
"sudo".to_string()
}
}
Err(_) => "sudo".to_string(),
}
}
/// return the string literal error
#[macro_export]
macro_rules! ret_err {
($str: expr) => {
return Err($str.into())
};
}
#[macro_export]
macro_rules! t {
($en:expr, $zh:expr, $use_zh:expr) => {
if $use_zh {
$zh
} else {
$en
}
};
}