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
This commit is contained in:
Tunglies
2025-08-26 01:49:51 +08:00
committed by GitHub
parent 4598c805eb
commit 355a18e5eb
47 changed files with 2127 additions and 1809 deletions

View File

@@ -13,9 +13,16 @@ use std::{
pub struct IClashTemp(pub Mapping);
impl IClashTemp {
pub fn new() -> Self {
pub async fn new() -> Self {
let template = Self::template();
match dirs::clash_path().and_then(|path| help::read_mapping(&path)) {
let clash_path_result = dirs::clash_path();
let map_result = if let Ok(path) = clash_path_result {
help::read_mapping(&path).await
} else {
Err(anyhow::anyhow!("Failed to get clash path"))
};
match map_result {
Ok(mut map) => {
template.0.keys().for_each(|key| {
if !map.contains_key(key) {
@@ -135,12 +142,13 @@ impl IClashTemp {
}
}
pub fn save_config(&self) -> Result<()> {
pub async fn save_config(&self) -> Result<()> {
help::save_yaml(
&dirs::clash_path()?,
&self.0,
Some("# Generated by Clash Verge"),
)
.await
}
pub fn get_mixed_port(&self) -> u16 {
@@ -280,9 +288,10 @@ impl IClashTemp {
Self::guard_server_ctrl(config)
}
pub fn guard_external_controller_with_setting(config: &Mapping) -> String {
pub async fn guard_external_controller_with_setting(config: &Mapping) -> String {
// 检查 enable_external_controller 设置,用于运行时配置生成
let enable_external_controller = Config::verge()
.await
.latest_ref()
.enable_external_controller
.unwrap_or(false);

View File

@@ -1,14 +1,14 @@
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
use crate::{
config::PrfItem,
config::{profiles::profiles_append_item_safe, PrfItem},
core::{handle, CoreManager},
enhance, logging,
process::AsyncHandler,
utils::{dirs, help, logging::Type},
};
use anyhow::{anyhow, Result};
use once_cell::sync::OnceCell;
use std::path::PathBuf;
use tokio::sync::OnceCell;
use tokio::time::{sleep, Duration};
pub const RUNTIME_CONFIG: &str = "clash-verge.yaml";
@@ -22,64 +22,65 @@ pub struct Config {
}
impl Config {
pub fn global() -> &'static Config {
static CONFIG: OnceCell<Config> = OnceCell::new();
CONFIG.get_or_init(|| Config {
clash_config: Draft::from(Box::new(IClashTemp::new())),
verge_config: Draft::from(Box::new(IVerge::new())),
profiles_config: Draft::from(Box::new(IProfiles::new())),
runtime_config: Draft::from(Box::new(IRuntime::new())),
})
pub async fn global() -> &'static Config {
static CONFIG: OnceCell<Config> = OnceCell::const_new();
CONFIG
.get_or_init(|| async {
Config {
clash_config: Draft::from(Box::new(IClashTemp::new().await)),
verge_config: Draft::from(Box::new(IVerge::new().await)),
profiles_config: Draft::from(Box::new(IProfiles::new().await)),
runtime_config: Draft::from(Box::new(IRuntime::new())),
}
})
.await
}
pub fn clash() -> Draft<Box<IClashTemp>> {
Self::global().clash_config.clone()
pub async fn clash() -> Draft<Box<IClashTemp>> {
Self::global().await.clash_config.clone()
}
pub fn verge() -> Draft<Box<IVerge>> {
Self::global().verge_config.clone()
pub async fn verge() -> Draft<Box<IVerge>> {
Self::global().await.verge_config.clone()
}
pub fn profiles() -> Draft<Box<IProfiles>> {
Self::global().profiles_config.clone()
pub async fn profiles() -> Draft<Box<IProfiles>> {
Self::global().await.profiles_config.clone()
}
pub fn runtime() -> Draft<Box<IRuntime>> {
Self::global().runtime_config.clone()
pub async fn runtime() -> Draft<Box<IRuntime>> {
Self::global().await.runtime_config.clone()
}
/// 初始化订阅
pub async fn init_config() -> Result<()> {
if Self::profiles()
.await
.latest_ref()
.get_item(&"Merge".to_string())
.is_err()
{
let merge_item = PrfItem::from_merge(Some("Merge".to_string()))?;
Self::profiles()
.data_mut()
.append_item(merge_item.clone())?;
profiles_append_item_safe(merge_item.clone()).await?;
}
if Self::profiles()
.await
.latest_ref()
.get_item(&"Script".to_string())
.is_err()
{
let script_item = PrfItem::from_script(Some("Script".to_string()))?;
Self::profiles()
.data_mut()
.append_item(script_item.clone())?;
profiles_append_item_safe(script_item.clone()).await?;
}
// 生成运行时配置
if let Err(err) = Self::generate() {
if let Err(err) = Self::generate().await {
logging!(error, Type::Config, true, "生成运行时配置失败: {}", err);
} else {
logging!(info, Type::Config, true, "生成运行时配置成功");
}
// 生成运行时配置文件并验证
let config_result = Self::generate_file(ConfigType::Run);
let config_result = Self::generate_file(ConfigType::Run).await;
let validation_result = if config_result.is_ok() {
// 验证配置文件
@@ -96,7 +97,8 @@ impl Config {
error_msg
);
CoreManager::global()
.use_default_config("config_validate::boot_error", &error_msg)?;
.use_default_config("config_validate::boot_error", &error_msg)
.await?;
Some(("config_validate::boot_error", error_msg))
} else {
logging!(info, Type::Config, true, "配置验证成功");
@@ -106,13 +108,16 @@ impl Config {
Err(err) => {
logging!(warn, Type::Config, true, "验证进程执行失败: {}", err);
CoreManager::global()
.use_default_config("config_validate::process_terminated", "")?;
.use_default_config("config_validate::process_terminated", "")
.await?;
Some(("config_validate::process_terminated", String::new()))
}
}
} else {
logging!(warn, Type::Config, true, "生成配置文件失败,使用默认配置");
CoreManager::global().use_default_config("config_validate::error", "")?;
CoreManager::global()
.use_default_config("config_validate::error", "")
.await?;
Some(("config_validate::error", String::new()))
};
@@ -128,28 +133,30 @@ impl Config {
}
/// 将订阅丢到对应的文件中
pub fn generate_file(typ: ConfigType) -> Result<PathBuf> {
pub async fn generate_file(typ: ConfigType) -> Result<PathBuf> {
let path = match typ {
ConfigType::Run => dirs::app_home_dir()?.join(RUNTIME_CONFIG),
ConfigType::Check => dirs::app_home_dir()?.join(CHECK_CONFIG),
};
let runtime = Config::runtime();
let runtime = runtime.latest_ref();
let runtime = Config::runtime().await;
let config = runtime
.latest_ref()
.config
.as_ref()
.ok_or(anyhow!("failed to get runtime config"))?;
.ok_or(anyhow!("failed to get runtime config"))?
.clone();
drop(runtime); // 显式释放锁
help::save_yaml(&path, &config, Some("# Generated by Clash Verge"))?;
help::save_yaml(&path, &config, Some("# Generated by Clash Verge")).await?;
Ok(path)
}
/// 生成订阅存好
pub fn generate() -> Result<()> {
let (config, exists_keys, logs) = enhance::enhance();
pub async fn generate() -> Result<()> {
let (config, exists_keys, logs) = enhance::enhance().await;
*Config::runtime().draft_mut() = Box::new(IRuntime {
*Config::runtime().await.draft_mut() = Box::new(IRuntime {
config: Some(config),
exists_keys,
chain_logs: logs,

View File

@@ -4,7 +4,7 @@ mod config;
mod draft;
mod encrypt;
mod prfitem;
mod profiles;
pub mod profiles;
mod runtime;
mod verge;

View File

@@ -9,8 +9,6 @@ use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::{fs, time::Duration};
use super::Config;
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct PrfItem {
pub uid: Option<String>,
@@ -163,7 +161,7 @@ impl PrfItem {
"local" => {
let name = item.name.unwrap_or("Local File".into());
let desc = item.desc.unwrap_or("".into());
PrfItem::from_local(name, desc, file_data, item.option)
PrfItem::from_local(name, desc, file_data, item.option).await
}
typ => bail!("invalid profile item type \"{typ}\""),
}
@@ -171,7 +169,7 @@ impl PrfItem {
/// ## Local type
/// create a new item from name/desc
pub fn from_local(
pub async fn from_local(
name: String,
desc: String,
file_data: Option<String>,
@@ -189,37 +187,27 @@ impl PrfItem {
if merge.is_none() {
let merge_item = PrfItem::from_merge(None)?;
Config::profiles()
.data_mut()
.append_item(merge_item.clone())?;
crate::config::profiles::profiles_append_item_safe(merge_item.clone()).await?;
merge = merge_item.uid;
}
if script.is_none() {
let script_item = PrfItem::from_script(None)?;
Config::profiles()
.data_mut()
.append_item(script_item.clone())?;
crate::config::profiles::profiles_append_item_safe(script_item.clone()).await?;
script = script_item.uid;
}
if rules.is_none() {
let rules_item = PrfItem::from_rules()?;
Config::profiles()
.data_mut()
.append_item(rules_item.clone())?;
crate::config::profiles::profiles_append_item_safe(rules_item.clone()).await?;
rules = rules_item.uid;
}
if proxies.is_none() {
let proxies_item = PrfItem::from_proxies()?;
Config::profiles()
.data_mut()
.append_item(proxies_item.clone())?;
crate::config::profiles::profiles_append_item_safe(proxies_item.clone()).await?;
proxies = proxies_item.uid;
}
if groups.is_none() {
let groups_item = PrfItem::from_groups()?;
Config::profiles()
.data_mut()
.append_item(groups_item.clone())?;
crate::config::profiles::profiles_append_item_safe(groups_item.clone()).await?;
groups = groups_item.uid;
}
Ok(PrfItem {
@@ -377,37 +365,27 @@ impl PrfItem {
if merge.is_none() {
let merge_item = PrfItem::from_merge(None)?;
Config::profiles()
.data_mut()
.append_item(merge_item.clone())?;
crate::config::profiles::profiles_append_item_safe(merge_item.clone()).await?;
merge = merge_item.uid;
}
if script.is_none() {
let script_item = PrfItem::from_script(None)?;
Config::profiles()
.data_mut()
.append_item(script_item.clone())?;
crate::config::profiles::profiles_append_item_safe(script_item.clone()).await?;
script = script_item.uid;
}
if rules.is_none() {
let rules_item = PrfItem::from_rules()?;
Config::profiles()
.data_mut()
.append_item(rules_item.clone())?;
crate::config::profiles::profiles_append_item_safe(rules_item.clone()).await?;
rules = rules_item.uid;
}
if proxies.is_none() {
let proxies_item = PrfItem::from_proxies()?;
Config::profiles()
.data_mut()
.append_item(proxies_item.clone())?;
crate::config::profiles::profiles_append_item_safe(proxies_item.clone()).await?;
proxies = proxies_item.uid;
}
if groups.is_none() {
let groups_item = PrfItem::from_groups()?;
Config::profiles()
.data_mut()
.append_item(groups_item.clone())?;
crate::config::profiles::profiles_append_item_safe(groups_item.clone()).await?;
groups = groups_item.uid;
}

View File

@@ -1,9 +1,14 @@
use super::{prfitem::PrfItem, PrfOption};
use crate::utils::{dirs, help};
use crate::{
logging_error,
process::AsyncHandler,
utils::{dirs, help, logging::Type},
};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::{collections::HashSet, fs, io::Write};
use std::collections::HashSet;
use tokio::fs;
/// Define the `profiles.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
@@ -32,22 +37,28 @@ macro_rules! patch {
}
impl IProfiles {
pub fn new() -> Self {
match dirs::profiles_path().and_then(|path| help::read_yaml::<Self>(&path)) {
Ok(mut profiles) => {
if profiles.items.is_none() {
profiles.items = Some(vec![]);
}
// compatible with the old old old version
if let Some(items) = profiles.items.as_mut() {
for item in items.iter_mut() {
if item.uid.is_none() {
item.uid = Some(help::get_uid("d"));
pub async fn new() -> Self {
match dirs::profiles_path() {
Ok(path) => match help::read_yaml::<Self>(&path).await {
Ok(mut profiles) => {
if profiles.items.is_none() {
profiles.items = Some(vec![]);
}
// compatible with the old old old version
if let Some(items) = profiles.items.as_mut() {
for item in items.iter_mut() {
if item.uid.is_none() {
item.uid = Some(help::get_uid("d"));
}
}
}
profiles
}
profiles
}
Err(err) => {
log::error!(target: "app", "{err}");
Self::template()
}
},
Err(err) => {
log::error!(target: "app", "{err}");
Self::template()
@@ -62,12 +73,13 @@ impl IProfiles {
}
}
pub fn save_file(&self) -> Result<()> {
pub async fn save_file(&self) -> Result<()> {
help::save_yaml(
&dirs::profiles_path()?,
self,
Some("# Profiles Config for Clash Verge"),
)
.await
}
/// 只修改currentvalid和chain
@@ -115,7 +127,7 @@ impl IProfiles {
/// append new item
/// if the file_data is some
/// then should save the data to file
pub fn append_item(&mut self, mut item: PrfItem) -> Result<()> {
pub async fn append_item(&mut self, mut item: PrfItem) -> Result<()> {
if item.uid.is_none() {
bail!("the uid should not be null");
}
@@ -133,9 +145,8 @@ impl IProfiles {
})?;
let path = dirs::app_profiles_dir()?.join(&file);
fs::File::create(path)
.with_context(|| format!("failed to create file \"{file}\""))?
.write(file_data.as_bytes())
fs::write(&path, file_data.as_bytes())
.await
.with_context(|| format!("failed to write to file \"{file}\""))?;
}
@@ -153,11 +164,11 @@ impl IProfiles {
items.push(item)
}
self.save_file()
self.save_file().await
}
/// reorder items
pub fn reorder(&mut self, active_id: String, over_id: String) -> Result<()> {
pub async fn reorder(&mut self, active_id: String, over_id: String) -> Result<()> {
let mut items = self.items.take().unwrap_or_default();
let mut old_index = None;
let mut new_index = None;
@@ -178,11 +189,11 @@ impl IProfiles {
let item = items.remove(old_idx);
items.insert(new_idx, item);
self.items = Some(items);
self.save_file()
self.save_file().await
}
/// update the item value
pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
pub async fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
let mut items = self.items.take().unwrap_or_default();
for each in items.iter_mut() {
@@ -198,7 +209,7 @@ impl IProfiles {
patch!(each, item, option);
self.items = Some(items);
return self.save_file();
return self.save_file().await;
}
}
@@ -208,7 +219,7 @@ impl IProfiles {
/// be used to update the remote item
/// only patch `updated` `extra` `file_data`
pub fn update_item(&mut self, uid: String, mut item: PrfItem) -> Result<()> {
pub async fn update_item(&mut self, uid: String, mut item: PrfItem) -> Result<()> {
if self.items.is_none() {
self.items = Some(vec![]);
}
@@ -237,9 +248,8 @@ impl IProfiles {
let path = dirs::app_profiles_dir()?.join(&file);
fs::File::create(path)
.with_context(|| format!("failed to create file \"{file}\""))?
.write(file_data.as_bytes())
fs::write(&path, file_data.as_bytes())
.await
.with_context(|| format!("failed to write to file \"{file}\""))?;
}
@@ -248,12 +258,12 @@ impl IProfiles {
}
}
self.save_file()
self.save_file().await
}
/// delete item
/// if delete the current then return true
pub fn delete_item(&mut self, uid: String) -> Result<bool> {
pub async fn delete_item(&mut self, uid: String) -> Result<bool> {
let current = self.current.as_ref().unwrap_or(&uid);
let current = current.clone();
let item = self.get_item(&uid)?;
@@ -279,10 +289,19 @@ impl IProfiles {
}
if let Some(index) = index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let _ = dirs::app_profiles_dir().map(async move |path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
let result = fs::remove_file(path.clone()).await;
if let Err(err) = result {
logging_error!(
Type::Config,
false,
"[配置文件删除] 删除文件 {} 失败: {}",
path.display(),
err
);
}
}
});
}
@@ -296,10 +315,19 @@ impl IProfiles {
}
if let Some(index) = merge_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let _ = dirs::app_profiles_dir().map(async move |path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
let result = fs::remove_file(path.clone()).await;
if let Err(err) = result {
logging_error!(
Type::Config,
false,
"[配置文件删除] 删除文件 {} 失败: {}",
path.display(),
err
);
}
}
});
}
@@ -313,10 +341,19 @@ impl IProfiles {
}
if let Some(index) = script_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let _ = dirs::app_profiles_dir().map(async move |path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
let result = fs::remove_file(path.clone()).await;
if let Err(err) = result {
logging_error!(
Type::Config,
false,
"[配置文件删除] 删除文件 {} 失败: {}",
path.display(),
err
);
}
}
});
}
@@ -330,10 +367,19 @@ impl IProfiles {
}
if let Some(index) = rules_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let _ = dirs::app_profiles_dir().map(async move |path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
let result = fs::remove_file(path.clone()).await;
if let Err(err) = result {
logging_error!(
Type::Config,
false,
"[配置文件删除] 删除文件 {} 失败: {}",
path.display(),
err
);
}
}
});
}
@@ -347,10 +393,19 @@ impl IProfiles {
}
if let Some(index) = proxies_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let _ = dirs::app_profiles_dir().map(async move |path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
let result = fs::remove_file(path.clone()).await;
if let Err(err) = result {
logging_error!(
Type::Config,
false,
"[配置文件删除] 删除文件 {} 失败: {}",
path.display(),
err
);
}
}
});
}
@@ -364,10 +419,19 @@ impl IProfiles {
}
if let Some(index) = groups_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let _ = dirs::app_profiles_dir().map(async move |path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
let result = fs::remove_file(path.clone()).await;
if let Err(err) = result {
logging_error!(
Type::Config,
false,
"[配置文件删除] 删除文件 {} 失败: {}",
path.display(),
err
);
}
}
});
}
@@ -386,12 +450,12 @@ impl IProfiles {
}
self.items = Some(items);
self.save_file()?;
self.save_file().await?;
Ok(current == uid)
}
/// 获取current指向的订阅内容
pub fn current_mapping(&self) -> Result<Mapping> {
pub async fn current_mapping(&self) -> Result<Mapping> {
match (self.current.as_ref(), self.items.as_ref()) {
(Some(current), Some(items)) => {
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
@@ -399,7 +463,7 @@ impl IProfiles {
Some(file) => dirs::app_profiles_dir()?.join(file),
None => bail!("failed to get the file field"),
};
return help::read_mapping(&file_path);
return help::read_mapping(&file_path).await;
}
bail!("failed to find the current profile \"uid:{current}\"");
}
@@ -691,3 +755,78 @@ impl IProfiles {
}
}
}
// 特殊的Send-safe helper函数完全避免跨await持有guard
use crate::config::Config;
pub async fn profiles_append_item_safe(item: PrfItem) -> Result<()> {
AsyncHandler::spawn_blocking(move || {
tokio::runtime::Handle::current().block_on(async {
let profiles = Config::profiles().await;
let mut profiles_guard = profiles.data_mut();
profiles_guard.append_item(item).await
})
})
.await
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
}
pub async fn profiles_patch_item_safe(index: String, item: PrfItem) -> Result<()> {
AsyncHandler::spawn_blocking(move || {
tokio::runtime::Handle::current().block_on(async {
let profiles = Config::profiles().await;
let mut profiles_guard = profiles.data_mut();
profiles_guard.patch_item(index, item).await
})
})
.await
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
}
pub async fn profiles_delete_item_safe(index: String) -> Result<bool> {
AsyncHandler::spawn_blocking(move || {
tokio::runtime::Handle::current().block_on(async {
let profiles = Config::profiles().await;
let mut profiles_guard = profiles.data_mut();
profiles_guard.delete_item(index).await
})
})
.await
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
}
pub async fn profiles_reorder_safe(active_id: String, over_id: String) -> Result<()> {
AsyncHandler::spawn_blocking(move || {
tokio::runtime::Handle::current().block_on(async {
let profiles = Config::profiles().await;
let mut profiles_guard = profiles.data_mut();
profiles_guard.reorder(active_id, over_id).await
})
})
.await
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
}
pub async fn profiles_save_file_safe() -> Result<()> {
AsyncHandler::spawn_blocking(move || {
tokio::runtime::Handle::current().block_on(async {
let profiles = Config::profiles().await;
let profiles_guard = profiles.data_mut();
profiles_guard.save_file().await
})
})
.await
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
}
pub async fn profiles_draft_update_item_safe(index: String, item: PrfItem) -> Result<()> {
AsyncHandler::spawn_blocking(move || {
tokio::runtime::Handle::current().block_on(async {
let profiles = Config::profiles().await;
let mut profiles_guard = profiles.draft_mut();
profiles_guard.update_item(index, item).await
})
})
.await
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
}

View File

@@ -237,9 +237,9 @@ impl IVerge {
pub const VALID_CLASH_CORES: &'static [&'static str] = &["verge-mihomo", "verge-mihomo-alpha"];
/// 验证并修正配置文件中的clash_core值
pub fn validate_and_fix_config() -> Result<()> {
pub async fn validate_and_fix_config() -> Result<()> {
let config_path = dirs::verge_path()?;
let mut config = match help::read_yaml::<IVerge>(&config_path) {
let mut config = match help::read_yaml::<IVerge>(&config_path).await {
Ok(config) => config,
Err(_) => Self::template(),
};
@@ -273,7 +273,7 @@ impl IVerge {
// 修正后保存配置
if needs_fix {
logging!(info, Type::Config, true, "正在保存修正后的配置文件...");
help::save_yaml(&config_path, &config, Some("# Clash Verge Config"))?;
help::save_yaml(&config_path, &config, Some("# Clash Verge Config")).await?;
logging!(
info,
Type::Config,
@@ -281,7 +281,7 @@ impl IVerge {
"配置文件修正完成,需要重新加载配置"
);
Self::reload_config_after_fix(config)?;
Self::reload_config_after_fix(config).await?;
} else {
logging!(
info,
@@ -296,10 +296,10 @@ impl IVerge {
}
/// 配置修正后重新加载配置
fn reload_config_after_fix(updated_config: IVerge) -> Result<()> {
async fn reload_config_after_fix(updated_config: IVerge) -> Result<()> {
use crate::config::Config;
let config_draft = Config::verge();
let config_draft = Config::verge().await;
*config_draft.draft_mut() = Box::new(updated_config.clone());
config_draft.apply();
@@ -335,9 +335,15 @@ impl IVerge {
}
}
pub fn new() -> Self {
match dirs::verge_path().and_then(|path| help::read_yaml::<IVerge>(&path)) {
Ok(config) => config,
pub async fn new() -> Self {
match dirs::verge_path() {
Ok(path) => match help::read_yaml::<IVerge>(&path).await {
Ok(config) => config,
Err(err) => {
log::error!(target: "app", "{err}");
Self::template()
}
},
Err(err) => {
log::error!(target: "app", "{err}");
Self::template()
@@ -408,8 +414,8 @@ impl IVerge {
}
/// Save IVerge App Config
pub fn save_file(&self) -> Result<()> {
help::save_yaml(&dirs::verge_path()?, &self, Some("# Clash Verge Config"))
pub async fn save_file(&self) -> Result<()> {
help::save_yaml(&dirs::verge_path()?, &self, Some("# Clash Verge Config")).await
}
/// patch verge config