mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-27 16:22:11 +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:
@@ -203,27 +203,17 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn check_dns_config_exists() -> CmdResult<bool> {
|
||||
use crate::utils::dirs;
|
||||
|
||||
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
|
||||
|
||||
Ok(dns_path.exists())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_dns_config_content() -> CmdResult<String> {
|
||||
pub async fn get_dns_config_content() -> CmdResult<Option<String>> {
|
||||
use crate::utils::dirs;
|
||||
use tokio::fs;
|
||||
|
||||
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
|
||||
|
||||
if !fs::try_exists(&dns_path).await.stringify_err()? {
|
||||
return Err("DNS config file not found".into());
|
||||
match fs::read_to_string(&dns_path).await {
|
||||
Ok(content) => Ok(Some(content.into())),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
||||
Err(error) => Err(CommandFailure::plain(error)),
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&dns_path).await.stringify_err()?.into();
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -177,7 +177,7 @@ pub async fn delete_profile(index: String) -> CmdResult {
|
||||
}
|
||||
};
|
||||
if should_update {
|
||||
logging_error!(Type::Config, profiles::activate_selected_nodes());
|
||||
profiles::activate_selected_nodes();
|
||||
}
|
||||
drop(config_update_guard);
|
||||
drop(profile_write_guard);
|
||||
@@ -241,7 +241,7 @@ async fn handle_success(current_value: Option<&String>) -> CmdResult<ValidationO
|
||||
.await
|
||||
.stringify_err()?;
|
||||
// Runtime refresh and tray rebuilding happen after saved node selections are restored.
|
||||
profiles::activate_selected_nodes().stringify_err()?;
|
||||
profiles::activate_selected_nodes();
|
||||
|
||||
if let Err(e) = profiles_save_file_safe().await {
|
||||
logging!(warn, Type::Cmd, "Warning: 异步保存配置文件失败: {e}");
|
||||
|
||||
@@ -107,7 +107,7 @@ async fn restore_original(
|
||||
}
|
||||
|
||||
fn profile_affects_runtime(profiles: &IProfiles, index: &str) -> bool {
|
||||
let Some(current_uid) = profiles.get_current() else {
|
||||
let Some(current_uid) = profiles.current.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
if current_uid == index {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::config::Config;
|
||||
use crate::constants::{network, tun as tun_const};
|
||||
use crate::utils::dirs::{path_to_str, sidecar_ipc_path};
|
||||
use crate::utils::{dirs, help};
|
||||
@@ -118,7 +117,7 @@ impl IClashTemp {
|
||||
let mixed_port = Self::guard_mixed_port(&config);
|
||||
let socks_port = Self::guard_socks_port(&config);
|
||||
let port = Self::guard_port(&config);
|
||||
let ctrl = Self::guard_external_controller(&config);
|
||||
let ctrl = Self::guard_server_ctrl(&config);
|
||||
#[cfg(unix)]
|
||||
let external_controller_unix = Self::guard_external_controller_ipc();
|
||||
#[cfg(windows)]
|
||||
@@ -154,16 +153,6 @@ impl IClashTemp {
|
||||
Self::guard_mixed_port(&self.0)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn get_socks_port(&self) -> u16 {
|
||||
Self::guard_socks_port(&self.0)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn get_port(&self) -> u16 {
|
||||
Self::guard_port(&self.0)
|
||||
}
|
||||
|
||||
pub fn get_client_info(&self) -> ClashInfo {
|
||||
let config = &self.0;
|
||||
|
||||
@@ -192,7 +181,7 @@ impl IClashTemp {
|
||||
})
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn guard_redir_port(config: &Mapping) -> u16 {
|
||||
fn guard_redir_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("redir-port")
|
||||
.and_then(|value| match value {
|
||||
@@ -208,7 +197,7 @@ impl IClashTemp {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn guard_tproxy_port(config: &Mapping) -> u16 {
|
||||
fn guard_tproxy_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("tproxy-port")
|
||||
.and_then(|value| match value {
|
||||
@@ -223,7 +212,7 @@ impl IClashTemp {
|
||||
port
|
||||
}
|
||||
|
||||
pub fn guard_mixed_port(config: &Mapping) -> u16 {
|
||||
fn guard_mixed_port(config: &Mapping) -> u16 {
|
||||
let raw_value = config.get("mixed-port");
|
||||
|
||||
let mut port = raw_value
|
||||
@@ -232,16 +221,16 @@ impl IClashTemp {
|
||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(7897);
|
||||
.unwrap_or(network::ports::DEFAULT_MIXED);
|
||||
|
||||
if port == 0 {
|
||||
port = 7897;
|
||||
port = network::ports::DEFAULT_MIXED;
|
||||
}
|
||||
|
||||
port
|
||||
}
|
||||
|
||||
pub fn guard_socks_port(config: &Mapping) -> u16 {
|
||||
fn guard_socks_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("socks-port")
|
||||
.and_then(|value| match value {
|
||||
@@ -256,7 +245,7 @@ impl IClashTemp {
|
||||
port
|
||||
}
|
||||
|
||||
pub fn guard_port(config: &Mapping) -> u16 {
|
||||
fn guard_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("port")
|
||||
.and_then(|value| match value {
|
||||
@@ -271,7 +260,7 @@ impl IClashTemp {
|
||||
port
|
||||
}
|
||||
|
||||
pub fn guard_server_ctrl(config: &Mapping) -> String {
|
||||
pub(super) fn guard_server_ctrl(config: &Mapping) -> String {
|
||||
config
|
||||
.get("external-controller")
|
||||
.and_then(|value| match value.as_str() {
|
||||
@@ -290,28 +279,7 @@ impl IClashTemp {
|
||||
.unwrap_or_else(|| "127.0.0.1:9097".into())
|
||||
}
|
||||
|
||||
pub fn guard_external_controller(config: &Mapping) -> String {
|
||||
// 在初始化阶段,直接返回配置中的值,不进行额外检查
|
||||
// 这样可以避免在配置加载期间的循环依赖
|
||||
Self::guard_server_ctrl(config)
|
||||
}
|
||||
|
||||
pub async fn guard_external_controller_with_setting(config: &Mapping) -> String {
|
||||
// 检查 enable_external_controller 设置,用于运行时配置生成
|
||||
let enable_external_controller = Config::verge()
|
||||
.await
|
||||
.latest_arc()
|
||||
.enable_external_controller
|
||||
.unwrap_or(false);
|
||||
|
||||
if enable_external_controller {
|
||||
Self::guard_server_ctrl(config)
|
||||
} else {
|
||||
"".into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn guard_client_ctrl(config: &Mapping) -> String {
|
||||
fn guard_client_ctrl(config: &Mapping) -> String {
|
||||
let value = Self::guard_server_ctrl(config);
|
||||
match SocketAddr::from_str(value.as_str()) {
|
||||
Ok(mut socket) => {
|
||||
@@ -324,7 +292,7 @@ impl IClashTemp {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn guard_external_controller_ipc() -> String {
|
||||
pub(crate) fn guard_external_controller_ipc() -> String {
|
||||
// 总是使用当前的 IPC 路径,确保配置文件与运行时路径一致
|
||||
sidecar_ipc_path()
|
||||
.ok()
|
||||
|
||||
@@ -27,7 +27,7 @@ use std::{
|
||||
use tokio::sync::{Mutex, MutexGuard, OnceCell};
|
||||
use tokio::time::sleep;
|
||||
|
||||
pub struct Config {
|
||||
pub(crate) struct Config {
|
||||
clash_config: Draft<IClashTemp>,
|
||||
verge_config: Draft<IVerge>,
|
||||
profiles_config: Draft<IProfiles>,
|
||||
@@ -38,7 +38,7 @@ static TUN_SESSION_SUPPRESSED: AtomicBool = AtomicBool::new(false);
|
||||
static CONFIG_WRITE_LOCK: Mutex<()> = Mutex::const_new(());
|
||||
|
||||
impl Config {
|
||||
pub async fn global() -> &'static Self {
|
||||
async fn global() -> &'static Self {
|
||||
static CONFIG: OnceCell<Config> = OnceCell::const_new();
|
||||
CONFIG
|
||||
.get_or_init(|| async {
|
||||
@@ -46,7 +46,7 @@ impl Config {
|
||||
clash_config: Draft::new(IClashTemp::new().await),
|
||||
verge_config: Draft::new(IVerge::new().await),
|
||||
profiles_config: Draft::new(IProfiles::new().await),
|
||||
runtime_config: Draft::new(IRuntime::new()),
|
||||
runtime_config: Draft::new(IRuntime::default()),
|
||||
}
|
||||
})
|
||||
.await
|
||||
@@ -73,11 +73,6 @@ impl Config {
|
||||
CONFIG_WRITE_LOCK.lock().await
|
||||
}
|
||||
|
||||
pub async fn init_config() -> Result<()> {
|
||||
Self::init_config_before_window().await?;
|
||||
Self::init_runtime_config().await
|
||||
}
|
||||
|
||||
pub async fn init_config_before_window() -> Result<()> {
|
||||
Self::ensure_default_profile_items().await?;
|
||||
|
||||
@@ -151,7 +146,7 @@ impl Config {
|
||||
}
|
||||
|
||||
async fn ensure_default_profile_items_for(profiles: &Draft<IProfiles>) -> Result<()> {
|
||||
if profiles.latest_arc().get_items().is_none() {
|
||||
if profiles.latest_arc().items.is_none() {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
@@ -161,11 +156,11 @@ impl Config {
|
||||
}
|
||||
|
||||
if profiles.latest_arc().get_item("Merge").is_err() {
|
||||
let merge_item = &mut PrfItem::from_merge(Some("Merge".into()))?;
|
||||
let merge_item = &mut PrfItem::from_merge(Some("Merge".into()));
|
||||
profiles_append_item_to_safe(profiles, merge_item).await?;
|
||||
}
|
||||
if profiles.latest_arc().get_item("Script").is_err() {
|
||||
let script_item = &mut PrfItem::from_script(Some("Script".into()))?;
|
||||
let script_item = &mut PrfItem::from_script(Some("Script".into()));
|
||||
profiles_append_item_to_safe(profiles, script_item).await?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -381,7 +376,7 @@ fn collect_names(config: &Mapping, list_key: &str, out: &mut HashSet<String>) {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigType {
|
||||
pub(crate) enum ConfigType {
|
||||
Run,
|
||||
Check,
|
||||
}
|
||||
@@ -405,7 +400,7 @@ mod tests {
|
||||
tokio::fs::remove_dir_all(&profiles_dir).await?;
|
||||
|
||||
assert!(profile_was_preserved);
|
||||
assert!(profiles.data_arc().get_items().is_none());
|
||||
assert!(profiles.data_arc().items.is_none());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,6 @@ use anyhow::Result;
|
||||
use super::Config;
|
||||
use crate::core::handle::Handle;
|
||||
|
||||
/// The port mihomo listens on when nothing else is configured.
|
||||
pub const DEFAULT_MIXED_PORT: u16 = 7897;
|
||||
|
||||
/// A startup fallback for this session only; persistence would make the port climb across launches.
|
||||
static SESSION_FALLBACK: AtomicU16 = AtomicU16::new(0);
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ pub mod runtime;
|
||||
pub(crate) mod snapshot;
|
||||
mod verge;
|
||||
|
||||
pub use self::{clash::*, config::*, encrypt::*, mixed_port::*, prfitem::*, profiles::*, verge::*};
|
||||
pub(crate) use self::config::{Config, ConfigType};
|
||||
pub use self::{clash::*, encrypt::*, mixed_port::*, prfitem::*, profiles::*, verge::*};
|
||||
|
||||
pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) {
|
||||
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";
|
||||
|
||||
@@ -36,14 +36,6 @@ static STARTUP_CORE_BLOCK_REASON: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mut
|
||||
|
||||
impl Config {
|
||||
pub(crate) async fn resolve_startup_mixed_port() -> Result<bool> {
|
||||
Self::resolve_startup_mixed_port_inner().await
|
||||
}
|
||||
|
||||
pub(crate) async fn retry_startup_mixed_port_fallback() -> Result<bool> {
|
||||
Self::resolve_startup_mixed_port_inner().await
|
||||
}
|
||||
|
||||
async fn resolve_startup_mixed_port_inner() -> Result<bool> {
|
||||
let _config_write = Self::lock_config_write().await;
|
||||
let clash = Self::clash().await.latest_arc();
|
||||
let verge = Self::verge().await.latest_arc();
|
||||
@@ -287,7 +279,7 @@ fn configured_listener_ports(clash: &IClashTemp, verge: &IVerge) -> HashSet<u16>
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(controller) = SocketAddr::from_str(IClashTemp::guard_external_controller(&clash.0).as_str()) {
|
||||
if let Ok(controller) = SocketAddr::from_str(IClashTemp::guard_server_ctrl(&clash.0).as_str()) {
|
||||
ports.insert(controller.port());
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ pub struct PrfOption {
|
||||
}
|
||||
|
||||
impl PrfOption {
|
||||
pub fn merge(one: Option<&Self>, other: Option<&Self>) -> Option<Self> {
|
||||
pub(crate) fn merge(one: Option<&Self>, other: Option<&Self>) -> Option<Self> {
|
||||
match (one, other) {
|
||||
(Some(a_ref), Some(b_ref)) => {
|
||||
let mut result = a_ref.clone();
|
||||
@@ -132,11 +132,7 @@ impl PrfOption {
|
||||
|
||||
impl PrfItem {
|
||||
/// Builds an item from a partial value that must include `itype`.
|
||||
pub async fn from(item: &Self, file_data: Option<String>) -> Result<Self> {
|
||||
if item.itype.is_none() {
|
||||
bail!("type should not be null");
|
||||
}
|
||||
|
||||
pub(super) async fn from(item: &Self, file_data: Option<String>) -> Result<Self> {
|
||||
let itype = item
|
||||
.itype
|
||||
.as_ref()
|
||||
@@ -162,7 +158,7 @@ impl PrfItem {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_local(
|
||||
async fn from_local(
|
||||
name: String,
|
||||
desc: String,
|
||||
file_data: Option<String>,
|
||||
@@ -179,27 +175,27 @@ impl PrfItem {
|
||||
let mut groups = opt_ref.and_then(|o| o.groups.clone());
|
||||
|
||||
if merge.is_none() {
|
||||
let merge_item = &mut Self::from_merge(None)?;
|
||||
let merge_item = &mut Self::from_merge(None);
|
||||
profiles::profiles_append_item_safe(merge_item).await?;
|
||||
merge = merge_item.uid.clone();
|
||||
}
|
||||
if script.is_none() {
|
||||
let script_item = &mut Self::from_script(None)?;
|
||||
let script_item = &mut Self::from_script(None);
|
||||
profiles::profiles_append_item_safe(script_item).await?;
|
||||
script = script_item.uid.clone();
|
||||
}
|
||||
if rules.is_none() {
|
||||
let rules_item = &mut Self::from_rules()?;
|
||||
let rules_item = &mut Self::from_rules();
|
||||
profiles::profiles_append_item_safe(rules_item).await?;
|
||||
rules = rules_item.uid.clone();
|
||||
}
|
||||
if proxies.is_none() {
|
||||
let proxies_item = &mut Self::from_proxies()?;
|
||||
let proxies_item = &mut Self::from_proxies();
|
||||
profiles::profiles_append_item_safe(proxies_item).await?;
|
||||
proxies = proxies_item.uid.clone();
|
||||
}
|
||||
if groups.is_none() {
|
||||
let groups_item = &mut Self::from_groups()?;
|
||||
let groups_item = &mut Self::from_groups();
|
||||
profiles::profiles_append_item_safe(groups_item).await?;
|
||||
groups = groups_item.uid.clone();
|
||||
}
|
||||
@@ -227,7 +223,7 @@ impl PrfItem {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn from_url(
|
||||
pub(crate) async fn from_url(
|
||||
url: &str,
|
||||
name: Option<&String>,
|
||||
desc: Option<&String>,
|
||||
@@ -257,7 +253,7 @@ impl PrfItem {
|
||||
let url = fix_dirty_url(url)?;
|
||||
|
||||
let resp = match NetworkManager::new()
|
||||
.get_with_interrupt(
|
||||
.get(
|
||||
url.as_str(),
|
||||
proxy_type,
|
||||
Some(timeout),
|
||||
@@ -349,7 +345,7 @@ impl PrfItem {
|
||||
let name = name
|
||||
.map(|s| s.to_owned())
|
||||
.unwrap_or_else(|| filename.map(|s| s.into()).unwrap_or_else(|| "Remote File".into()));
|
||||
let data = resp.text_with_charset()?;
|
||||
let data = resp.text();
|
||||
|
||||
let data = data.trim_start_matches('\u{feff}');
|
||||
|
||||
@@ -360,27 +356,27 @@ impl PrfItem {
|
||||
}
|
||||
|
||||
if merge.is_none() {
|
||||
let merge_item = &mut Self::from_merge(None)?;
|
||||
let merge_item = &mut Self::from_merge(None);
|
||||
profiles::profiles_append_item_safe(merge_item).await?;
|
||||
merge = merge_item.uid.clone();
|
||||
}
|
||||
if script.is_none() {
|
||||
let script_item = &mut Self::from_script(None)?;
|
||||
let script_item = &mut Self::from_script(None);
|
||||
profiles::profiles_append_item_safe(script_item).await?;
|
||||
script = script_item.uid.clone();
|
||||
}
|
||||
if rules.is_none() {
|
||||
let rules_item = &mut Self::from_rules()?;
|
||||
let rules_item = &mut Self::from_rules();
|
||||
profiles::profiles_append_item_safe(rules_item).await?;
|
||||
rules = rules_item.uid.clone();
|
||||
}
|
||||
if proxies.is_none() {
|
||||
let proxies_item = &mut Self::from_proxies()?;
|
||||
let proxies_item = &mut Self::from_proxies();
|
||||
profiles::profiles_append_item_safe(proxies_item).await?;
|
||||
proxies = proxies_item.uid.clone();
|
||||
}
|
||||
if groups.is_none() {
|
||||
let groups_item = &mut Self::from_groups()?;
|
||||
let groups_item = &mut Self::from_groups();
|
||||
profiles::profiles_append_item_safe(groups_item).await?;
|
||||
groups = groups_item.uid.clone();
|
||||
}
|
||||
@@ -410,7 +406,7 @@ impl PrfItem {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_merge(uid: Option<String>) -> Result<Self> {
|
||||
pub(super) fn from_merge(uid: Option<String>) -> Self {
|
||||
let (id, template) = if let Some(uid) = uid {
|
||||
(uid, tmpl::ITEM_MERGE.into())
|
||||
} else {
|
||||
@@ -418,76 +414,76 @@ impl PrfItem {
|
||||
};
|
||||
let file = format!("{id}.yaml").into();
|
||||
|
||||
Ok(Self {
|
||||
Self {
|
||||
uid: Some(id),
|
||||
itype: Some("merge".into()),
|
||||
file: Some(file),
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(template),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_script(uid: Option<String>) -> Result<Self> {
|
||||
pub(super) fn from_script(uid: Option<String>) -> Self {
|
||||
let id = if let Some(uid) = uid {
|
||||
uid
|
||||
} else {
|
||||
help::get_uid("s").into()
|
||||
};
|
||||
let file = format!("{id}.js").into(); // js ext
|
||||
Ok(Self {
|
||||
Self {
|
||||
uid: Some(id),
|
||||
itype: Some("script".into()),
|
||||
file: Some(file),
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(tmpl::ITEM_SCRIPT.into()),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_rules() -> Result<Self> {
|
||||
fn from_rules() -> Self {
|
||||
let uid = help::get_uid("r").into();
|
||||
let file = format!("{uid}.yaml").into(); // yaml ext
|
||||
|
||||
Ok(Self {
|
||||
Self {
|
||||
uid: Some(uid),
|
||||
itype: Some("rules".into()),
|
||||
file: Some(file),
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(tmpl::ITEM_RULES.into()),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_proxies() -> Result<Self> {
|
||||
fn from_proxies() -> Self {
|
||||
let uid = help::get_uid("p").into();
|
||||
let file = format!("{uid}.yaml").into(); // yaml ext
|
||||
|
||||
Ok(Self {
|
||||
Self {
|
||||
uid: Some(uid),
|
||||
itype: Some("proxies".into()),
|
||||
file: Some(file),
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(tmpl::ITEM_PROXIES.into()),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_groups() -> Result<Self> {
|
||||
fn from_groups() -> Self {
|
||||
let uid = help::get_uid("g").into();
|
||||
let file = format!("{uid}.yaml").into(); // yaml ext
|
||||
|
||||
Ok(Self {
|
||||
Self {
|
||||
uid: Some(uid),
|
||||
itype: Some("groups".into()),
|
||||
file: Some(file),
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(tmpl::ITEM_GROUPS.into()),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_file(&self) -> Result<String> {
|
||||
pub(crate) async fn read_file(&self) -> Result<String> {
|
||||
let file = self
|
||||
.file
|
||||
.as_ref()
|
||||
@@ -498,37 +494,26 @@ impl PrfItem {
|
||||
.with_context(|| format!("failed to read the file \"{}\"", path.display()))?;
|
||||
Ok(content.into())
|
||||
}
|
||||
|
||||
pub async fn save_file(&self, data: String) -> Result<()> {
|
||||
let file = self
|
||||
.file
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("could not find the file"))?;
|
||||
let path = dirs::app_profiles_dir()?.join(file.as_str());
|
||||
fs::write(path, data.as_bytes())
|
||||
.await
|
||||
.context("failed to save the file")
|
||||
}
|
||||
}
|
||||
|
||||
impl PrfItem {
|
||||
pub fn current_merge(&self) -> Option<&String> {
|
||||
pub(crate) fn current_merge(&self) -> Option<&String> {
|
||||
self.option.as_ref().and_then(|o| o.merge.as_ref())
|
||||
}
|
||||
|
||||
pub fn current_script(&self) -> Option<&String> {
|
||||
pub(crate) fn current_script(&self) -> Option<&String> {
|
||||
self.option.as_ref().and_then(|o| o.script.as_ref())
|
||||
}
|
||||
|
||||
pub fn current_rules(&self) -> Option<&String> {
|
||||
pub(crate) fn current_rules(&self) -> Option<&String> {
|
||||
self.option.as_ref().and_then(|o| o.rules.as_ref())
|
||||
}
|
||||
|
||||
pub fn current_proxies(&self) -> Option<&String> {
|
||||
pub(crate) fn current_proxies(&self) -> Option<&String> {
|
||||
self.option.as_ref().and_then(|o| o.proxies.as_ref())
|
||||
}
|
||||
|
||||
pub fn current_groups(&self) -> Option<&String> {
|
||||
pub(crate) fn current_groups(&self) -> Option<&String> {
|
||||
self.option.as_ref().and_then(|o| o.groups.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,14 +63,6 @@ pub struct IProfilePreview<'a> {
|
||||
pub is_current: bool,
|
||||
}
|
||||
|
||||
/// 清理结果
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CleanupResult {
|
||||
pub total_files: usize,
|
||||
pub deleted_files: usize,
|
||||
pub failed_deletions: usize,
|
||||
}
|
||||
|
||||
pub(crate) struct ProfileDeletePlan {
|
||||
files: Vec<String>,
|
||||
}
|
||||
@@ -135,7 +127,7 @@ impl IProfiles {
|
||||
}
|
||||
|
||||
pub async fn save_file(&self) -> Result<()> {
|
||||
help::save_yaml_atomic(&dirs::profiles_path()?, self, Some("# Profiles Config for Clash Verge")).await
|
||||
help::save_yaml(&dirs::profiles_path()?, self, Some("# Profiles Config for Clash Verge")).await
|
||||
}
|
||||
|
||||
/// 只修改current,valid和chain
|
||||
@@ -154,14 +146,6 @@ impl IProfiles {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn get_current(&self) -> Option<&String> {
|
||||
self.current.as_ref()
|
||||
}
|
||||
|
||||
pub const fn get_items(&self) -> Option<&Vec<PrfItem>> {
|
||||
self.items.as_ref()
|
||||
}
|
||||
|
||||
pub fn get_item(&self, uid: impl AsRef<str>) -> Result<&PrfItem> {
|
||||
let uid_str = uid.as_ref();
|
||||
|
||||
@@ -178,7 +162,7 @@ impl IProfiles {
|
||||
bail!("failed to get the profile item \"uid:{}\"", uid_str);
|
||||
}
|
||||
|
||||
pub async fn append_item(&mut self, item: &mut PrfItem) -> Result<()> {
|
||||
async fn append_item(&mut self, item: &mut PrfItem) -> Result<()> {
|
||||
let uid = &item.uid;
|
||||
if uid.is_none() {
|
||||
bail!("the uid should not be null");
|
||||
@@ -215,31 +199,23 @@ impl IProfiles {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for (i, _) in items.iter().enumerate() {
|
||||
if items[i].uid.as_ref() == Some(active_id) {
|
||||
old_index = Some(i);
|
||||
}
|
||||
if items[i].uid.as_ref() == Some(over_id) {
|
||||
new_index = Some(i);
|
||||
}
|
||||
async fn reorder(&mut self, active_id: &str, over_id: &str) -> Result<()> {
|
||||
{
|
||||
let Some(items) = self.items.as_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
let old_index = items.iter().rposition(|item| item.uid.as_deref() == Some(active_id));
|
||||
let new_index = items.iter().rposition(|item| item.uid.as_deref() == Some(over_id));
|
||||
let (Some(old_idx), Some(new_idx)) = (old_index, new_index) else {
|
||||
return Ok(());
|
||||
};
|
||||
let item = items.remove(old_idx);
|
||||
items.insert(new_idx, item);
|
||||
}
|
||||
|
||||
let (old_idx, new_idx) = match (old_index, new_index) {
|
||||
(Some(old), Some(new)) => (old, new),
|
||||
_ => return Ok(()),
|
||||
};
|
||||
let item = items.remove(old_idx);
|
||||
items.insert(new_idx, item);
|
||||
self.items = Some(items);
|
||||
self.save_file().await
|
||||
}
|
||||
|
||||
pub async fn patch_item(&mut self, uid: &String, item: &PrfItem) -> Result<()> {
|
||||
async fn patch_item(&mut self, uid: &str, item: &PrfItem) -> Result<()> {
|
||||
if let Some(file) = &item.file {
|
||||
Self::validate_profile_file(file)?;
|
||||
}
|
||||
@@ -247,7 +223,7 @@ impl IProfiles {
|
||||
let mut items = self.items.take().unwrap_or_default();
|
||||
|
||||
for each in items.iter_mut() {
|
||||
if each.uid.as_ref() == Some(uid) {
|
||||
if each.uid.as_deref() == Some(uid) {
|
||||
patch!(each, item, itype);
|
||||
patch!(each, item, name);
|
||||
patch!(each, item, desc);
|
||||
@@ -284,7 +260,7 @@ impl IProfiles {
|
||||
}
|
||||
|
||||
/// Updates fields returned by a remote profile refresh.
|
||||
pub async fn update_item(&mut self, uid: &String, item: &mut PrfItem) -> Result<()> {
|
||||
async fn update_item(&mut self, uid: &str, item: &mut PrfItem) -> Result<()> {
|
||||
if self.items.is_none() {
|
||||
self.items = Some(vec![]);
|
||||
}
|
||||
@@ -292,10 +268,8 @@ impl IProfiles {
|
||||
let _ = self.get_item(uid)?;
|
||||
|
||||
if let Some(items) = self.items.as_mut() {
|
||||
let some_uid = Some(uid.clone());
|
||||
|
||||
for each in items.iter_mut() {
|
||||
if each.uid == some_uid {
|
||||
if each.uid.as_deref() == Some(uid) {
|
||||
each.extra = item.extra;
|
||||
each.updated = item.updated;
|
||||
each.home = item.home.to_owned();
|
||||
@@ -343,8 +317,8 @@ impl IProfiles {
|
||||
raised
|
||||
}
|
||||
|
||||
pub(crate) fn plan_delete_item(&mut self, uid: &String) -> Result<(bool, ProfileDeletePlan)> {
|
||||
let current = self.current.as_ref().unwrap_or(uid).clone();
|
||||
pub(crate) fn plan_delete_item(&mut self, uid: &str) -> Result<(bool, ProfileDeletePlan)> {
|
||||
let deleting_current = self.current.as_deref().is_none_or(|current| current == uid);
|
||||
let delete_uids = self.get_item(uid)?.option.as_ref().map_or_else(Vec::new, |op| {
|
||||
[
|
||||
op.merge.clone(),
|
||||
@@ -359,7 +333,7 @@ impl IProfiles {
|
||||
let mut items = self.items.take().unwrap_or_default();
|
||||
let mut files = Vec::new();
|
||||
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, Some(uid.as_str())) {
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, Some(uid)) {
|
||||
files.push(file);
|
||||
}
|
||||
|
||||
@@ -369,7 +343,7 @@ impl IProfiles {
|
||||
}
|
||||
}
|
||||
|
||||
if current == *uid {
|
||||
if deleting_current {
|
||||
self.current = None;
|
||||
for item in items.iter() {
|
||||
if item.itype == Some("remote".into()) || item.itype == Some("local".into()) {
|
||||
@@ -380,7 +354,7 @@ impl IProfiles {
|
||||
}
|
||||
|
||||
self.items = Some(items);
|
||||
Ok((current == *uid, ProfileDeletePlan { files }))
|
||||
Ok((deleting_current, ProfileDeletePlan { files }))
|
||||
}
|
||||
|
||||
/// 获取current指向的订阅内容
|
||||
@@ -400,17 +374,13 @@ impl IProfiles {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_current_profile_index(&self, index: &String) -> bool {
|
||||
self.current.as_ref() == Some(index)
|
||||
}
|
||||
|
||||
pub fn profiles_preview(&self) -> Option<Vec<IProfilePreview<'_>>> {
|
||||
self.items.as_ref().map(|items| {
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
if let (Some(uid), Some(name)) = (e.uid.as_ref(), e.name.as_ref()) {
|
||||
let is_current = self.is_current_profile_index(uid);
|
||||
let is_current = self.current.as_ref() == Some(uid);
|
||||
let preview = IProfilePreview { uid, name, is_current };
|
||||
Some(preview)
|
||||
} else {
|
||||
@@ -421,17 +391,6 @@ impl IProfiles {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_name_by_uid(&self, uid: &String) -> Option<&String> {
|
||||
if let Some(items) = &self.items {
|
||||
for item in items {
|
||||
if item.uid.as_ref() == Some(uid) {
|
||||
return item.name.as_ref();
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 以 app 中的 profile 列表为准,删除不再需要的文件
|
||||
pub async fn cleanup_orphaned_files(&self) -> Result<()> {
|
||||
let profiles_dir = dirs::app_profiles_dir()?;
|
||||
@@ -495,19 +454,13 @@ impl IProfiles {
|
||||
}
|
||||
}
|
||||
|
||||
let result = CleanupResult {
|
||||
total_files,
|
||||
deleted_files,
|
||||
failed_deletions,
|
||||
};
|
||||
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
"Profile 文件清理完成: 总文件数={}, 删除文件数={}, 失败数={}",
|
||||
result.total_files,
|
||||
result.deleted_files,
|
||||
result.failed_deletions
|
||||
total_files,
|
||||
deleted_files,
|
||||
failed_deletions
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -581,15 +534,15 @@ impl IProfiles {
|
||||
}
|
||||
}
|
||||
|
||||
// These helpers avoid holding a parking_lot guard across `await`.
|
||||
// These helpers serialize asynchronous operations against committed profile data.
|
||||
use crate::config::Config;
|
||||
|
||||
pub async fn profiles_append_item_with_filedata_safe(item: &PrfItem, file_data: Option<String>) -> Result<()> {
|
||||
pub(crate) async fn profiles_append_item_with_filedata_safe(item: &PrfItem, file_data: Option<String>) -> Result<()> {
|
||||
let item = &mut PrfItem::from(item, file_data).await?;
|
||||
profiles_append_item_safe(item).await
|
||||
}
|
||||
|
||||
pub async fn profiles_append_item_safe(item: &mut PrfItem) -> Result<()> {
|
||||
pub(crate) async fn profiles_append_item_safe(item: &mut PrfItem) -> Result<()> {
|
||||
let profiles = Config::profiles().await;
|
||||
profiles_append_item_to_safe(&profiles, item).await
|
||||
}
|
||||
@@ -603,7 +556,7 @@ pub(super) async fn profiles_append_item_to_safe(profiles: &Draft<IProfiles>, it
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn profiles_patch_item_safe(index: &String, item: &PrfItem) -> Result<()> {
|
||||
pub(crate) async fn profiles_patch_item_safe(index: &str, item: &PrfItem) -> Result<()> {
|
||||
Config::profiles()
|
||||
.await
|
||||
.with_data_modify(|mut profiles| async move {
|
||||
@@ -613,7 +566,7 @@ pub async fn profiles_patch_item_safe(index: &String, item: &PrfItem) -> Result<
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn profiles_reorder_safe(active_id: &String, over_id: &String) -> Result<()> {
|
||||
pub(crate) async fn profiles_reorder_safe(active_id: &str, over_id: &str) -> Result<()> {
|
||||
Config::profiles()
|
||||
.await
|
||||
.with_data_modify(|mut profiles| async move {
|
||||
@@ -623,7 +576,7 @@ pub async fn profiles_reorder_safe(active_id: &String, over_id: &String) -> Resu
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn profiles_save_file_safe() -> Result<()> {
|
||||
pub(crate) async fn profiles_save_file_safe() -> Result<()> {
|
||||
Config::profiles()
|
||||
.await
|
||||
.with_data_modify(|profiles| async move {
|
||||
@@ -633,7 +586,7 @@ pub async fn profiles_save_file_safe() -> Result<()> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn profiles_draft_update_item_safe(index: &String, item: &mut PrfItem) -> Result<()> {
|
||||
pub(crate) async fn profiles_update_item_safe(index: &str, item: &mut PrfItem) -> Result<()> {
|
||||
Config::profiles()
|
||||
.await
|
||||
.with_data_modify(|mut profiles| async move {
|
||||
@@ -765,7 +718,7 @@ fn reconcile_selected_nodes(
|
||||
}
|
||||
|
||||
/// Cancels restoration so it cannot overwrite a newer selection or profile snapshot.
|
||||
pub fn supersede_selected_activation() {
|
||||
pub(crate) fn supersede_selected_activation() {
|
||||
ACTIVATE_SELECTED_GENERATION.fetch_add(1, Ordering::AcqRel);
|
||||
}
|
||||
|
||||
@@ -866,7 +819,7 @@ async fn update_tray_after_activation(generation: u64) {
|
||||
}
|
||||
|
||||
/// Records a backend-made selection so the next core start restores it.
|
||||
pub async fn record_selected_node(group_name: &str, node: &str) -> Result<()> {
|
||||
pub(crate) async fn record_selected_node(group_name: &str, node: &str) -> Result<()> {
|
||||
let group_name = String::from(group_name);
|
||||
let node = String::from(node);
|
||||
let recorded = Config::profiles()
|
||||
@@ -918,7 +871,7 @@ fn remove_selected_node(selected: &mut Vec<PrfSelected>, group_name: &str) -> bo
|
||||
selected.len() != original_len
|
||||
}
|
||||
|
||||
pub async fn forget_selected_node(group_name: &str) -> Result<()> {
|
||||
pub(crate) async fn forget_selected_node(group_name: &str) -> Result<()> {
|
||||
let cleared = Config::profiles()
|
||||
.await
|
||||
.with_data_modify(move |mut profiles| async move {
|
||||
@@ -1150,22 +1103,21 @@ async fn activate_selected_nodes_worker(
|
||||
|
||||
/// Whether an activation may also prune the records it cannot match.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum SelectionRepair {
|
||||
enum SelectionRepair {
|
||||
/// Prune records after a fully loaded profile switch.
|
||||
Prune,
|
||||
/// Preserve records during startup while provider-backed groups may still be absent.
|
||||
KeepRecords,
|
||||
}
|
||||
|
||||
pub fn activate_selected_nodes() -> Result<()> {
|
||||
pub(crate) fn activate_selected_nodes() {
|
||||
// The first-pass signal is for callers that wait; a profile switch does not, and dropping
|
||||
// the receiver simply makes the send a no-op.
|
||||
drop(activate_selected_nodes_with(SelectionRepair::Prune));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Restores available selections before the system proxy points at the core, then retries the rest.
|
||||
pub async fn restore_selected_nodes() {
|
||||
pub(crate) async fn restore_selected_nodes() {
|
||||
let first_pass = activate_selected_nodes_with(SelectionRepair::KeepRecords);
|
||||
if tokio::time::timeout(SELECTED_NODES_FIRST_PASS_BUDGET, first_pass)
|
||||
.await
|
||||
@@ -1199,7 +1151,7 @@ fn activate_selected_nodes_with(repair: SelectionRepair) -> tokio::sync::oneshot
|
||||
let result = async {
|
||||
// A draft may be a profile switch that has not passed validation yet.
|
||||
let profiles = Config::profiles().await.data_arc();
|
||||
let current = profiles.get_current().context("no current profile running")?.clone();
|
||||
let current = profiles.current.clone().context("no current profile running")?;
|
||||
let selected = profiles
|
||||
.get_item(¤t)
|
||||
.context("failed to get current profile")?
|
||||
@@ -1262,6 +1214,43 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn missing_reorder_ids_preserve_ordered_items() -> Result<()> {
|
||||
let mut profiles = IProfiles {
|
||||
current: Some("a".into()),
|
||||
items: Some(vec![
|
||||
PrfItem {
|
||||
uid: Some("a".into()),
|
||||
..PrfItem::default()
|
||||
},
|
||||
PrfItem {
|
||||
uid: Some("b".into()),
|
||||
..PrfItem::default()
|
||||
},
|
||||
PrfItem {
|
||||
uid: Some("c".into()),
|
||||
..PrfItem::default()
|
||||
},
|
||||
]),
|
||||
};
|
||||
let expected = Some(vec![Some("a"), Some("b"), Some("c")]);
|
||||
|
||||
profiles.reorder("missing", "b").await?;
|
||||
let ordered_uids = profiles
|
||||
.items
|
||||
.as_ref()
|
||||
.map(|items| items.iter().map(|item| item.uid.as_deref()).collect::<Vec<_>>());
|
||||
assert_eq!(ordered_uids, expected);
|
||||
|
||||
profiles.reorder("a", "missing").await?;
|
||||
let ordered_uids = profiles
|
||||
.items
|
||||
.as_ref()
|
||||
.map(|items| items.iter().map(|item| item.uid.as_deref()).collect::<Vec<_>>());
|
||||
assert_eq!(ordered_uids, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raising_intervals_only_touches_those_scheduled_too_often() {
|
||||
let mut profiles = IProfiles {
|
||||
@@ -1313,7 +1302,7 @@ mod tests {
|
||||
]),
|
||||
};
|
||||
|
||||
let (should_update, plan) = profiles.plan_delete_item(&"a".into())?;
|
||||
let (should_update, plan) = profiles.plan_delete_item("a")?;
|
||||
|
||||
assert!(should_update);
|
||||
assert_eq!(profiles.current.as_deref(), Some("b"));
|
||||
|
||||
@@ -16,11 +16,6 @@ pub struct IRuntime {
|
||||
}
|
||||
|
||||
impl IRuntime {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn patch_config(&mut self, patch: &Mapping) {
|
||||
let config = if let Some(config) = self.config.as_mut() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::config::Config;
|
||||
use crate::{
|
||||
config::{DEFAULT_PAC, deserialize_encrypted, serialize_encrypted},
|
||||
constants::network,
|
||||
utils::{dirs, help},
|
||||
};
|
||||
use anyhow::Result;
|
||||
@@ -265,7 +266,7 @@ impl IVerge {
|
||||
help::save_yaml(&config_path, &config, Some("# Clash Verge Config")).await?;
|
||||
logging!(info, Type::Config, "配置文件修正完成,需要重新加载配置");
|
||||
|
||||
Self::reload_config_after_fix(config).await?;
|
||||
Self::reload_config_after_fix(config).await;
|
||||
} else {
|
||||
logging!(info, Type::Config, "clash_core配置验证通过: {:?}", config.clash_core);
|
||||
}
|
||||
@@ -273,7 +274,7 @@ impl IVerge {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reload_config_after_fix(updated_config: Self) -> Result<()> {
|
||||
async fn reload_config_after_fix(updated_config: Self) {
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
@@ -286,8 +287,6 @@ impl IVerge {
|
||||
*d = updated_config;
|
||||
});
|
||||
config_draft.apply();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_valid_clash_core(&self) -> String {
|
||||
@@ -357,7 +356,7 @@ impl IVerge {
|
||||
verge_tproxy_port: Some(7896),
|
||||
#[cfg(target_os = "linux")]
|
||||
verge_tproxy_enabled: Some(false),
|
||||
verge_mixed_port: Some(7897),
|
||||
verge_mixed_port: Some(network::ports::DEFAULT_MIXED),
|
||||
verge_socks_port: Some(7898),
|
||||
verge_socks_enabled: Some(false),
|
||||
verge_port: Some(7899),
|
||||
|
||||
@@ -263,7 +263,7 @@ impl CoreManager {
|
||||
crate::config::Config::notify_startup_mixed_port_fallback();
|
||||
return Err(start_error);
|
||||
}
|
||||
match crate::config::Config::retry_startup_mixed_port_fallback().await {
|
||||
match crate::config::Config::resolve_startup_mixed_port().await {
|
||||
Ok(true) => {
|
||||
retries += 1;
|
||||
logging!(
|
||||
|
||||
@@ -153,7 +153,7 @@ impl Timer {
|
||||
// Redundant with first_delay == ZERO, kept as the immediate path; mark_task_running
|
||||
// no-ops whichever of the two arrives second.
|
||||
let cur_timestamp = chrono::Local::now().timestamp();
|
||||
if let Some(items) = Config::profiles().await.data_arc().get_items() {
|
||||
if let Some(items) = Config::profiles().await.data_arc().items.as_ref() {
|
||||
for item in items.iter() {
|
||||
if let Some(option) = item.option.as_ref()
|
||||
&& option.allow_auto_update.unwrap_or(true)
|
||||
@@ -204,7 +204,7 @@ impl Timer {
|
||||
}
|
||||
|
||||
async fn gen_map(&self) -> HashMap<String, TaskSchedule> {
|
||||
if let Some(items) = Config::profiles().await.data_arc().get_items() {
|
||||
if let Some(items) = Config::profiles().await.data_arc().items.as_ref() {
|
||||
return Self::gen_map_from_items(items);
|
||||
}
|
||||
|
||||
@@ -446,7 +446,7 @@ impl Timer {
|
||||
let task_interval = *self.timer_map.read().get(uid)?;
|
||||
let profiles = Config::profiles().await;
|
||||
let profiles_guard = profiles.latest_arc();
|
||||
let items = profiles_guard.get_items()?;
|
||||
let items = profiles_guard.items.as_ref()?;
|
||||
|
||||
let profile = items.iter().find(|item| item.uid.as_deref() == Some(uid))?;
|
||||
let updated = profile.updated.unwrap_or(0) as i64;
|
||||
|
||||
@@ -294,7 +294,7 @@ impl Tray {
|
||||
{
|
||||
let profiles = Config::profiles().await;
|
||||
let profiles = profiles.latest_arc();
|
||||
if let Some(current_profile_uid) = profiles.get_current()
|
||||
if let Some(current_profile_uid) = profiles.current.as_ref()
|
||||
&& let Ok(profile) = profiles.get_item(current_profile_uid)
|
||||
{
|
||||
current_profile_name = match &profile.name {
|
||||
|
||||
@@ -54,21 +54,21 @@ impl AsyncChainItemFrom for Option<ChainItem> {
|
||||
data: ChainType::Merge(help::read_mapping(&path).await.ok()?),
|
||||
}),
|
||||
"rules" => {
|
||||
let seq_map = help::read_seq_map(&path).await.ok()?;
|
||||
let seq_map = help::read_yaml::<SeqMap>(&path).await.ok()?;
|
||||
Some(ChainItem {
|
||||
uid,
|
||||
data: ChainType::Rules(seq_map),
|
||||
})
|
||||
}
|
||||
"proxies" => {
|
||||
let seq_map = help::read_seq_map(&path).await.ok()?;
|
||||
let seq_map = help::read_yaml::<SeqMap>(&path).await.ok()?;
|
||||
Some(ChainItem {
|
||||
uid,
|
||||
data: ChainType::Proxies(seq_map),
|
||||
})
|
||||
}
|
||||
"groups" => {
|
||||
let seq_map = help::read_seq_map(&path).await.ok()?;
|
||||
let seq_map = help::read_yaml::<SeqMap>(&path).await.ok()?;
|
||||
Some(ChainItem {
|
||||
uid,
|
||||
data: ChainType::Groups(seq_map),
|
||||
|
||||
@@ -158,7 +158,7 @@ async fn get_config_values() -> ConfigValues {
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
async fn collect_profile_items(profiles: &IProfiles) -> Result<ProfileItems> {
|
||||
let current_profile_uid = match profiles.get_current().cloned() {
|
||||
let current_profile_uid = match profiles.current.clone() {
|
||||
Some(uid) => uid,
|
||||
None => return Ok(ProfileItems::default()),
|
||||
};
|
||||
|
||||
@@ -185,13 +185,13 @@ async fn resolve_latest_version(alpha: bool) -> Result<(ProxyType, std::string::
|
||||
|
||||
for proxy in [ProxyType::Localhost, ProxyType::System, ProxyType::None] {
|
||||
let attempt = NetworkManager::new()
|
||||
.get_with_interrupt(&url, proxy, Some(VERSION_TIMEOUT_SECS), None, false)
|
||||
.get(&url, proxy, Some(VERSION_TIMEOUT_SECS), None, false)
|
||||
.await;
|
||||
|
||||
match attempt {
|
||||
Ok(response) if response.status().is_success() => {
|
||||
// An error page answered with 200 must not become a version, nor reach the URL.
|
||||
let version = response.text_with_charset()?.trim().to_owned();
|
||||
let version = response.text().trim().to_owned();
|
||||
if !is_usable_version(&version) {
|
||||
last_error = Some(anyhow!("{url} returned an unusable version"));
|
||||
continue;
|
||||
@@ -220,7 +220,7 @@ async fn download_package(proxy: ProxyType, alpha: bool, version: &str) -> Resul
|
||||
logging!(info, Type::Core, "core upgrade: downloading {url}");
|
||||
|
||||
NetworkManager::new()
|
||||
.get_bytes_with_interrupt(&url, proxy, Some(PACKAGE_TIMEOUT_SECS), MAX_PACKAGE_BYTES)
|
||||
.get_bytes(&url, proxy, Some(PACKAGE_TIMEOUT_SECS), MAX_PACKAGE_BYTES)
|
||||
.await
|
||||
.with_context(|| format!("failed to download {url}"))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
cmd,
|
||||
config::{Config, PrfItem, PrfOption, profiles::profiles_draft_update_item_safe},
|
||||
config::{Config, PrfItem, PrfOption, profiles::profiles_update_item_safe},
|
||||
core::{CoreManager, handle, tray, validate::ValidationOutcome},
|
||||
utils::help::{mask_err, mask_url},
|
||||
};
|
||||
@@ -124,13 +124,14 @@ async fn perform_profile_update(
|
||||
let mut merged_opt = PrfOption::merge(opt, option);
|
||||
let is_current = {
|
||||
let profiles = Config::profiles().await;
|
||||
profiles.latest_arc().is_current_profile_index(uid)
|
||||
profiles.latest_arc().current.as_ref() == Some(uid)
|
||||
};
|
||||
let profiles = Config::profiles().await;
|
||||
let profiles_arc = profiles.latest_arc();
|
||||
let profile_name = profiles_arc
|
||||
.get_name_by_uid(uid)
|
||||
.cloned()
|
||||
.get_item(uid)
|
||||
.ok()
|
||||
.and_then(|item| item.name.clone())
|
||||
.unwrap_or_else(|| String::from("UnKnown Profile"));
|
||||
|
||||
let mut last_err;
|
||||
@@ -138,7 +139,7 @@ async fn perform_profile_update(
|
||||
match PrfItem::from_url(url, None, None, merged_opt.as_ref()).await {
|
||||
Ok(mut item) => {
|
||||
logging!(info, Type::Config, "[订阅更新] 更新订阅配置成功");
|
||||
profiles_draft_update_item_safe(uid, &mut item).await?;
|
||||
profiles_update_item_safe(uid, &mut item).await?;
|
||||
return Ok(is_current);
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -158,7 +159,7 @@ async fn perform_profile_update(
|
||||
match PrfItem::from_url(url, None, None, merged_opt.as_ref()).await {
|
||||
Ok(mut item) => {
|
||||
logging!(info, Type::Config, "[订阅更新] 使用 Clash代理 更新订阅配置成功");
|
||||
profiles_draft_update_item_safe(uid, &mut item).await?;
|
||||
profiles_update_item_safe(uid, &mut item).await?;
|
||||
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
|
||||
drop(last_err);
|
||||
return Ok(is_current);
|
||||
@@ -180,7 +181,7 @@ async fn perform_profile_update(
|
||||
match PrfItem::from_url(url, None, None, merged_opt.as_ref()).await {
|
||||
Ok(mut item) => {
|
||||
logging!(info, Type::Config, "[订阅更新] 使用 系统代理 更新订阅配置成功");
|
||||
profiles_draft_update_item_safe(uid, &mut item).await?;
|
||||
profiles_update_item_safe(uid, &mut item).await?;
|
||||
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
|
||||
drop(last_err);
|
||||
return Ok(is_current);
|
||||
|
||||
@@ -80,10 +80,8 @@ mod app_init {
|
||||
app.deep_link().on_open_url(|event| {
|
||||
let urls = event.urls();
|
||||
AsyncHandler::spawn(move || async move {
|
||||
if let Some(url) = urls.first()
|
||||
&& let Err(e) = resolve::resolve_scheme(url.as_ref()).await
|
||||
{
|
||||
logging!(error, Type::Setup, "Failed to resolve scheme: {}", e);
|
||||
if let Some(url) = urls.first() {
|
||||
resolve::resolve_scheme(url.as_ref()).await;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -169,7 +167,6 @@ mod app_init {
|
||||
cmd::forget_selected_node,
|
||||
cmd::save_dns_config,
|
||||
cmd::apply_dns_config,
|
||||
cmd::check_dns_config_exists,
|
||||
cmd::get_dns_config_content,
|
||||
cmd::validate_dns_config,
|
||||
cmd::get_clash_logs,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -467,13 +467,12 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
|
||||
const initDnsConfig = useCallback(async () => {
|
||||
try {
|
||||
const dnsConfigExists = await invoke<boolean>(
|
||||
'check_dns_config_exists',
|
||||
const dnsConfig = await invoke<string | null>(
|
||||
'get_dns_config_content',
|
||||
{},
|
||||
)
|
||||
|
||||
if (dnsConfigExists) {
|
||||
const dnsConfig = await invoke<string>('get_dns_config_content', {})
|
||||
if (dnsConfig !== null) {
|
||||
const config = yaml.load(dnsConfig) as any
|
||||
|
||||
updateValuesFromConfig(config)
|
||||
|
||||
Reference in New Issue
Block a user