mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-08 02:52:07 +08:00
chore: update i18n
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use super::CmdResult;
|
||||
use super::{CmdResult, WithErrorCode as _, coded_error};
|
||||
use crate::feat;
|
||||
use crate::utils::{dirs, yaml_emitter};
|
||||
use crate::{
|
||||
@@ -32,7 +32,9 @@ pub async fn get_clash_info() -> CmdResult<ClashInfo> {
|
||||
/// 修改Clash配置
|
||||
#[tauri::command]
|
||||
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
|
||||
feat::patch_clash(&payload).await.stringify_err()
|
||||
feat::patch_clash(&payload)
|
||||
.await
|
||||
.with_error_code("CLASH_CONFIG_UPDATE_FAILED")
|
||||
}
|
||||
|
||||
/// 修改Clash模式
|
||||
@@ -41,7 +43,9 @@ pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
|
||||
/// 并提示用户(此前命令始终返回 `Ok(())`,吞掉了后端错误)。
|
||||
#[tauri::command]
|
||||
pub async fn patch_clash_mode(payload: String) -> CmdResult {
|
||||
feat::change_clash_mode(payload).await
|
||||
feat::change_clash_mode(payload)
|
||||
.await
|
||||
.with_error_code("CLASH_MODE_UPDATE_FAILED")
|
||||
}
|
||||
|
||||
/// 获取当前 Clash 模式(容错读取)
|
||||
@@ -74,7 +78,7 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>>
|
||||
let error_msg: String = format!("Core changed but failed to restart: {err}").into();
|
||||
handle::Handle::notice_message("config_core::change_error", error_msg.clone());
|
||||
logging!(error, Type::Core, "{error_msg}");
|
||||
Ok(Some(error_msg))
|
||||
Ok(Some(coded_error("CORE_CHANGE_FAILED", error_msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +86,7 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>>
|
||||
let error_msg: String = err;
|
||||
logging!(error, Type::Core, "failed to change core: {error_msg}");
|
||||
handle::Handle::notice_message("config_core::change_error", error_msg.clone());
|
||||
Ok(Some(error_msg))
|
||||
Ok(Some(coded_error("CORE_CHANGE_FAILED", error_msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +94,10 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>>
|
||||
/// 启动核心
|
||||
#[tauri::command]
|
||||
pub async fn start_core() -> CmdResult {
|
||||
let result = CoreManager::global().start_core().await.stringify_err();
|
||||
let result = CoreManager::global()
|
||||
.start_core()
|
||||
.await
|
||||
.with_error_code("CORE_START_FAILED");
|
||||
if result.is_ok() {
|
||||
handle::Handle::refresh_clash();
|
||||
}
|
||||
@@ -101,7 +108,10 @@ pub async fn start_core() -> CmdResult {
|
||||
#[tauri::command]
|
||||
pub async fn stop_core() -> CmdResult {
|
||||
logging_error!(Type::Core, Config::profiles().await.data_arc().save_file().await);
|
||||
let result = CoreManager::global().stop_core().await.stringify_err();
|
||||
let result = CoreManager::global()
|
||||
.stop_core()
|
||||
.await
|
||||
.with_error_code("CORE_STOP_FAILED");
|
||||
if result.is_ok() {
|
||||
handle::Handle::refresh_clash();
|
||||
}
|
||||
@@ -112,7 +122,10 @@ pub async fn stop_core() -> CmdResult {
|
||||
#[tauri::command]
|
||||
pub async fn restart_core() -> CmdResult {
|
||||
logging_error!(Type::Core, Config::profiles().await.data_arc().save_file().await);
|
||||
let result = CoreManager::global().restart_core().await.stringify_err();
|
||||
let result = CoreManager::global()
|
||||
.restart_core()
|
||||
.await
|
||||
.with_error_code("CORE_RESTART_FAILED");
|
||||
if result.is_ok() {
|
||||
handle::Handle::refresh_clash();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@ use smartstring::alias::String;
|
||||
|
||||
pub type CmdResult<T = ()> = Result<T, String>;
|
||||
|
||||
const USER_ERROR_PREFIX: &str = "CVR_ERROR:";
|
||||
|
||||
pub fn coded_error(code: &str, detail: impl std::fmt::Display) -> String {
|
||||
format!("{USER_ERROR_PREFIX}{code}\n{detail}").into()
|
||||
}
|
||||
|
||||
// Command modules
|
||||
pub mod app;
|
||||
pub mod backup;
|
||||
@@ -46,6 +52,10 @@ pub trait StringifyErr<T> {
|
||||
F: Fn(&str);
|
||||
}
|
||||
|
||||
pub trait WithErrorCode<T> {
|
||||
fn with_error_code(self, code: &str) -> CmdResult<T>;
|
||||
}
|
||||
|
||||
impl<T, E: std::fmt::Display> StringifyErr<T> for Result<T, E> {
|
||||
fn stringify_err(self) -> CmdResult<T> {
|
||||
self.map_err(|e| e.to_string().into())
|
||||
@@ -62,3 +72,22 @@ impl<T, E: std::fmt::Display> StringifyErr<T> for Result<T, E> {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E: std::fmt::Display> WithErrorCode<T> for Result<T, E> {
|
||||
fn with_error_code(self, code: &str) -> CmdResult<T> {
|
||||
self.map_err(|error| coded_error(code, error))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::coded_error;
|
||||
|
||||
#[test]
|
||||
fn coded_error_preserves_stable_code_and_diagnostic_detail() {
|
||||
assert_eq!(
|
||||
coded_error("CORE_RESTART_FAILED", "connection refused"),
|
||||
"CVR_ERROR:CORE_RESTART_FAILED\nconnection refused"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::CmdResult;
|
||||
use super::StringifyErr as _;
|
||||
use super::{CmdResult, StringifyErr as _, WithErrorCode as _, coded_error};
|
||||
use crate::cmd::validate::{ValidationNoticeTarget, handle_validation_notice};
|
||||
use crate::config::profiles;
|
||||
use crate::utils::window_manager::WindowManager;
|
||||
@@ -60,7 +59,7 @@ pub async fn enhance_profiles() -> CmdResult<ValidationOutcome> {
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(error, Type::Cmd, "{}", e);
|
||||
Err(e.to_string().into())
|
||||
Err(coded_error("PROFILE_ENHANCE_FAILED", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,18 +77,18 @@ pub async fn import_profile(url: std::string::String, option: Option<PrfOption>)
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(error, Type::Cmd, "[导入订阅] 下载失败: {}", e);
|
||||
return Err(profile_import_error(&e).into());
|
||||
return Err(coded_error("PROFILE_IMPORT_FAILED", profile_import_error(&e)));
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = profiles_append_item_safe(item).await {
|
||||
logging!(error, Type::Cmd, "[导入订阅] 保存配置失败: {}", e);
|
||||
return Err(format!("导入订阅失败: {}", e).into());
|
||||
return Err(coded_error("PROFILE_IMPORT_FAILED", e));
|
||||
}
|
||||
|
||||
if let Err(e) = profiles_save_file_safe().await {
|
||||
logging!(error, Type::Cmd, "[导入订阅] 保存配置文件失败: {}", e);
|
||||
return Err(format!("导入订阅失败: {}", e).into());
|
||||
return Err(coded_error("PROFILE_IMPORT_FAILED", e));
|
||||
}
|
||||
logging!(info, Type::Cmd, "[导入订阅] 配置文件保存成功");
|
||||
logging_error!(Type::Timer, Timer::global().refresh().await);
|
||||
@@ -113,7 +112,7 @@ pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::Cmd, "重新排序配置文件失败: {}", err);
|
||||
Err(format!("重新排序配置文件失败: {}", err).into())
|
||||
Err(coded_error("PROFILE_REORDER_FAILED", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,7 +123,9 @@ pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
|
||||
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
|
||||
match profiles_append_item_with_filedata_safe(&item, file_data).await {
|
||||
Ok(_) => {
|
||||
profiles_save_file_safe().await.stringify_err()?;
|
||||
profiles_save_file_safe()
|
||||
.await
|
||||
.with_error_code("PROFILE_CREATE_FAILED")?;
|
||||
logging_error!(Type::Timer, Timer::global().refresh().await);
|
||||
// 发送配置变更通知
|
||||
if let Some(uid) = &item.uid {
|
||||
@@ -133,10 +134,7 @@ pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResu
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => match err.to_string().as_str() {
|
||||
"the file already exists" => Err("the file already exists".into()),
|
||||
_ => Err(format!("add profile error: {err}").into()),
|
||||
},
|
||||
Err(err) => Err(coded_error("PROFILE_CREATE_FAILED", err)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +145,7 @@ pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResu
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
logging!(error, Type::Cmd, "{}", e);
|
||||
Err(e.to_string().into())
|
||||
Err(coded_error("PROFILE_UPDATE_FAILED", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,8 +154,12 @@ pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResu
|
||||
#[tauri::command]
|
||||
pub async fn delete_profile(index: String) -> CmdResult {
|
||||
// 使用Send-safe helper函数
|
||||
let should_update = profiles_delete_item_safe(&index).await.stringify_err()?;
|
||||
profiles_save_file_safe().await.stringify_err()?;
|
||||
let should_update = profiles_delete_item_safe(&index)
|
||||
.await
|
||||
.with_error_code("PROFILE_DELETE_FAILED")?;
|
||||
profiles_save_file_safe()
|
||||
.await
|
||||
.with_error_code("PROFILE_DELETE_FAILED")?;
|
||||
if let Err(e) = Tray::global().update_tooltip().await {
|
||||
logging!(warn, Type::Cmd, "Warning: 异步更新托盘提示失败: {e}");
|
||||
}
|
||||
@@ -176,15 +178,18 @@ pub async fn delete_profile(index: String) -> CmdResult {
|
||||
Ok(outcome) => {
|
||||
logging!(warn, Type::Cmd, "删除订阅后更新配置失败: {}", outcome);
|
||||
handle_validation_notice(&outcome, ValidationNoticeTarget::Runtime, "运行时配置");
|
||||
return Err(outcome.to_string().into());
|
||||
return Err(coded_error("PROFILE_DELETE_FAILED", outcome));
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(error, Type::Cmd, "{}", e);
|
||||
return Err(e.to_string().into());
|
||||
return Err(coded_error("PROFILE_DELETE_FAILED", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Timer::global().refresh().await.stringify_err()?;
|
||||
Timer::global()
|
||||
.refresh()
|
||||
.await
|
||||
.with_error_code("PROFILE_DELETE_FAILED")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -322,7 +327,9 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<ValidationO
|
||||
|
||||
Config::profiles().await.edit_draft(|d| d.patch_config(&profiles));
|
||||
|
||||
perform_config_update(target_profile, previous_profile.as_ref()).await
|
||||
perform_config_update(target_profile, previous_profile.as_ref())
|
||||
.await
|
||||
.map_err(|error| coded_error("PROFILE_SWITCH_FAILED", error))
|
||||
}
|
||||
|
||||
/// 根据profile name修改profiles
|
||||
@@ -354,7 +361,9 @@ pub async fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
||||
false
|
||||
};
|
||||
|
||||
profiles_patch_item_safe(&index, &profile).await.stringify_err()?;
|
||||
profiles_patch_item_safe(&index, &profile)
|
||||
.await
|
||||
.with_error_code("PROFILE_UPDATE_FAILED")?;
|
||||
|
||||
// 如果更新间隔或允许自动更新变更,异步刷新定时器
|
||||
if should_refresh_timer {
|
||||
@@ -379,17 +388,22 @@ pub async fn view_profile(index: String) -> CmdResult {
|
||||
let profiles_ref = profiles.latest_arc();
|
||||
let file = profiles_ref
|
||||
.get_item(&index)
|
||||
.stringify_err()?
|
||||
.with_error_code("PROFILE_OPEN_FAILED")?
|
||||
.file
|
||||
.as_ref()
|
||||
.ok_or("the file field is null")?;
|
||||
.ok_or_else(|| coded_error("PROFILE_OPEN_FAILED", "the file field is null"))?;
|
||||
|
||||
let path = dirs::app_profiles_dir().stringify_err()?.join(file.as_str());
|
||||
let path = dirs::app_profiles_dir()
|
||||
.with_error_code("PROFILE_OPEN_FAILED")?
|
||||
.join(file.as_str());
|
||||
if !path.exists() {
|
||||
return CmdResult::Err(format!("file not found \"{}\"", path.display()).into());
|
||||
return CmdResult::Err(coded_error(
|
||||
"PROFILE_OPEN_FAILED",
|
||||
format!("file not found \"{}\"", path.display()),
|
||||
));
|
||||
}
|
||||
|
||||
help::open_file(path).stringify_err()
|
||||
help::open_file(path).with_error_code("PROFILE_OPEN_FAILED")
|
||||
}
|
||||
|
||||
/// 读取配置文件内容
|
||||
@@ -399,23 +413,32 @@ pub async fn read_profile_file(index: String) -> CmdResult<String> {
|
||||
let profiles = Config::profiles().await;
|
||||
let profiles_ref = profiles.latest_arc();
|
||||
PrfItem {
|
||||
file: profiles_ref.get_item(&index).stringify_err()?.file.to_owned(),
|
||||
file: profiles_ref
|
||||
.get_item(&index)
|
||||
.with_error_code("PROFILE_READ_FAILED")?
|
||||
.file
|
||||
.to_owned(),
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(file) = item.file.as_ref() {
|
||||
let path = dirs::app_profiles_dir().stringify_err()?.join(file.as_str());
|
||||
let path = dirs::app_profiles_dir()
|
||||
.with_error_code("PROFILE_READ_FAILED")?
|
||||
.join(file.as_str());
|
||||
match tokio::fs::try_exists(&path).await {
|
||||
Ok(true) => {}
|
||||
Ok(false) => return Ok(String::new()),
|
||||
Err(err) => {
|
||||
return Err(format!("failed to check profile file \"{}\": {err}", path.display()).into());
|
||||
return Err(coded_error(
|
||||
"PROFILE_READ_FAILED",
|
||||
format!("failed to check profile file \"{}\": {err}", path.display()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let data = item.read_file().await.stringify_err()?;
|
||||
let data = item.read_file().await.with_error_code("PROFILE_READ_FAILED")?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
use super::{CmdResult, StringifyErr as _};
|
||||
use super::{CmdResult, WithErrorCode as _};
|
||||
use crate::core::{
|
||||
CoreManager,
|
||||
service::{self, SERVICE_MANAGER, ServiceStatus},
|
||||
};
|
||||
|
||||
async fn execute_service_operation_sync(status: ServiceStatus, op_type: &str) -> CmdResult {
|
||||
async fn execute_service_operation_sync(status: ServiceStatus, error_code: &str) -> CmdResult {
|
||||
let manager = CoreManager::global();
|
||||
let _lifecycle = manager.lifecycle_lock.lock().await;
|
||||
if matches!(
|
||||
&status,
|
||||
ServiceStatus::ReinstallRequired | ServiceStatus::ForceReinstallRequired
|
||||
) {
|
||||
manager
|
||||
.controlled_stop_core_inner()
|
||||
.await
|
||||
.map_err(|error| format!("{op_type} Service failed: {error:#}"))?;
|
||||
manager.controlled_stop_core_inner().await.with_error_code(error_code)?;
|
||||
}
|
||||
SERVICE_MANAGER
|
||||
.handle_service_status(status)
|
||||
.await
|
||||
.map_err(|e| format!("{op_type} Service failed: {e}").into())
|
||||
.with_error_code(error_code)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_service() -> CmdResult {
|
||||
execute_service_operation_sync(ServiceStatus::InstallRequired, "Install").await
|
||||
execute_service_operation_sync(ServiceStatus::InstallRequired, "SERVICE_INSTALL_FAILED").await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -32,17 +29,17 @@ pub async fn uninstall_service() -> CmdResult {
|
||||
CoreManager::global()
|
||||
.uninstall_service_and_start_sidecar()
|
||||
.await
|
||||
.map_err(|error| format!("Uninstall Service failed: {error:#}").into())
|
||||
.with_error_code("SERVICE_UNINSTALL_FAILED")
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn reinstall_service() -> CmdResult {
|
||||
execute_service_operation_sync(ServiceStatus::ReinstallRequired, "Reinstall").await
|
||||
execute_service_operation_sync(ServiceStatus::ReinstallRequired, "SERVICE_REINSTALL_FAILED").await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn repair_service() -> CmdResult {
|
||||
execute_service_operation_sync(ServiceStatus::ForceReinstallRequired, "Repair").await
|
||||
execute_service_operation_sync(ServiceStatus::ForceReinstallRequired, "SERVICE_REPAIR_FAILED").await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -60,5 +57,5 @@ pub async fn continue_with_sidecar() -> CmdResult {
|
||||
crate::core::CoreManager::global()
|
||||
.continue_with_sidecar()
|
||||
.await
|
||||
.stringify_err()
|
||||
.with_error_code("SERVICE_SIDECAR_FAILED")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user