mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-03 05:22:09 +08:00
refactor: api cleanup (#7812)
* refactor(config): unify YAML persistence * refactor(config): remove redundant config APIs * refactor(config): remove unused config APIs * refactor(config): narrow profile APIs * fix(config): preserve profiles on invalid reorder * refactor(startup): narrow resolve helpers * refactor(config): unify mixed port defaults * refactor(network): simplify response APIs * refactor(config): make profile builders infallible * refactor(config): make profile activation infallible * refactor(api): simplify internal runtime APIs * refactor: consolidate internal config and window APIs
This commit is contained in:
@@ -3,7 +3,10 @@ use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::{fs, path::PathBuf};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tauri::Manager as _;
|
||||
|
||||
#[cfg(not(feature = "verge-dev"))]
|
||||
@@ -198,12 +201,9 @@ pub fn clash_latest_log() -> Result<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path_to_str(path: &PathBuf) -> Result<&str> {
|
||||
let path_str = path
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to get path from {:?}", path))?;
|
||||
Ok(path_str)
|
||||
pub fn path_to_str(path: &Path) -> Result<&str> {
|
||||
path.to_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to get path from {:?}", path))
|
||||
}
|
||||
|
||||
pub fn get_encryption_key() -> Result<Vec<u8>> {
|
||||
|
||||
+53
-20
@@ -1,15 +1,17 @@
|
||||
use crate::{config::with_encryption, enhance::seq::SeqMap};
|
||||
use crate::config::with_encryption;
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use nanoid::nanoid;
|
||||
use scopeguard::{ScopeGuard, guard};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use serde_yaml_ng::{Mapping, Value};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
use tokio::io::AsyncWriteExt as _;
|
||||
|
||||
pub async fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
|
||||
pub async fn read_yaml<T: DeserializeOwned>(path: &Path) -> Result<T> {
|
||||
if !tokio::fs::try_exists(path).await.unwrap_or(false) {
|
||||
bail!("file not found \"{}\"", path.display());
|
||||
}
|
||||
@@ -19,7 +21,7 @@ pub async fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
|
||||
Ok(with_encryption(|| async { serde_yaml_ng::from_str::<T>(&yaml_str) }).await?)
|
||||
}
|
||||
|
||||
pub async fn read_mapping(path: &PathBuf) -> Result<Mapping> {
|
||||
pub async fn read_mapping(path: &Path) -> Result<Mapping> {
|
||||
if !tokio::fs::try_exists(path).await.unwrap_or(false) {
|
||||
bail!("file not found \"{}\"", path.display());
|
||||
}
|
||||
@@ -49,11 +51,7 @@ pub async fn read_mapping(path: &PathBuf) -> Result<Mapping> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_seq_map(path: &PathBuf) -> Result<SeqMap> {
|
||||
read_yaml(path).await
|
||||
}
|
||||
|
||||
pub async fn save_yaml<T: Serialize + Sync>(path: &PathBuf, data: &T, prefix: Option<&str>) -> Result<()> {
|
||||
pub async fn save_yaml<T: Serialize + Sync>(path: &Path, data: &T, prefix: Option<&str>) -> Result<()> {
|
||||
let data_str = with_encryption(|| async { serde_yaml_ng::to_string(data) }).await?;
|
||||
|
||||
let yaml_str = match prefix {
|
||||
@@ -61,20 +59,55 @@ pub async fn save_yaml<T: Serialize + Sync>(path: &PathBuf, data: &T, prefix: Op
|
||||
None => data_str,
|
||||
};
|
||||
|
||||
tokio::fs::write(path, yaml_str.as_bytes())
|
||||
.await
|
||||
.with_context(|| format!("failed to save file \"{}\"", path.display()))?;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
Ok(())
|
||||
}
|
||||
let (temporary, file) = loop {
|
||||
let temporary = path.with_extension(format!("tmp-{}-{}", std::process::id(), nanoid!()));
|
||||
match std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&temporary)
|
||||
{
|
||||
Ok(file) => break (temporary, file),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {}
|
||||
Err(error) => {
|
||||
return Err(error).with_context(|| {
|
||||
format!(
|
||||
"failed to create temporary file \"{}\" for \"{}\"",
|
||||
temporary.display(),
|
||||
path.display()
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub async fn save_yaml_atomic<T: Serialize + Sync>(path: &Path, data: &T, prefix: Option<&str>) -> Result<()> {
|
||||
let temporary = path.with_extension("delete.tmp");
|
||||
save_yaml(&temporary, data, prefix).await?;
|
||||
if let Err(error) = super::server::replace_file_atomic(&temporary, path) {
|
||||
let _ = tokio::fs::remove_file(&temporary).await;
|
||||
return Err(error).with_context(|| format!("failed to replace file \"{}\"", path.display()));
|
||||
let temporary = guard(temporary, |path| {
|
||||
let _ = std::fs::remove_file(path);
|
||||
});
|
||||
let mut file = tokio::fs::File::from_std(file);
|
||||
file.write_all(yaml_str.as_bytes())
|
||||
.await
|
||||
.with_context(|| format!("failed to write temporary file for \"{}\"", path.display()))?;
|
||||
file.flush()
|
||||
.await
|
||||
.with_context(|| format!("failed to flush temporary file for \"{}\"", path.display()))?;
|
||||
drop(file);
|
||||
|
||||
match tokio::fs::symlink_metadata(path).await {
|
||||
Ok(metadata) if metadata.file_type().is_file() => {
|
||||
tokio::fs::set_permissions(temporary.as_path(), metadata.permissions())
|
||||
.await
|
||||
.with_context(|| format!("failed to preserve permissions for \"{}\"", path.display()))?;
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
|
||||
Err(error) => {
|
||||
return Err(error).with_context(|| format!("failed to inspect file \"{}\"", path.display()));
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::rename(temporary.as_path(), path)
|
||||
.with_context(|| format!("failed to replace file \"{}\"", path.display()))?;
|
||||
let _ = ScopeGuard::into_inner(temporary);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ pub mod init;
|
||||
pub mod linux;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos_launch_guard;
|
||||
pub mod network;
|
||||
pub(crate) mod network;
|
||||
pub mod notification;
|
||||
pub mod port;
|
||||
pub mod resolve;
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct HttpResponse {
|
||||
}
|
||||
|
||||
impl HttpResponse {
|
||||
pub const fn new(status: StatusCode, headers: HeaderMap, body: String) -> Self {
|
||||
const fn new(status: StatusCode, headers: HeaderMap, body: String) -> Self {
|
||||
Self { status, headers, body }
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ impl HttpResponse {
|
||||
&self.headers
|
||||
}
|
||||
|
||||
pub fn text_with_charset(&self) -> Result<&str> {
|
||||
Ok(&self.body)
|
||||
pub fn text(&self) -> &str {
|
||||
&self.body
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,12 +50,6 @@ enum TlsRootMode {
|
||||
|
||||
pub struct NetworkManager;
|
||||
|
||||
impl Default for NetworkManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkManager {
|
||||
pub const fn new() -> Self {
|
||||
Self
|
||||
@@ -161,23 +155,6 @@ impl NetworkManager {
|
||||
detail.contains("protocolversion") || detail.contains("protocol version")
|
||||
}
|
||||
|
||||
pub async fn create_request(
|
||||
&self,
|
||||
proxy_type: ProxyType,
|
||||
timeout_secs: Option<u64>,
|
||||
user_agent: Option<String>,
|
||||
accept_invalid_certs: bool,
|
||||
) -> Result<Client> {
|
||||
self.create_request_with_tls_mode(
|
||||
proxy_type,
|
||||
timeout_secs,
|
||||
user_agent,
|
||||
accept_invalid_certs,
|
||||
TlsRootMode::PlatformVerifier,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_with_tls_mode(
|
||||
&self,
|
||||
url: &str,
|
||||
@@ -279,7 +256,7 @@ impl NetworkManager {
|
||||
self.build_client(proxy_url, headers, accept_invalid_certs, timeout_secs, tls_root_mode)
|
||||
}
|
||||
|
||||
pub async fn get_with_interrupt(
|
||||
pub async fn get(
|
||||
&self,
|
||||
url: &str,
|
||||
proxy_type: ProxyType,
|
||||
@@ -355,8 +332,8 @@ impl NetworkManager {
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
/// Downloads a binary body, mirroring the TLS fallback of [`Self::get_with_interrupt`].
|
||||
pub async fn get_bytes_with_interrupt(
|
||||
/// Downloads a binary body, mirroring the TLS fallback of [`Self::get`].
|
||||
pub async fn get_bytes(
|
||||
&self,
|
||||
url: &str,
|
||||
proxy_type: ProxyType,
|
||||
|
||||
@@ -21,13 +21,13 @@ use clash_verge_logging::{Type, logging, logging_error};
|
||||
use clash_verge_signal;
|
||||
|
||||
pub mod dns;
|
||||
pub mod scheme;
|
||||
pub mod window;
|
||||
pub mod window_script;
|
||||
mod scheme;
|
||||
pub(crate) mod window;
|
||||
mod window_script;
|
||||
|
||||
static RESOLVE_DONE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn init_work_dir_and_logger() -> anyhow::Result<()> {
|
||||
pub(crate) fn init_work_dir_and_logger() -> anyhow::Result<()> {
|
||||
AsyncHandler::block_on(async {
|
||||
init_work_config().await;
|
||||
logging!(info, Type::Setup, "Initializing logger");
|
||||
@@ -36,16 +36,16 @@ pub fn init_work_dir_and_logger() -> anyhow::Result<()> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resolve_setup_sync() {
|
||||
pub(crate) fn resolve_setup_sync() {
|
||||
AsyncHandler::spawn(|| async {
|
||||
AsyncHandler::spawn_blocking(init_scheme);
|
||||
AsyncHandler::spawn_blocking(init_embed_server);
|
||||
AsyncHandler::spawn_blocking(server::embed_server);
|
||||
#[cfg(target_os = "linux")]
|
||||
AsyncHandler::spawn_blocking(watch_linux_theme_changed);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn resolve_setup_async() {
|
||||
pub(crate) fn resolve_setup_async() {
|
||||
AsyncHandler::spawn(|| async {
|
||||
logging!(info, Type::ClashVergeRev, "Version: {}", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
@@ -100,21 +100,16 @@ pub async fn resolve_reset_async() -> Result<(), anyhow::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn init_scheme() {
|
||||
fn init_scheme() {
|
||||
logging_error!(Type::Setup, init::init_scheme());
|
||||
}
|
||||
|
||||
pub async fn resolve_scheme(param: &str) -> Result<()> {
|
||||
pub(crate) async fn resolve_scheme(param: &str) {
|
||||
logging_error!(Type::Setup, scheme::resolve_scheme(param).await);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn init_embed_server() {
|
||||
server::embed_server();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) fn watch_linux_theme_changed() {
|
||||
fn watch_linux_theme_changed() {
|
||||
match dark_light::subscribe() {
|
||||
Ok(watcher) => {
|
||||
for mode in watcher.iter() {
|
||||
@@ -137,28 +132,28 @@ pub(crate) fn watch_linux_theme_changed() {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn init_resources() {
|
||||
async fn init_resources() {
|
||||
logging_error!(Type::Setup, init::init_resources().await);
|
||||
}
|
||||
|
||||
pub(super) async fn init_startup_script() {
|
||||
async fn init_startup_script() {
|
||||
logging_error!(Type::Setup, init::startup_script().await);
|
||||
}
|
||||
|
||||
pub(super) async fn init_timer() {
|
||||
async fn init_timer() {
|
||||
logging_error!(Type::Setup, Timer::global().init().await);
|
||||
}
|
||||
|
||||
pub(super) async fn init_hotkey() {
|
||||
async fn init_hotkey() {
|
||||
let skip_register_hotkeys = !Config::verge().await.latest_arc().enable_global_hotkey.unwrap_or(true);
|
||||
logging_error!(Type::Setup, Hotkey::global().init(skip_register_hotkeys).await);
|
||||
}
|
||||
|
||||
pub(super) async fn init_auto_lightweight_boot() {
|
||||
async fn init_auto_lightweight_boot() {
|
||||
logging_error!(Type::Setup, auto_lightweight_boot().await);
|
||||
}
|
||||
|
||||
pub(super) async fn init_auto_backup() {
|
||||
async fn init_auto_backup() {
|
||||
logging_error!(Type::Setup, AutoBackupManager::global().init().await);
|
||||
}
|
||||
|
||||
@@ -184,37 +179,37 @@ async fn init_silent_updater() {
|
||||
logging!(info, Type::Setup, "Silent updater initialized");
|
||||
}
|
||||
|
||||
pub fn init_signal() {
|
||||
pub(crate) fn init_signal() {
|
||||
logging!(info, Type::Setup, "Initializing signal handlers...");
|
||||
clash_verge_signal::register(feat::quit);
|
||||
}
|
||||
|
||||
pub async fn init_work_config() {
|
||||
async fn init_work_config() {
|
||||
logging_error!(Type::Setup, init::init_config().await);
|
||||
}
|
||||
|
||||
pub(super) async fn init_tray() {
|
||||
async fn init_tray() {
|
||||
logging_error!(Type::Setup, Tray::global().init().await);
|
||||
}
|
||||
|
||||
pub(super) async fn init_verge_config() {
|
||||
async fn init_verge_config() {
|
||||
logging_error!(Type::Setup, Config::init_runtime_config().await);
|
||||
}
|
||||
|
||||
pub(super) async fn init_verge_config_before_window() -> bool {
|
||||
async fn init_verge_config_before_window() -> bool {
|
||||
let result = Config::init_config_before_window().await;
|
||||
let success = result.is_ok();
|
||||
logging_error!(Type::Setup, result);
|
||||
success
|
||||
}
|
||||
|
||||
pub(super) async fn init_service_manager() {
|
||||
async fn init_service_manager() {
|
||||
clash_verge_service_ipc::set_config(Some(ServiceManager::config())).await;
|
||||
|
||||
SERVICE_MANAGER.detect_startup_status().await;
|
||||
}
|
||||
|
||||
pub(super) async fn init_core_manager() -> bool {
|
||||
async fn init_core_manager() -> bool {
|
||||
match CoreManager::global().init().await {
|
||||
Ok(initialized) => initialized,
|
||||
Err(error) => {
|
||||
@@ -224,27 +219,27 @@ pub(super) async fn init_core_manager() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn refresh_tray_menu() {
|
||||
async fn refresh_tray_menu() {
|
||||
logging_error!(Type::Setup, Tray::global().update_part().await);
|
||||
}
|
||||
|
||||
pub(super) async fn init_window() {
|
||||
async fn init_window() {
|
||||
let is_silent_start = Config::verge().await.data_arc().enable_silent_start.unwrap_or(false);
|
||||
WindowManager::create_window(!is_silent_start).await;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(super) async fn resolve_dock_show() {
|
||||
async fn resolve_dock_show() {
|
||||
let is_silent_start = Config::verge().await.data_arc().enable_silent_start.unwrap_or(false);
|
||||
if is_silent_start {
|
||||
Handle::global().set_activation_policy_accessory();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_done() {
|
||||
fn resolve_done() {
|
||||
RESOLVE_DONE.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn is_resolve_done() -> bool {
|
||||
pub(crate) fn is_resolve_done() -> bool {
|
||||
RESOLVE_DONE.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ async fn post_import_updates(uid: &String, had_current_profile: bool) {
|
||||
false
|
||||
} else {
|
||||
let profiles = Config::profiles().await;
|
||||
profiles.latest_arc().is_current_profile_index(uid)
|
||||
profiles.latest_arc().current.as_ref() == Some(uid)
|
||||
};
|
||||
|
||||
if should_update_core {
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
utils::{dirs, window_manager::WindowManager},
|
||||
};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use clash_verge_logging::{Type, logging, logging_error};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use reqwest::ClientBuilder;
|
||||
@@ -250,7 +250,7 @@ fn start_embedded_server(listener: tokio::net::TcpListener, token: String) {
|
||||
));
|
||||
}
|
||||
AsyncHandler::spawn(|| async move {
|
||||
logging_error!(Type::Setup, resolve::resolve_scheme(&query.param).await);
|
||||
resolve::resolve_scheme(&query.param).await;
|
||||
});
|
||||
Ok::<_, warp::Rejection>(warp::reply::with_status("ok".to_string(), warp::http::StatusCode::OK))
|
||||
});
|
||||
@@ -426,35 +426,7 @@ fn write_instance_record(path: &Path, record: &InstanceRecord) -> Result<()> {
|
||||
file.set_permissions(std::fs::Permissions::from_mode(0o600))?;
|
||||
}
|
||||
drop(file);
|
||||
replace_file_atomic(&temporary, path).context("failed to replace singleton record")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub(crate) fn replace_file_atomic(source: &Path, destination: &Path) -> Result<()> {
|
||||
std::fs::rename(source, destination)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn replace_file_atomic(source: &Path, destination: &Path) -> Result<()> {
|
||||
use std::os::windows::ffi::OsStrExt as _;
|
||||
use windows_sys::Win32::Storage::FileSystem::{MOVEFILE_REPLACE_EXISTING, MOVEFILE_WRITE_THROUGH, MoveFileExW};
|
||||
|
||||
let mut source: Vec<u16> = source.as_os_str().encode_wide().collect();
|
||||
source.push(0);
|
||||
let mut destination: Vec<u16> = destination.as_os_str().encode_wide().collect();
|
||||
destination.push(0);
|
||||
if unsafe {
|
||||
MoveFileExW(
|
||||
source.as_ptr(),
|
||||
destination.as_ptr(),
|
||||
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH,
|
||||
)
|
||||
} == 0
|
||||
{
|
||||
return Err(std::io::Error::last_os_error().into());
|
||||
}
|
||||
std::fs::rename(&temporary, path).context("failed to replace singleton record")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ impl WindowManager {
|
||||
handle::Handle::global().set_activation_policy_regular();
|
||||
}
|
||||
|
||||
pub fn get_main_window_with_state() -> (Option<WebviewWindow<Wry>>, WindowState) {
|
||||
fn get_main_window_with_state() -> (Option<WebviewWindow<Wry>>, WindowState) {
|
||||
let Some(window) = Self::get_main_window() else {
|
||||
return (None, WindowState::NotExist);
|
||||
};
|
||||
@@ -73,28 +73,7 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
pub fn get_main_window_state() -> WindowState {
|
||||
match Self::get_main_window() {
|
||||
Some(window) => {
|
||||
let is_minimized = window.is_minimized().unwrap_or(false);
|
||||
let is_visible = window.is_visible().unwrap_or(false);
|
||||
let is_focused = window.is_focused().unwrap_or(false);
|
||||
|
||||
if is_minimized {
|
||||
return WindowState::Minimized;
|
||||
}
|
||||
|
||||
if !is_visible {
|
||||
return WindowState::Hidden;
|
||||
}
|
||||
|
||||
if is_focused {
|
||||
WindowState::VisibleFocused
|
||||
} else {
|
||||
WindowState::VisibleUnfocused
|
||||
}
|
||||
}
|
||||
None => WindowState::NotExist,
|
||||
}
|
||||
Self::get_main_window_with_state().1
|
||||
}
|
||||
|
||||
pub fn get_main_window() -> Option<WebviewWindow<Wry>> {
|
||||
@@ -262,7 +241,7 @@ impl WindowManager {
|
||||
window.map(|w| w.is_focused().unwrap_or(false)).unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn is_main_window_minimized(window: Option<&WebviewWindow<Wry>>) -> bool {
|
||||
fn is_main_window_minimized(window: Option<&WebviewWindow<Wry>>) -> bool {
|
||||
window.map(|w| w.is_minimized().unwrap_or(false)).unwrap_or(false)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user