refactor: wip

This commit is contained in:
GyDi 2022-11-17 17:07:13 +08:00
parent abdbf158d1
commit 63b474a32c
No known key found for this signature in database
GPG Key ID: 9C3AD40F1F99880A
21 changed files with 324 additions and 1498 deletions

View File

@ -14,8 +14,7 @@ type CmdResult<T = ()> = Result<T, String>;
#[tauri::command]
pub fn get_profiles() -> CmdResult<IProfiles> {
let profiles = ProfilesN::global().config.lock();
Ok(profiles.clone())
Ok(Config::profiles().data().clone())
}
#[tauri::command]
@ -27,47 +26,80 @@ pub async fn enhance_profiles() -> CmdResult {
#[tauri::command]
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
let mut profiles = ProfilesN::global().config.lock();
wrap_err!(profiles.append_item(item))
wrap_err!(Config::profiles().data().append_item(item))
}
#[tauri::command]
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
let mut profiles = ProfilesN::global().config.lock();
wrap_err!(profiles.append_item(item))
wrap_err!(Config::profiles().data().append_item(item))
}
#[tauri::command]
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
wrap_err!(ProfilesN::global().update_item(index, option).await)
wrap_err!(feat::update_profile(index, option).await)
}
#[tauri::command]
pub async fn select_profile(index: String) -> CmdResult {
wrap_err!({ ProfilesN::global().config.lock().put_current(index) })?;
wrap_err!(CoreManager::global().activate_config().await)
wrap_err!({ Config::profiles().draft().put_current(index) })?;
match feat::handle_activate().await {
Ok(_) => {
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
Ok(())
}
Err(err) => {
Config::profiles().discard();
log::error!(target: "app", "{err}");
Err(format!("{err}"))
}
}
}
/// change the profile chain
#[tauri::command]
pub async fn change_profile_chain(chain: Option<Vec<String>>) -> CmdResult {
wrap_err!({ ProfilesN::global().config.lock().put_chain(chain) })?;
wrap_err!(CoreManager::global().activate_config().await)
wrap_err!({ Config::profiles().draft().put_chain(chain) })?;
match feat::handle_activate().await {
Ok(_) => {
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
Ok(())
}
Err(err) => {
Config::profiles().discard();
log::error!(target: "app", "{err}");
Err(format!("{err}"))
}
}
}
#[tauri::command]
pub async fn change_profile_valid(valid: Option<Vec<String>>) -> CmdResult {
wrap_err!({ ProfilesN::global().config.lock().put_valid(valid) })?;
wrap_err!(CoreManager::global().activate_config().await)
wrap_err!({ Config::profiles().draft().put_valid(valid) })?;
match feat::handle_activate().await {
Ok(_) => {
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
Ok(())
}
Err(err) => {
Config::profiles().discard();
log::error!(target: "app", "{err}");
Err(format!("{err}"))
}
}
}
#[tauri::command]
pub async fn delete_profile(index: String) -> CmdResult {
let should_update = { wrap_err!(ProfilesN::global().config.lock().delete_item(index))? };
let should_update = wrap_err!({ Config::profiles().data().delete_item(index) })?;
if should_update {
wrap_err!(CoreManager::global().activate_config().await)?;
wrap_err!(feat::handle_activate().await)?;
}
Ok(())
@ -75,19 +107,20 @@ pub async fn delete_profile(index: String) -> CmdResult {
#[tauri::command]
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
let mut profiles = ProfilesN::global().config.lock();
wrap_err!(profiles.patch_item(index, profile))?;
drop(profiles);
wrap_err!(Config::profiles().data().patch_item(index, profile))?;
wrap_err!(timer::Timer::global().refresh())
}
#[tauri::command]
pub fn view_profile(index: String) -> CmdResult {
let profiles = ProfilesN::global().config.lock();
let item = wrap_err!(profiles.get_item(&index))?;
let file = {
wrap_err!(Config::profiles().latest().get_item(&index))?
.file
.clone()
.ok_or("the file field is null")
}?;
let file = item.file.clone().ok_or("the file field is null")?;
let path = dirs::app_profiles_dir().join(file);
if !path.exists() {
ret_err!("the file not found");
@ -98,7 +131,8 @@ pub fn view_profile(index: String) -> CmdResult {
#[tauri::command]
pub fn read_profile_file(index: String) -> CmdResult<String> {
let profiles = ProfilesN::global().config.lock();
let profiles = Config::profiles();
let profiles = profiles.latest();
let item = wrap_err!(profiles.get_item(&index))?;
let data = wrap_err!(item.read_file())?;
Ok(data)
@ -110,14 +144,15 @@ pub fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult
return Ok(());
}
let profiles = ProfilesN::global().config.lock();
let profiles = Config::profiles();
let profiles = profiles.latest();
let item = wrap_err!(profiles.get_item(&index))?;
wrap_err!(item.save_file(file_data.unwrap()))
}
#[tauri::command]
pub fn get_clash_info() -> CmdResult<ClashInfoN> {
Ok(ClashN::global().info.lock().clone())
wrap_err!(Config::clash().latest().get_info())
}
#[tauri::command]
@ -153,18 +188,18 @@ pub fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
}
#[tauri::command]
pub fn patch_clash_config(payload: Mapping) -> CmdResult {
wrap_err!(feat::patch_clash(payload))
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
wrap_err!(feat::patch_clash(payload).await)
}
#[tauri::command]
pub fn get_verge_config() -> CmdResult<IVerge> {
Ok(VergeN::global().config.lock().clone())
Ok(Config::verge().data().clone())
}
#[tauri::command]
pub fn patch_verge_config(payload: IVerge) -> CmdResult {
wrap_err!(feat::patch_verge(payload))
pub async fn patch_verge_config(payload: IVerge) -> CmdResult {
wrap_err!(feat::patch_verge(payload).await)
}
#[tauri::command]

View File

@ -1,10 +1,7 @@
use crate::utils::{config, dirs};
use anyhow::Result;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Value};
use std::{net::SocketAddr, sync::Arc};
#[derive(Default, Debug, Clone)]
pub struct IClashTemp(pub Mapping);
@ -33,69 +30,6 @@ impl IClashTemp {
}
}
#[derive(Debug)]
#[deprecated]
pub struct ClashN {
/// maintain the clash config
pub config: Arc<Mutex<Mapping>>,
/// some info
pub info: Arc<Mutex<ClashInfoN>>,
}
impl ClashN {
pub fn global() -> &'static ClashN {
static DATA: OnceCell<ClashN> = OnceCell::new();
DATA.get_or_init(|| {
let config = ClashN::read_config();
let info = ClashInfoN::from(&config);
ClashN {
config: Arc::new(Mutex::new(config)),
info: Arc::new(Mutex::new(info)),
}
})
}
/// get clash config
pub fn read_config() -> Mapping {
config::read_merge_mapping(dirs::clash_path())
}
/// save the clash config
pub fn save_config(&self) -> Result<()> {
let config = self.config.lock();
config::save_yaml(
dirs::clash_path(),
&*config,
Some("# Default Config For ClashN Core\n\n"),
)
}
/// 返回旧值
pub fn patch_info(&self, info: ClashInfoN) -> Result<ClashInfoN> {
let mut old_info = self.info.lock();
let old = (*old_info).to_owned();
*old_info = info;
Ok(old)
}
/// patch update the clash config
/// if the port is changed then return true
pub fn patch_config(&self, patch: Mapping) -> Result<()> {
let mut config = self.config.lock();
for (key, value) in patch.into_iter() {
config.insert(key, value);
}
drop(config);
self.save_config()
}
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct ClashInfoN {
/// clash sidecar status
@ -245,21 +179,3 @@ pub struct IClashFallbackFilter {
pub ipcidr: Option<Vec<String>>,
pub domain: Option<Vec<String>>,
}
#[test]
fn test() {
let socket = SocketAddr::new("127.0.0.1".parse().unwrap(), 9090);
let s = "[::]:8080".parse::<SocketAddr>();
dbg!(s);
// match "::8080".parse::<SocketAddr>() {
// Ok(_) => {}
// Err(err) => {
// }
// }
// assert_eq!(":8080".parse(), Ok(socket));
}

View File

@ -1,7 +1,5 @@
use super::{Draft, IClashTemp, IProfiles, IVerge};
use crate::config::ClashN;
use once_cell::sync::OnceCell;
use serde_yaml::Mapping;
pub struct Config {
clash_config: Draft<IClashTemp>,
@ -20,18 +18,6 @@ impl Config {
})
}
// pub fn clash<'a>() -> MappedMutexGuard<'a, IClash> {
// Self::global().clash_config.latest()
// }
// pub fn verge<'a>() -> MappedMutexGuard<'a, IVerge> {
// Self::global().verge_config.latest()
// }
// pub fn profiles<'a>() -> MappedMutexGuard<'a, IProfiles> {
// Self::global().profiles_config.latest()
// }
pub fn clash() -> Draft<IClashTemp> {
Self::global().clash_config.clone()
}

View File

@ -1,6 +1,5 @@
use super::{IClash, IClashTemp, IProfiles, IVerge};
use super::{IClashTemp, IProfiles, IVerge};
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
use serde_yaml::Mapping;
use std::sync::Arc;
#[derive(Debug, Clone)]
@ -64,10 +63,10 @@ macro_rules! draft_define {
};
}
draft_define!(IClash);
// draft_define!(IClash);
draft_define!(IClashTemp);
draft_define!(IVerge);
draft_define!(Mapping);
// draft_define!(Mapping);
draft_define!(IProfiles);
#[test]

