refactor: streamline profile import logic and enhance error handling (#5051)

This commit is contained in:
Tunglies
2025-10-14 12:39:22 +08:00
committed by GitHub
Unverified
parent db091f5d2e
commit 3d96a575c0
5 changed files with 103 additions and 130 deletions

View File

@@ -47,6 +47,7 @@
- 修复 Windows 深色模式下首次启动客户端标题栏颜色异常
- 修复静默启动不加载完整 WebView 的问题
- 修复 Linux WebKit 网络进程的崩溃
- 修复无法导入订阅
- 修复实际导入成功但显示导入失败的问题
- 修复 macOS 连接界面显示异常

View File

@@ -97,80 +97,48 @@ pub async fn enhance_profiles() -> CmdResult {
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
logging!(info, Type::Cmd, "[导入订阅] 开始导入: {}", url);
let import_result = tokio::time::timeout(Duration::from_secs(60), async {
let item = PrfItem::from_url(&url, None, None, option).await?;
logging!(info, Type::Cmd, "[导入订阅] 下载完成,开始保存配置");
let profiles = Config::profiles().await;
let pre_count = profiles
.latest_ref()
.items
.as_ref()
.map_or(0, |items| items.len());
let result = profiles_append_item_safe(item.clone()).await;
result?;
let post_count = profiles
.latest_ref()
.items
.as_ref()
.map_or(0, |items| items.len());
if post_count <= pre_count {
logging!(error, Type::Cmd, "[导入订阅] 配置未增加,导入可能失败");
return Err(anyhow::anyhow!("配置导入后数量未增加"));
// 直接依赖 PrfItem::from_url 自身的超时/重试逻辑,不再使用 tokio::time::timeout 包裹
let item = match PrfItem::from_url(&url, None, None, option).await {
Ok(it) => {
logging!(info, Type::Cmd, "[导入订阅] 下载完成,开始保存配置");
it
}
logging!(
info,
Type::Cmd,
"[导入订阅] 配置保存成功,数量: {} -> {}",
pre_count,
post_count
);
// 立即发送配置变更通知
if let Some(uid) = &item.uid {
logging!(info, Type::Cmd, "[导入订阅] 发送配置变更通知: {}", uid);
handle::Handle::notify_profile_changed(uid.clone());
Err(e) => {
logging!(error, Type::Cmd, "[导入订阅] 下载失败: {}", e);
return Err(format!("导入订阅失败: {}", e));
}
};
// 异步保存配置文件并发送全局通知
let uid_clone = item.uid.clone();
crate::process::AsyncHandler::spawn(move || async move {
// 使用Send-safe helper函数
if let Err(e) = profiles_save_file_safe().await {
logging!(error, Type::Cmd, "[导入订阅] 保存配置文件失败: {}", e);
} else {
match profiles_append_item_safe(item.clone()).await {
Ok(_) => match profiles_save_file_safe().await {
Ok(_) => {
logging!(info, Type::Cmd, "[导入订阅] 配置文件保存成功");
// 发送全局配置更新通知
if let Some(uid) = uid_clone {
// 延迟发送,确保文件已完全写入
tokio::time::sleep(Duration::from_millis(100)).await;
handle::Handle::notify_profile_changed(uid);
}
}
});
Ok(())
})
.await;
match import_result {
Ok(Ok(())) => {
logging!(info, Type::Cmd, "[导入订阅] 导入完成: {}", url);
Ok(())
}
Ok(Err(e)) => {
logging!(error, Type::Cmd, "[导入订阅] 导入失败: {}", e);
Err(format!("导入订阅失败: {e}"))
}
Err(_) => {
logging!(error, Type::Cmd, "[导入订阅] 导入超时(60秒): {}", url);
Err("导入订阅超时,请检查网络连接".into())
Err(e) => {
logging!(error, Type::Cmd, "[导入订阅] 保存配置文件失败: {}", e);
}
},
Err(e) => {
logging!(error, Type::Cmd, "[导入订阅] 保存配置失败: {}", e);
return Err(format!("导入订阅失败: {}", e));
}
}
// 立即发送配置变更通知
if let Some(uid) = &item.uid {
logging!(info, Type::Cmd, "[导入订阅] 发送配置变更通知: {}", uid);
handle::Handle::notify_profile_changed(uid.clone());
}
// 异步保存配置文件并发送全局通知
let uid_clone = item.uid.clone();
if let Some(uid) = uid_clone {
// 延迟发送,确保文件已完全写入
tokio::time::sleep(Duration::from_millis(100)).await;
handle::Handle::notify_profile_changed(uid);
}
logging!(info, Type::Cmd, "[导入订阅] 导入完成: {}", url);
Ok(())
}
/// 调整profile的顺序

View File

@@ -81,6 +81,30 @@ impl<T: Clone + ToOwned> Draft<Box<T>> {
pub fn discard(&self) -> Option<Box<T>> {
self.inner.write().1.take()
}
/// 异步修改正式数据,闭包直接获得 Box<T> 所有权
pub async fn with_data_modify<F, Fut, R, E>(&self, f: F) -> Result<R, E>
where
T: Send + Sync + 'static,
F: FnOnce(Box<T>) -> Fut + Send,
Fut: std::future::Future<Output = Result<(Box<T>, R), E>> + Send,
E: From<anyhow::Error>,
{
// 克隆正式数据
let local = {
let guard = self.inner.read();
guard.0.clone()
};
// 异步闭包执行,返回修改后的 Box<T> 和业务结果 R
let (new_local, res) = f(local).await?;
// 写回正式数据
let mut guard = self.inner.write();
guard.0 = new_local;
Ok(res)
}
}
#[test]

View File

@@ -1,7 +1,6 @@
use super::{PrfOption, prfitem::PrfItem};
use crate::{
logging_error,
process::AsyncHandler,
utils::{dirs, help, logging::Type},
};
use anyhow::{Context, Result, bail};
@@ -164,7 +163,8 @@ impl IProfiles {
items.push(item)
}
self.save_file().await
// self.save_file().await
Ok(())
}
/// reorder items
@@ -730,86 +730,66 @@ pub async fn profiles_append_item_with_filedata_safe(
item: PrfItem,
file_data: Option<String>,
) -> Result<()> {
AsyncHandler::spawn_blocking(move || {
AsyncHandler::handle().block_on(async {
let item = PrfItem::from(item, file_data).await?;
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))?
let item = PrfItem::from(item, file_data).await?;
profiles_append_item_safe(item).await
}
pub async fn profiles_append_item_safe(item: PrfItem) -> Result<()> {
AsyncHandler::spawn_blocking(move || {
AsyncHandler::handle().block_on(async {
let profiles = Config::profiles().await;
let mut profiles_guard = profiles.data_mut();
profiles_guard.append_item(item).await
Config::profiles()
.await
.with_data_modify(|mut profiles| async move {
profiles.append_item(item).await?;
Ok((profiles, ()))
})
})
.await
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
.await
}
pub async fn profiles_patch_item_safe(index: String, item: PrfItem) -> Result<()> {
AsyncHandler::spawn_blocking(move || {
AsyncHandler::handle().block_on(async {
let profiles = Config::profiles().await;
let mut profiles_guard = profiles.data_mut();
profiles_guard.patch_item(index, item).await
Config::profiles()
.await
.with_data_modify(|mut profiles| async move {
profiles.patch_item(index, item).await?;
Ok((profiles, ()))
})
})
.await
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
.await
}
pub async fn profiles_delete_item_safe(index: String) -> Result<bool> {
AsyncHandler::spawn_blocking(move || {
AsyncHandler::handle().block_on(async {
let profiles = Config::profiles().await;
let mut profiles_guard = profiles.data_mut();
profiles_guard.delete_item(index).await
Config::profiles()
.await
.with_data_modify(|mut profiles| async move {
let deleted = profiles.delete_item(index).await?;
Ok((profiles, deleted))
})
})
.await
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
.await
}
pub async fn profiles_reorder_safe(active_id: String, over_id: String) -> Result<()> {
AsyncHandler::spawn_blocking(move || {
AsyncHandler::handle().block_on(async {
let profiles = Config::profiles().await;
let mut profiles_guard = profiles.data_mut();
profiles_guard.reorder(active_id, over_id).await
Config::profiles()
.await
.with_data_modify(|mut profiles| async move {
profiles.reorder(active_id, over_id).await?;
Ok((profiles, ()))
})
})
.await
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
.await
}
pub async fn profiles_save_file_safe() -> Result<()> {
AsyncHandler::spawn_blocking(move || {
AsyncHandler::handle().block_on(async {
let profiles = Config::profiles().await;
let profiles_guard = profiles.data_mut();
profiles_guard.save_file().await
Config::profiles()
.await
.with_data_modify(|profiles| async move {
profiles.save_file().await?;
Ok((profiles, ()))
})
})
.await
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
.await
}
pub async fn profiles_draft_update_item_safe(index: String, item: PrfItem) -> Result<()> {
AsyncHandler::spawn_blocking(move || {
AsyncHandler::handle().block_on(async {
let profiles = Config::profiles().await;
let mut profiles_guard = profiles.draft_mut();
profiles_guard.update_item(index, item).await
Config::profiles()
.await
.with_data_modify(|mut profiles| async move {
profiles.update_item(index, item).await?;
Ok((profiles, ()))
})
})
.await
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
.await
}

View File

@@ -8,9 +8,9 @@ use tauri::{async_runtime, async_runtime::JoinHandle};
pub struct AsyncHandler;
impl AsyncHandler {
pub fn handle() -> async_runtime::RuntimeHandle {
async_runtime::handle()
}
// pub fn handle() -> async_runtime::RuntimeHandle {
// async_runtime::handle()
// }
#[track_caller]
pub fn spawn<F, Fut>(f: F) -> JoinHandle<()>