View File

@ -6,6 +6,8 @@ use serde_yaml::Mapping;
use std::fs;
use sysproxy::Sysproxy;
use super::Config;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PrfItem {
pub uid: Option<String>,
@ -192,9 +194,9 @@ impl PrfItem {
// 使用软件自己的代理
if self_proxy {
let clash = super::ClashN::global();
let port = clash.info.lock().port.clone();
let port = Config::clash().data().get_info()?.port;
let port = port.ok_or(anyhow::anyhow!("failed to get clash info port"))?;
let proxy_scheme = format!("http://127.0.0.1:{port}");
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {

View File

@ -1,82 +1,25 @@
use super::{prfitem::PrfItem, ChainItem, PrfOption};
use crate::{
core::CoreManager,
utils::{config, dirs, help},
};
use super::{prfitem::PrfItem, ChainItem};
use crate::utils::{config, dirs, help};
use anyhow::{bail, Context, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::collections::HashMap;
use std::sync::Arc;
use std::{fs, io::Write};
#[deprecated]
pub struct ProfilesN {
pub config: Arc<Mutex<IProfiles>>,
}
impl ProfilesN {
pub fn global() -> &'static ProfilesN {
static PROFILES: OnceCell<ProfilesN> = OnceCell::new();
PROFILES.get_or_init(|| ProfilesN {
config: Arc::new(Mutex::new(IProfiles::read_file())),
})
}
/// 更新单个配置
pub async fn update_item(&self, uid: String, option: Option<PrfOption>) -> Result<()> {
let url_opt = {
let profiles = self.config.lock();
let item = profiles.get_item(&uid)?;
let is_remote = item.itype.as_ref().map_or(false, |s| s == "remote");
if !is_remote {
None // 直接更新
} else if item.url.is_none() {
bail!("failed to get the profile item url");
} else {
Some((item.url.clone().unwrap(), item.option.clone()))
}
};
let should_update = match url_opt {
Some((url, opt)) => {
let merged_opt = PrfOption::merge(opt, option);
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
let mut profiles = self.config.lock();
profiles.update_item(uid.clone(), item)?;
Some(uid) == profiles.get_current()
}
None => true,
};
if should_update {
CoreManager::global().activate_config().await?;
}
Ok(())
}
}
/// Define the `profiles.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct IProfiles {
/// same as PrfConfig.current
current: Option<String>,
pub current: Option<String>,
/// same as PrfConfig.chain
chain: Option<Vec<String>>,
pub chain: Option<Vec<String>>,
/// record valid fields for clash
valid: Option<Vec<String>>,
pub valid: Option<Vec<String>>,
/// profile list
items: Option<Vec<PrfItem>>,
pub items: Option<Vec<PrfItem>>,
}
macro_rules! patch {
@ -134,7 +77,7 @@ impl IProfiles {
if items.iter().find(|&each| each.uid == some_uid).is_some() {
self.current = some_uid;
return self.save_file();
return self.save_file(); // todo remove
}
bail!("invalid uid \"{uid}\"");

View File

@ -1,53 +1,6 @@
use crate::utils::{config, dirs};
use anyhow::Result;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[deprecated]
pub struct VergeN {
pub config: Arc<Mutex<IVerge>>,
}
impl VergeN {
pub fn global() -> &'static VergeN {
static DATA: OnceCell<VergeN> = OnceCell::new();
DATA.get_or_init(|| {
let config = config::read_yaml::<IVerge>(dirs::verge_path());
VergeN {
config: Arc::new(Mutex::new(config)),
}
})
}
/// Save IVerge App Config
pub fn save_file(&self) -> Result<()> {
self.config.lock().save_file()
}
/// patch verge config
/// only save to file
pub fn patch_config(&self, patch: IVerge) -> Result<()> {
{
self.config.lock().patch_config(patch);
}
self.save_file()
}
/// 在初始化前尝试拿到单例端口的值
pub fn get_singleton_port() -> u16 {
let config = config::read_yaml::<IVerge>(dirs::verge_path());
#[cfg(not(feature = "verge-dev"))]
const SERVER_PORT: u16 = 33331;
#[cfg(feature = "verge-dev")]
const SERVER_PORT: u16 = 11233;
config.app_singleton_port.unwrap_or(SERVER_PORT)
}
}
/// ### `verge.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
@ -177,4 +130,16 @@ impl IVerge {
patch!(auto_close_connection);
patch!(default_latency_test);
}
/// 在初始化前尝试拿到单例端口的值
pub fn get_singleton_port() -> u16 {
let config = config::read_yaml::<IVerge>(dirs::verge_path());
#[cfg(not(feature = "verge-dev"))]
const SERVER_PORT: u16 = 33331;
#[cfg(feature = "verge-dev")]
const SERVER_PORT: u16 = 11233;
config.app_singleton_port.unwrap_or(SERVER_PORT)
}
}

View File

@ -1,4 +1,4 @@
use crate::{config, utils::dirs};
use crate::{config::Config, utils::dirs};
use anyhow::{bail, Result};
use reqwest::header::HeaderMap;
use serde_yaml::Mapping;
@ -40,7 +40,7 @@ pub async fn patch_configs(config: &Mapping) -> Result<()> {
/// 根据clash info获取clash服务地址和请求头
fn clash_client_info() -> Result<(String, HeaderMap)> {
let info = { config::ClashN::global().info.lock().clone() };
let info = { Config::clash().data().get_info()? };
if info.server.is_none() {
let status = &info.status;

View File

@ -64,12 +64,12 @@ impl Hotkey {
"clash_mode_global" => || feat::change_clash_mode("global".into()),
"clash_mode_direct" => || feat::change_clash_mode("direct".into()),
"clash_mode_script" => || feat::change_clash_mode("script".into()),
"toggle_system_proxy" => || log_err!(feat::toggle_system_proxy()),
"enable_system_proxy" => || log_err!(feat::enable_system_proxy()),
"disable_system_proxy" => || log_err!(feat::disable_system_proxy()),
"toggle_tun_mode" => || log_err!(feat::toggle_tun_mode()),
"enable_tun_mode" => || log_err!(feat::enable_tun_mode()),
"disable_tun_mode" => || log_err!(feat::disable_tun_mode()),
"toggle_system_proxy" => || feat::toggle_system_proxy(),
"enable_system_proxy" => || feat::enable_system_proxy(),
"disable_system_proxy" => || feat::disable_system_proxy(),
"toggle_tun_mode" => || feat::toggle_tun_mode(),
"enable_tun_mode" => || feat::enable_tun_mode(),
"disable_tun_mode" => || feat::disable_tun_mode(),
_ => bail!("invalid function \"{func}\""),
};

View File

@ -144,13 +144,8 @@ impl Sysopt {
/// init the auto launch
pub fn init_launch(&self) -> Result<()> {
let enable = {
Config::verge()
.latest()
.enable_auto_launch
.clone()
.unwrap_or(false)
};
let enable = { Config::verge().latest().enable_auto_launch.clone() };
let enable = enable.unwrap_or(false);
let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?;
@ -213,13 +208,8 @@ impl Sysopt {
drop(auto_launch);
return self.init_launch();
}
let enable = {
Config::verge()
.latest()
.enable_auto_launch
.clone()
.unwrap_or(false)
};
let enable = { Config::verge().latest().enable_auto_launch.clone() };
let enable = enable.unwrap_or(false);
let auto_launch = auto_launch.as_ref().unwrap();
match enable {

View File

@ -1,4 +1,5 @@
use crate::config::{self, ProfilesN};
use crate::config::Config;
use crate::feat;
use anyhow::{Context, Result};
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
use once_cell::sync::OnceCell;
@ -36,12 +37,10 @@ impl Timer {
let cur_timestamp = chrono::Local::now().timestamp();
let profiles = config::ProfilesN::global().config.lock();
let timer_map = self.timer_map.lock();
let delay_timer = self.delay_timer.lock();
profiles.get_items().map(|items| {
Config::profiles().latest().get_items().map(|items| {
items
.iter()
// .filter_map(|item| {
@ -100,11 +99,9 @@ impl Timer {
/// generate a uid -> update_interval map
fn gen_map(&self) -> HashMap<String, u64> {
let profiles = config::ProfilesN::global().config.lock();
let mut new_map = HashMap::new();
if let Some(items) = profiles.get_items() {
if let Some(items) = Config::profiles().latest().get_items() {
for item in items.iter() {
if item.option.is_some() {
let option = item.option.as_ref().unwrap();
@ -178,7 +175,7 @@ impl Timer {
/// the task runner
async fn async_task(uid: String) {
log::info!(target: "app", "running timer task `{uid}`");
crate::log_err!(ProfilesN::global().update_item(uid, None).await);
crate::log_err!(feat::update_profile(uid, None).await);
}
}

View File

@ -1,4 +1,3 @@
use crate::log_err;
use crate::{config::Config, feat, utils::resolve};
use anyhow::Result;
use tauri::{
@ -109,8 +108,8 @@ impl Tray {
}
"open_window" => resolve::create_window(app_handle),
"system_proxy" => log_err!(feat::toggle_system_proxy()),
"tun_mode" => log_err!(feat::toggle_tun_mode()),
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(),
"restart_clash" => feat::restart_clash_core(),
"restart_app" => api::process::restart(&app_handle.env()),
"quit" => {

View File

@ -1,167 +0,0 @@
use crate::utils::{config, dirs};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Value};
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct ClashInfo {
/// clash sidecar status
pub status: String,
/// clash core port
pub port: Option<String>,
/// same as `external-controller`
pub server: Option<String>,
/// clash secret
pub secret: Option<String>,
}
impl ClashInfo {
/// parse the clash's config.yaml
/// get some information
pub fn from(config: &Mapping) -> ClashInfo {
let key_port_1 = Value::from("mixed-port");
let key_port_2 = Value::from("port");
let key_server = Value::from("external-controller");
let key_secret = Value::from("secret");
let mut status: u32 = 0;
let port = match config.get(&key_port_1) {
Some(value) => match value {
Value::String(val_str) => Some(val_str.clone()),
Value::Number(val_num) => Some(val_num.to_string()),
_ => {
status |= 0b1;
None
}
},
_ => {
status |= 0b10;
None
}
};
let port = match port {
Some(_) => port,
None => match config.get(&key_port_2) {
Some(value) => match value {
Value::String(val_str) => Some(val_str.clone()),
Value::Number(val_num) => Some(val_num.to_string()),
_ => {
status |= 0b100;
None
}
},
_ => {
status |= 0b1000;
None
}
},
};
// `external-controller` could be
// "127.0.0.1:9090" or ":9090"
let server = match config.get(&key_server) {
Some(value) => match value.as_str() {
Some(val_str) => {
if val_str.starts_with(":") {
Some(format!("127.0.0.1{val_str}"))
} else if val_str.starts_with("0.0.0.0:") {
Some(format!("127.0.0.1:{}", &val_str[8..]))
} else if val_str.starts_with("[::]:") {
Some(format!("127.0.0.1:{}", &val_str[5..]))
} else {
Some(val_str.into())
}
}
None => {
status |= 0b10000;
None
}
},
None => {
status |= 0b100000;
None
}
};
let secret = match config.get(&key_secret) {
Some(value) => match value {
Value::String(val_str) => Some(val_str.clone()),
Value::Bool(val_bool) => Some(val_bool.to_string()),
Value::Number(val_num) => Some(val_num.to_string()),
_ => None,
},
_ => None,
};
ClashInfo {
status: format!("{status}"),
port,
server,
secret,
}
}
}
#[derive(Debug)]
pub struct Clash {
/// maintain the clash config
pub config: Mapping,
/// some info
pub info: ClashInfo,
}
impl Clash {
pub fn new() -> Clash {
let config = Clash::read_config();
let info = ClashInfo::from(&config);
Clash { config, info }
}
/// get clash config
pub fn read_config() -> Mapping {
config::read_merge_mapping(dirs::clash_path())
}
/// save the clash config
pub fn save_config(&self) -> Result<()> {
config::save_yaml(
dirs::clash_path(),
&self.config,
Some("# Default Config For Clash Core\n\n"),
)
}
/// patch update the clash config
/// if the port is changed then return true
pub fn patch_config(&mut self, patch: Mapping) -> Result<()> {
let port_key = Value::from("mixed-port");
let server_key = Value::from("external-controller");
let secret_key = Value::from("secret");
let change_info = patch.contains_key(&port_key)
|| patch.contains_key(&server_key)
|| patch.contains_key(&secret_key);
for (key, value) in patch.into_iter() {
self.config.insert(key, value);
}
if change_info {
self.info = ClashInfo::from(&self.config);
}
self.save_config()
}
}
impl Default for Clash {
fn default() -> Self {
Clash::new()
}
}

View File

@ -1,32 +0,0 @@
mod clash;
mod prfitem;
mod profiles;
mod verge;
pub use self::clash::*;
pub use self::prfitem::*;
pub use self::profiles::*;
pub use self::verge::*;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Data {
pub clash: Arc<Mutex<Clash>>,
pub verge: Arc<Mutex<Verge>>,
pub profiles: Arc<Mutex<Profiles>>,
}
impl Data {
pub fn global() -> &'static Data {
static DATA: OnceCell<Data> = OnceCell::new();
DATA.get_or_init(|| Data {
clash: Arc::new(Mutex::new(Clash::new())),
verge: Arc::new(Mutex::new(Verge::new())),
profiles: Arc::new(Mutex::new(Profiles::new())),
})
}
}

View File

@ -1,406 +0,0 @@
use crate::utils::{config, dirs, help, tmpl};
use anyhow::{bail, Context, Result};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::fs;
use sysproxy::Sysproxy;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PrfItem {
pub uid: Option<String>,
/// profile item type
/// enum value: remote | local | script | merge
#[serde(rename = "type")]
pub itype: Option<String>,
/// profile name
pub name: Option<String>,
/// profile file
pub file: Option<String>,
/// profile description
#[serde(skip_serializing_if = "Option::is_none")]
pub desc: Option<String>,
/// source url
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
/// selected infomation
#[serde(skip_serializing_if = "Option::is_none")]
pub selected: Option<Vec<PrfSelected>>,
/// subscription user info
#[serde(skip_serializing_if = "Option::is_none")]
pub extra: Option<PrfExtra>,
/// updated time
pub updated: Option<usize>,
/// some options of the item
#[serde(skip_serializing_if = "Option::is_none")]
pub option: Option<PrfOption>,
/// the file data
#[serde(skip)]
pub file_data: Option<String>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct PrfSelected {
pub name: Option<String>,
pub now: Option<String>,
}
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
pub struct PrfExtra {
pub upload: usize,
pub download: usize,
pub total: usize,
pub expire: usize,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct PrfOption {
/// for `remote` profile's http request
/// see issue #13
#[serde(skip_serializing_if = "Option::is_none")]
pub user_agent: Option<String>,
/// for `remote` profile
/// use system proxy
#[serde(skip_serializing_if = "Option::is_none")]
pub with_proxy: Option<bool>,
/// for `remote` profile
/// use self proxy
#[serde(skip_serializing_if = "Option::is_none")]
pub self_proxy: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub update_interval: Option<u64>,
}
impl PrfOption {
pub fn merge(one: Option<Self>, other: Option<Self>) -> Option<Self> {
match (one, other) {
(Some(mut a), Some(b)) => {
a.user_agent = b.user_agent.or(a.user_agent);
a.with_proxy = b.with_proxy.or(a.with_proxy);
a.self_proxy = b.self_proxy.or(a.self_proxy);
a.update_interval = b.update_interval.or(a.update_interval);
Some(a)
}
t @ _ => t.0.or(t.1),
}
}
}
impl Default for PrfItem {
fn default() -> Self {
PrfItem {
uid: None,
itype: None,
name: None,
desc: None,
file: None,
url: None,
selected: None,
extra: None,
updated: None,
option: None,
file_data: None,
}
}
}
impl PrfItem {
/// From partial item
/// must contain `itype`
pub async fn from(item: PrfItem, file_data: Option<String>) -> Result<PrfItem> {
if item.itype.is_none() {
bail!("type should not be null");
}
match item.itype.unwrap().as_str() {
"remote" => {
if item.url.is_none() {
bail!("url should not be null");
}
let url = item.url.as_ref().unwrap().as_str();
let name = item.name;
let desc = item.desc;
PrfItem::from_url(url, name, desc, item.option).await
}
"local" => {
let name = item.name.unwrap_or("Local File".into());
let desc = item.desc.unwrap_or("".into());
PrfItem::from_local(name, desc, file_data)
}
"merge" => {
let name = item.name.unwrap_or("Merge".into());
let desc = item.desc.unwrap_or("".into());
PrfItem::from_merge(name, desc)
}
"script" => {
let name = item.name.unwrap_or("Script".into());
let desc = item.desc.unwrap_or("".into());
PrfItem::from_script(name, desc)
}
typ @ _ => bail!("invalid profile item type \"{typ}\""),
}
}
/// ## Local type
/// create a new item from name/desc
pub fn from_local(name: String, desc: String, file_data: Option<String>) -> Result<PrfItem> {
let uid = help::get_uid("l");
let file = format!("{uid}.yaml");
Ok(PrfItem {
uid: Some(uid),
itype: Some("local".into()),
name: Some(name),
desc: Some(desc),
file: Some(file),
url: None,
selected: None,
extra: None,
option: None,
updated: Some(help::get_now()),
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
})
}
/// ## Remote type
/// create a new item from url
pub async fn from_url(
url: &str,
name: Option<String>,
desc: Option<String>,
option: Option<PrfOption>,
) -> Result<PrfItem> {
let opt_ref = option.as_ref();
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false));
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false));
let user_agent = opt_ref.map_or(None, |o| o.user_agent.clone());
let mut builder = reqwest::ClientBuilder::new().no_proxy();
// 使用软件自己的代理
if self_proxy {
let data = super::Data::global();
let port = data.clash.lock().info.port.clone();
let port = port.ok_or(anyhow::anyhow!("failed to get clash info port"))?;
let proxy_scheme = format!("http://127.0.0.1:{port}");
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
}
// 使用系统代理
else if with_proxy {
match Sysproxy::get_system_proxy() {
Ok(p @ Sysproxy { enable: true, .. }) => {
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
}
_ => {}
};
}
let version = unsafe { dirs::APP_VERSION };
let version = format!("clash-verge/{version}");
builder = builder.user_agent(user_agent.unwrap_or(version));
let resp = builder.build()?.get(url).send().await?;
let status_code = resp.status();
if !StatusCode::is_success(&status_code) {
bail!("failed to fetch remote profile with status {status_code}")
}
let header = resp.headers();
// parse the Subscription UserInfo
let extra = match header.get("Subscription-Userinfo") {
Some(value) => {
let sub_info = value.to_str().unwrap_or("");
Some(PrfExtra {
upload: help::parse_str(sub_info, "upload=").unwrap_or(0),
download: help::parse_str(sub_info, "download=").unwrap_or(0),
total: help::parse_str(sub_info, "total=").unwrap_or(0),
expire: help::parse_str(sub_info, "expire=").unwrap_or(0),
})
}
None => None,
};
// parse the Content-Disposition
let filename = match header.get("Content-Disposition") {
Some(value) => {
let filename = value.to_str().unwrap_or("");
help::parse_str::<String>(filename, "filename=")
}
None => None,
};
// parse the profile-update-interval
let option = match header.get("profile-update-interval") {
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
Ok(val) => Some(PrfOption {
update_interval: Some(val * 60), // hour -> min
..PrfOption::default()
}),
Err(_) => None,
},
None => None,
};
let uid = help::get_uid("r");
let file = format!("{uid}.yaml");
let name = name.unwrap_or(filename.unwrap_or("Remote File".into()));
let data = resp.text_with_charset("utf-8").await?;
// check the data whether the valid yaml format
let yaml = serde_yaml::from_str::<Mapping>(&data) //
.context("the remote profile data is invalid yaml")?;
if !yaml.contains_key("proxies") && !yaml.contains_key("proxy-providers") {
bail!("profile does not contain `proxies` or `proxy-providers`");
}
Ok(PrfItem {
uid: Some(uid),
itype: Some("remote".into()),
name: Some(name),
desc,
file: Some(file),
url: Some(url.into()),
selected: None,
extra,
option,
updated: Some(help::get_now()),
file_data: Some(data),
})
}
/// ## Merge type (enhance)
/// create the enhanced item by using `merge` rule
pub fn from_merge(name: String, desc: String) -> Result<PrfItem> {
let uid = help::get_uid("m");
let file = format!("{uid}.yaml");
Ok(PrfItem {
uid: Some(uid),
itype: Some("merge".into()),
name: Some(name),
desc: Some(desc),
file: Some(file),
url: None,
selected: None,
extra: None,
option: None,
updated: Some(help::get_now()),
file_data: Some(tmpl::ITEM_MERGE.into()),
})
}
/// ## Script type (enhance)
/// create the enhanced item by using javascript(browserjs)
pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
let uid = help::get_uid("s");
let file = format!("{uid}.js"); // js ext
Ok(PrfItem {
uid: Some(uid),
itype: Some("script".into()),
name: Some(name),
desc: Some(desc),
file: Some(file),
url: None,
selected: None,
extra: None,
option: None,
updated: Some(help::get_now()),
file_data: Some(tmpl::ITEM_SCRIPT.into()),
})
}
/// get the file data
pub fn read_file(&self) -> Result<String> {
if self.file.is_none() {
bail!("could not find the file");
}
let file = self.file.clone().unwrap();
let path = dirs::app_profiles_dir().join(file);
fs::read_to_string(path).context("failed to read the file")
}
/// save the file data
pub fn save_file(&self, data: String) -> Result<()> {
if self.file.is_none() {
bail!("could not find the file");
}
let file = self.file.clone().unwrap();
let path = dirs::app_profiles_dir().join(file);
fs::write(path, data.as_bytes()).context("failed to save the file")
}
/// get the data for enhanced mode
pub fn to_enhance(&self) -> Option<ChainItem> {
let itype = self.itype.as_ref()?.as_str();
let file = self.file.clone()?;
let uid = self.uid.clone().unwrap_or("".into());
let path = dirs::app_profiles_dir().join(file);
if !path.exists() {
return None;
}
match itype {
"script" => Some(ChainItem {
uid,
data: ChainType::Script(fs::read_to_string(path).unwrap_or("".into())),
}),
"merge" => Some(ChainItem {
uid,
data: ChainType::Merge(config::read_merge_mapping(path)),
}),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct ChainItem {
pub uid: String,
pub data: ChainType,
}
#[derive(Debug, Clone)]
pub enum ChainType {
Merge(Mapping),
Script(String),
}

View File

@ -1,329 +0,0 @@
use super::prfitem::PrfItem;
use super::ChainItem;
use crate::utils::{config, dirs, help};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::collections::HashMap;
use std::{fs, io::Write};
///
/// ## Profiles Config
///
/// Define the `profiles.yaml` schema
///
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct Profiles {
/// same as PrfConfig.current
current: Option<String>,
/// same as PrfConfig.chain
chain: Option<Vec<String>>,
/// record valid fields for clash
valid: Option<Vec<String>>,
/// profile list
items: Option<Vec<PrfItem>>,
}
macro_rules! patch {
($lv: expr, $rv: expr, $key: tt) => {
if ($rv.$key).is_some() {
$lv.$key = $rv.$key;
}
};
}
impl Profiles {
pub fn new() -> Self {
Profiles::read_file()
}
/// read the config from the file
pub fn read_file() -> Self {
let mut profiles = config::read_yaml::<Self>(dirs::profiles_path());
if profiles.items.is_none() {
profiles.items = Some(vec![]);
}
// compatiable with the old old old version
profiles.items.as_mut().map(|items| {
for mut item in items.iter_mut() {
if item.uid.is_none() {
item.uid = Some(help::get_uid("d"));
}
}
});
profiles
}
/// save the config to the file
pub fn save_file(&self) -> Result<()> {
config::save_yaml(
dirs::profiles_path(),
self,
Some("# Profiles Config for Clash Verge\n\n"),
)
}
/// get the current uid
pub fn get_current(&self) -> Option<String> {
self.current.clone()
}
/// only change the main to the target id
pub fn put_current(&mut self, uid: String) -> Result<()> {
if self.items.is_none() {
self.items = Some(vec![]);
}
let items = self.items.as_ref().unwrap();
let some_uid = Some(uid.clone());
if items.iter().find(|&each| each.uid == some_uid).is_some() {
self.current = some_uid;
return self.save_file();
}
bail!("invalid uid \"{uid}\"");
}
/// just change the `chain`
pub fn put_chain(&mut self, chain: Option<Vec<String>>) -> Result<()> {
self.chain = chain;
self.save_file()
}
/// just change the `field`
pub fn put_valid(&mut self, valid: Option<Vec<String>>) -> Result<()> {
self.valid = valid;
self.save_file()
}
/// get items ref
pub fn get_items(&self) -> Option<&Vec<PrfItem>> {
self.items.as_ref()
}
/// find the item by the uid
pub fn get_item(&self, uid: &String) -> Result<&PrfItem> {
if self.items.is_some() {
let items = self.items.as_ref().unwrap();
let some_uid = Some(uid.clone());
for each in items.iter() {
if each.uid == some_uid {
return Ok(each);
}
}
}
bail!("failed to get the profile item \"uid:{uid}\"");
}
/// append new item
/// if the file_data is some
/// then should save the data to file
pub fn append_item(&mut self, mut item: PrfItem) -> Result<()> {
if item.uid.is_none() {
bail!("the uid should not be null");
}
// save the file data
// move the field value after save
if let Some(file_data) = item.file_data.take() {
if item.file.is_none() {
bail!("the file should not be null");
}
let file = item.file.clone().unwrap();
let path = dirs::app_profiles_dir().join(&file);
fs::File::create(path)
.context(format!("failed to create file \"{}\"", file))?
.write(file_data.as_bytes())
.context(format!("failed to write to file \"{}\"", file))?;
}
if self.items.is_none() {
self.items = Some(vec![]);
}
self.items.as_mut().map(|items| items.push(item));
self.save_file()
}
/// update the item value
pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
let mut items = self.items.take().unwrap_or(vec![]);
for mut each in items.iter_mut() {
if each.uid == Some(uid.clone()) {
patch!(each, item, itype);
patch!(each, item, name);
patch!(each, item, desc);
patch!(each, item, file);
patch!(each, item, url);
patch!(each, item, selected);
patch!(each, item, extra);
patch!(each, item, updated);
patch!(each, item, option);
self.items = Some(items);
return self.save_file();
}
}
self.items = Some(items);
bail!("failed to find the profile item \"uid:{uid}\"")
}
/// be used to update the remote item
/// only patch `updated` `extra` `file_data`
pub fn update_item(&mut self, uid: String, mut item: PrfItem) -> Result<()> {
if self.items.is_none() {
self.items = Some(vec![]);
}
// find the item
let _ = self.get_item(&uid)?;
if let Some(items) = self.items.as_mut() {
let some_uid = Some(uid.clone());
for mut each in items.iter_mut() {
if each.uid == some_uid {
each.extra = item.extra;
each.updated = item.updated;
// save the file data
// move the field value after save
if let Some(file_data) = item.file_data.take() {
let file = each.file.take();
let file =
file.unwrap_or(item.file.take().unwrap_or(format!("{}.yaml", &uid)));
// the file must exists
each.file = Some(file.clone());
let path = dirs::app_profiles_dir().join(&file);
fs::File::create(path)
.context(format!("failed to create file \"{}\"", file))?
.write(file_data.as_bytes())
.context(format!("failed to write to file \"{}\"", file))?;
}
break;
}
}
}
self.save_file()
}
/// delete item
/// if delete the current then return true
pub fn delete_item(&mut self, uid: String) -> Result<bool> {
let current = self.current.as_ref().unwrap_or(&uid);
let current = current.clone();
let mut items = self.items.take().unwrap_or(vec![]);
let mut index = None;
// get the index
for i in 0..items.len() {
if items[i].uid == Some(uid.clone()) {
index = Some(i);
break;
}
}
if let Some(index) = index {
items.remove(index).file.map(|file| {
let path = dirs::app_profiles_dir().join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
}
// delete the original uid
if current == uid {
self.current = match items.len() > 0 {
true => items[0].uid.clone(),
false => None,
};
}
self.items = Some(items);
self.save_file()?;
Ok(current == uid)
}
/// generate the current Mapping data
fn gen_current(&self) -> Result<Mapping> {
let config = Mapping::new();
if self.current.is_none() || self.items.is_none() {
return Ok(config);
}
let current = self.current.clone().unwrap();
for item in self.items.as_ref().unwrap().iter() {
if item.uid == Some(current.clone()) {
let file_path = match item.file.clone() {
Some(file) => dirs::app_profiles_dir().join(file),
None => bail!("failed to get the file field"),
};
if !file_path.exists() {
bail!("failed to read the file \"{}\"", file_path.display());
}
return Ok(config::read_merge_mapping(file_path.clone()));
}
}
bail!("failed to find current profile \"uid:{current}\"");
}
/// generate the data for activate clash config
pub fn gen_activate(&self) -> Result<PrfActivate> {
let current = self.gen_current()?;
let chain = match self.chain.as_ref() {
Some(chain) => chain
.iter()
.filter_map(|uid| self.get_item(uid).ok())
.filter_map(|item| item.to_enhance())
.collect::<Vec<ChainItem>>(),
None => vec![],
};
let valid = self.valid.clone().unwrap_or(vec![]);
Ok(PrfActivate {
current,
chain,
valid,
})
}
}
#[derive(Default, Clone)]
pub struct PrfActivate {
pub current: Mapping,
pub chain: Vec<ChainItem>,
pub valid: Vec<String>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct RuntimeResult {
pub config: Option<Mapping>,
pub config_yaml: Option<String>,
// 记录在配置中包括merge和script生成的出现过的keys
// 这些keys不一定都生效
pub exists_keys: Vec<String>,
pub chain_logs: HashMap<String, Vec<(String, String)>>,
}

View File

@ -1,136 +0,0 @@
use crate::utils::{config, dirs};
use anyhow::Result;
use serde::{Deserialize, Serialize};
/// ### `verge.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct Verge {
/// app listening port
/// for app singleton
pub app_singleton_port: Option<u16>,
// i18n
pub language: Option<String>,
/// `light` or `dark` or `system`
pub theme_mode: Option<String>,
/// enable blur mode
/// maybe be able to set the alpha
pub theme_blur: Option<bool>,
/// enable traffic graph default is true
pub traffic_graph: Option<bool>,
/// clash tun mode
pub enable_tun_mode: Option<bool>,
/// windows service mode
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_service_mode: Option<bool>,
/// can the app auto startup
pub enable_auto_launch: Option<bool>,
/// not show the window on launch
pub enable_silent_start: Option<bool>,
/// set system proxy
pub enable_system_proxy: Option<bool>,
/// enable proxy guard
pub enable_proxy_guard: Option<bool>,
/// set system proxy bypass
pub system_proxy_bypass: Option<String>,
/// proxy guard duration
pub proxy_guard_duration: Option<u64>,
/// theme setting
pub theme_setting: Option<VergeTheme>,
/// web ui list
pub web_ui_list: Option<Vec<String>>,
/// clash core path
#[serde(skip_serializing_if = "Option::is_none")]
pub clash_core: Option<String>,
/// hotkey map
/// format: {func},{key}
pub hotkeys: Option<Vec<String>>,
/// 切换代理时自动关闭连接
pub auto_close_connection: Option<bool>,
/// 默认的延迟测试连接
pub default_latency_test: Option<String>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct VergeTheme {
pub primary_color: Option<String>,
pub secondary_color: Option<String>,
pub primary_text: Option<String>,
pub secondary_text: Option<String>,
pub info_color: Option<String>,
pub error_color: Option<String>,
pub warning_color: Option<String>,
pub success_color: Option<String>,
pub font_family: Option<String>,
pub css_injection: Option<String>,
}
impl Verge {
pub fn new() -> Self {
config::read_yaml::<Verge>(dirs::verge_path())
}
/// Save Verge App Config
pub fn save_file(&self) -> Result<()> {
config::save_yaml(
dirs::verge_path(),
self,
Some("# The Config for Clash Verge App\n\n"),
)
}
/// patch verge config
/// only save to file
pub fn patch_config(&mut self, patch: Verge) -> Result<()> {
macro_rules! patch {
($key: tt) => {
if patch.$key.is_some() {
self.$key = patch.$key;
}
};
}
patch!(language);
patch!(theme_mode);
patch!(theme_blur);
patch!(traffic_graph);
patch!(enable_tun_mode);
patch!(enable_service_mode);
patch!(enable_auto_launch);
patch!(enable_silent_start);
patch!(enable_system_proxy);
patch!(enable_proxy_guard);
patch!(system_proxy_bypass);
patch!(proxy_guard_duration);
patch!(theme_setting);
patch!(web_ui_list);
patch!(clash_core);
patch!(hotkeys);
patch!(auto_close_connection);
patch!(default_latency_test);
self.save_file()
}
}

View File

@ -1,7 +1,7 @@
use crate::config::*;
use crate::core::*;
use crate::log_err;
use anyhow::Result;
use anyhow::{bail, Result};
use serde_yaml::{Mapping, Value};
// 重启clash
@ -22,11 +22,9 @@ pub fn change_clash_mode(mode: String) {
match clash_api::patch_configs(&mapping).await {
Ok(_) => {
// 更新配置
let mut clash = ClashN::global().config.lock();
clash.insert(Value::from("mode"), mode.into());
drop(clash);
Config::clash().data().patch_config(mapping);
if let Ok(_) = ClashN::global().save_config() {
if let Ok(_) = Config::clash().data().save_config() {
handle::Handle::refresh_clash();
log_err!(handle::Handle::update_systray_part());
}
@ -39,163 +37,199 @@ pub fn change_clash_mode(mode: String) {
}
// 切换系统代理
pub fn toggle_system_proxy() -> Result<()> {
let enable = {
let verge = VergeN::global().config.lock();
verge.enable_system_proxy.clone().unwrap_or(false)
};
patch_verge(IVerge {
enable_system_proxy: Some(!enable),
..IVerge::default()
})?;
handle::Handle::refresh_verge();
Ok(())
}
pub fn toggle_system_proxy() {
let enable = Config::verge().draft().enable_system_proxy.clone();
let enable = enable.unwrap_or(false);
// 打开系统代理
pub fn enable_system_proxy() -> Result<()> {
patch_verge(IVerge {
enable_system_proxy: Some(true),
..IVerge::default()
})?;
handle::Handle::refresh_verge();
Ok(())
}
// 关闭系统代理
pub fn disable_system_proxy() -> Result<()> {
patch_verge(IVerge {
enable_system_proxy: Some(false),
..IVerge::default()
})?;
handle::Handle::refresh_verge();
Ok(())
}
// 切换tun模式
pub fn toggle_tun_mode() -> Result<()> {
let enable = {
let verge = VergeN::global().config.lock();
verge.enable_tun_mode.clone().unwrap_or(false)
};
patch_verge(IVerge {
enable_tun_mode: Some(!enable),
..IVerge::default()
})?;
handle::Handle::refresh_verge();
Ok(())
}
// 打开tun模式
pub fn enable_tun_mode() -> Result<()> {
patch_verge(IVerge {
enable_tun_mode: Some(true),
..IVerge::default()
})?;
handle::Handle::refresh_verge();
Ok(())
}
// 关闭tun模式
pub fn disable_tun_mode() -> Result<()> {
patch_verge(IVerge {
enable_tun_mode: Some(false),
..IVerge::default()
})?;
handle::Handle::refresh_verge();
Ok(())
}
/// 修改clash的配置
pub fn patch_clash(patch: Mapping) -> Result<()> {
let patch_cloned = patch.clone();
let clash_mode = patch.get("mode").is_some();
let mixed_port = patch.get("mixed-port").is_some();
let external = patch.get("external-controller").is_some();
let secret = patch.get("secret").is_some();
// 更新info信息
if mixed_port || external || secret {
let mut tmp_config = { ClashN::global().config.lock().clone() };
for (key, value) in patch.into_iter() {
tmp_config.insert(key, value);
}
let old_info = ClashN::global().patch_info(ClashInfoN::from(&tmp_config))?;
if let Err(err) = CoreManager::global().run_core() {
// 恢复旧值
ClashN::global().patch_info(old_info)?;
return Err(err);
}
}
// 存好再搞
ClashN::global().patch_config(patch_cloned)?;
// 激活配置
tauri::async_runtime::spawn(async move {
match handle_activate().await {
Ok(_) => {
// 更新系统代理
if mixed_port {
log_err!(sysopt::Sysopt::global().init_sysproxy());
}
if clash_mode {
log_err!(handle::Handle::update_systray_part());
}
}
match patch_verge(IVerge {
enable_system_proxy: Some(!enable),
..IVerge::default()
})
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
Ok(())
}
// 打开系统代理
pub fn enable_system_proxy() {
tauri::async_runtime::spawn(async {
match patch_verge(IVerge {
enable_system_proxy: Some(true),
..IVerge::default()
})
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
// 关闭系统代理
pub fn disable_system_proxy() {
tauri::async_runtime::spawn(async {
match patch_verge(IVerge {
enable_system_proxy: Some(false),
..IVerge::default()
})
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
// 切换tun模式
pub fn toggle_tun_mode() {
let enable = Config::verge().data().enable_tun_mode.clone();
let enable = enable.unwrap_or(false);
tauri::async_runtime::spawn(async move {
match patch_verge(IVerge {
enable_tun_mode: Some(!enable),
..IVerge::default()
})
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
// 打开tun模式
pub fn enable_tun_mode() {
tauri::async_runtime::spawn(async {
match patch_verge(IVerge {
enable_tun_mode: Some(true),
..IVerge::default()
})
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
// 关闭tun模式
pub fn disable_tun_mode() {
tauri::async_runtime::spawn(async {
match patch_verge(IVerge {
enable_tun_mode: Some(false),
..IVerge::default()
})
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
/// 修改clash的配置
pub async fn patch_clash(patch: Mapping) -> Result<()> {
Config::clash().draft().patch_config(patch.clone());
match {
let mixed_port = patch.get("mixed-port");
if mixed_port.is_some() {
let changed = mixed_port != Config::clash().data().0.get("mixed-port");
// 检查端口占用
if changed {
if let Some(port) = mixed_port.clone().unwrap().as_u64() {
if !port_scanner::local_port_available(port as u16) {
Config::clash().discard();
bail!("the port not available");
}
}
}
};
// 激活配置
handle_activate().await?;
// 更新系统代理
if mixed_port.is_some() {
log_err!(sysopt::Sysopt::global().init_sysproxy());
}
if patch.get("mode").is_some() {
log_err!(handle::Handle::update_systray_part());
}
<Result<()>>::Ok(())
} {
Ok(()) => {
Config::clash().apply();
Config::clash().data().save_config()?;
Ok(())
}
Err(err) => {
Config::clash().discard();
Err(err)
}
}
}
/// 修改verge的配置
/// 一般都是一个个的修改
pub fn patch_verge(patch: IVerge) -> Result<()> {
VergeN::global().patch_config(patch.clone())?;
pub async fn patch_verge(patch: IVerge) -> Result<()> {
Config::verge().draft().patch_config(patch.clone());
let tun_mode = patch.enable_tun_mode;
let auto_launch = patch.enable_auto_launch;
let system_proxy = patch.enable_system_proxy;
let proxy_bypass = patch.system_proxy_bypass;
let proxy_guard = patch.enable_proxy_guard;
let language = patch.language;
#[cfg(target_os = "windows")]
{}
match {
#[cfg(target_os = "windows")]
{}
if tun_mode.is_some() {
tauri::async_runtime::spawn(async {
log_err!(handle_activate().await);
});
}
if tun_mode.is_some() {
handle_activate().await?;
}
if auto_launch.is_some() {
sysopt::Sysopt::global().update_launch()?;
}
if system_proxy.is_some() || proxy_bypass.is_some() {
sysopt::Sysopt::global().update_sysproxy()?;
sysopt::Sysopt::global().guard_proxy();
}
if proxy_guard.unwrap_or(false) {
sysopt::Sysopt::global().guard_proxy();
}
if auto_launch.is_some() {
sysopt::Sysopt::global().update_launch()?;
}
if system_proxy.is_some() || proxy_bypass.is_some() {
sysopt::Sysopt::global().update_sysproxy()?;
sysopt::Sysopt::global().guard_proxy();
}
if language.is_some() {
handle::Handle::update_systray()?;
} else if system_proxy.or(tun_mode).is_some() {
handle::Handle::update_systray_part()?;
}
if let Some(true) = patch.enable_proxy_guard {
sysopt::Sysopt::global().guard_proxy();
}
if patch.hotkeys.is_some() {
hotkey::Hotkey::global().update(patch.hotkeys.unwrap())?;
}
if let Some(hotkeys) = patch.hotkeys {
hotkey::Hotkey::global().update(hotkeys)?;
}
Ok(())
if language.is_some() {
handle::Handle::update_systray()?;
} else if system_proxy.or(tun_mode).is_some() {
handle::Handle::update_systray_part()?;
}
<Result<()>>::Ok(())
} {
Ok(()) => {
Config::verge().apply();
Config::verge().data().save_file()?;
Ok(())
}
Err(err) => {
Config::verge().discard();
Err(err)
}
}
}
/// 激活配置
@ -216,5 +250,38 @@ pub async fn handle_activate() -> Result<()> {
/// 更新某个profile
/// 如果更新当前配置就激活配置
pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()> {
let url_opt = {
let profiles = Config::profiles();
let profiles = profiles.latest();
let item = profiles.get_item(&uid)?;
let is_remote = item.itype.as_ref().map_or(false, |s| s == "remote");
if !is_remote {
None // 直接更新
} else if item.url.is_none() {
bail!("failed to get the profile item url");
} else {
Some((item.url.clone().unwrap(), item.option.clone()))
}
};
let should_update = match url_opt {
Some((url, opt)) => {
let merged_opt = PrfOption::merge(opt, option);
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
let profiles = Config::profiles();
let mut profiles = profiles.latest();
profiles.update_item(uid.clone(), item)?;
Some(uid) == profiles.get_current()
}
None => true,
};
if should_update {
handle_activate().await?;
}
Ok(())
}

View File

@ -6,7 +6,6 @@
mod cmds;
mod config;
mod core;
// mod data;
mod enhance;
mod feat;
mod utils;

View File

@ -1,5 +1,6 @@
use crate::config::Config;
use crate::log_err;
use crate::{config::VergeN, core::*, utils::init, utils::server};
use crate::{core::*, utils::init, utils::server};
use tauri::{App, AppHandle, Manager};
/// handle something when start app
@ -19,10 +20,7 @@ pub fn resolve_setup(app: &mut App) {
log_err!(tray::Tray::update_systray(&app.app_handle()));
let silent_start = {
let verge = VergeN::global().config.lock();
verge.enable_silent_start.clone()
};
let silent_start = { Config::verge().data().enable_silent_start.clone() };
if !silent_start.unwrap_or(false) {
create_window(&app.app_handle());
}

View File

@ -1,14 +1,14 @@
extern crate warp;
use super::resolve;
use crate::config::VergeN;
use crate::config::IVerge;
use port_scanner::local_port_available;
use tauri::AppHandle;
use warp::Filter;
/// check whether there is already exists
pub fn check_singleton() -> Result<(), ()> {
let port = VergeN::get_singleton_port();
let port = IVerge::get_singleton_port();
if !local_port_available(port) {
tauri::async_runtime::block_on(async {
@ -25,7 +25,7 @@ pub fn check_singleton() -> Result<(), ()> {
/// maybe it can be used as pac server later
pub fn embed_server(app_handle: AppHandle) {
let app_handle = app_handle.clone();
let port = VergeN::get_singleton_port();
let port = IVerge::get_singleton_port();
tauri::async_runtime::spawn(async move {
let commands = warp::path!("commands" / "visible").map(move || {