From d49fd37656f611bfab2fbb17d9a46eebe8c78fdc Mon Sep 17 00:00:00 2001 From: GyDi Date: Fri, 21 Jan 2022 02:31:44 +0800 Subject: [PATCH] feat: manage clash config --- src-tauri/src/cmds.rs | 48 ++++++++--------------- src-tauri/src/core/clash.rs | 72 ++++++++++++++++++++++++++++------ src-tauri/src/core/profiles.rs | 60 ++++++++++++++++++++-------- src-tauri/src/main.rs | 9 ++--- src-tauri/src/utils/config.rs | 16 -------- src-tauri/src/utils/resolve.rs | 2 +- 6 files changed, 122 insertions(+), 85 deletions(-) diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 4ff20db..1f13a7c 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -1,12 +1,7 @@ use crate::{ core::{ClashInfo, ProfileItem, ProfilesConfig, VergeConfig}, states::{ClashState, ProfilesState, VergeState}, - utils::{ - config::{read_clash, save_clash}, - dirs::app_home_dir, - fetch::fetch_profile, - sysopt::SysProxyConfig, - }, + utils::{dirs::app_home_dir, fetch::fetch_profile, sysopt::SysProxyConfig}, }; use serde_yaml::Mapping; use std::process::Command; @@ -82,7 +77,7 @@ pub async fn update_profile( let current = profiles.current.clone().unwrap_or(0); if current == index { let clash = clash.0.lock().unwrap(); - profiles.activate(clash.info.clone()) + profiles.activate(&clash) } else { Ok(()) } @@ -105,7 +100,7 @@ pub fn select_profile( match profiles.put_current(index) { Ok(()) => { let clash = clash.0.lock().unwrap(); - profiles.activate(clash.info.clone()) + profiles.activate(&clash) } Err(err) => Err(err), } @@ -123,7 +118,7 @@ pub fn delete_profile( Ok(change) => match change { true => { let clash = clash_state.0.lock().unwrap(); - profiles.activate(clash.info.clone()) + profiles.activate(&clash) } false => Ok(()), }, @@ -179,18 +174,10 @@ pub fn restart_sidecar( profiles_state: State<'_, ProfilesState>, ) -> Result<(), String> { let mut clash = clash_state.0.lock().unwrap(); + let mut profiles = profiles_state.0.lock().unwrap(); - match clash.restart_sidecar() { - Ok(_) => { - let profiles = profiles_state.0.lock().unwrap(); - match profiles.activate(clash.info.clone()) { - Ok(()) => Ok(()), - Err(err) => { - log::error!("{}", err); - Err(err) - } - } - } + match clash.restart_sidecar(&mut profiles) { + Ok(_) => Ok(()), Err(err) => { log::error!("{}", err); Err(err) @@ -203,26 +190,23 @@ pub fn restart_sidecar( #[tauri::command] pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result { match clash_state.0.lock() { - Ok(arc) => Ok(arc.info.clone()), + Ok(clash) => Ok(clash.info.clone()), Err(_) => Err("failed to get clash lock".into()), } } -/// todo: need refactor /// update the clash core config /// after putting the change to the clash core /// then we should save the latest config #[tauri::command] -pub fn patch_clash_config(payload: Mapping) -> Result<(), String> { - let mut config = read_clash(); - for (key, value) in payload.iter() { - if config.contains_key(key) { - config[key] = value.clone(); - } else { - config.insert(key.clone(), value.clone()); - } - } - save_clash(&config) +pub fn patch_clash_config( + payload: Mapping, + clash_state: State<'_, ClashState>, + profiles_state: State<'_, ProfilesState>, +) -> Result<(), String> { + let mut clash = clash_state.0.lock().unwrap(); + let mut profiles = profiles_state.0.lock().unwrap(); + clash.patch_config(payload, &mut profiles) } /// get the system proxy diff --git a/src-tauri/src/core/clash.rs b/src-tauri/src/core/clash.rs index e281b04..e212cbd 100644 --- a/src-tauri/src/core/clash.rs +++ b/src-tauri/src/core/clash.rs @@ -1,3 +1,4 @@ +use super::ProfilesConfig; use crate::utils::{config, dirs}; use serde::{Deserialize, Serialize}; use serde_yaml::{Mapping, Value}; @@ -20,6 +21,9 @@ pub struct ClashInfo { #[derive(Debug)] pub struct Clash { + /// maintain the clash config + pub config: Mapping, + /// some info pub info: ClashInfo, @@ -32,17 +36,19 @@ static CLASH_CONFIG: &str = "config.yaml"; // todo: be able to change config field impl Clash { pub fn new() -> Clash { + let config = Clash::get_config(); + let info = Clash::get_info(&config); + Clash { - info: Clash::get_info(), + config, + info, sidecar: None, } } /// parse the clash's config.yaml /// get some information - fn get_info() -> ClashInfo { - let clash_config = config::read_yaml::(dirs::app_home_dir().join(CLASH_CONFIG)); - + fn get_info(clash_config: &Mapping) -> ClashInfo { let key_port_1 = Value::String("port".to_string()); let key_port_2 = Value::String("mixed-port".to_string()); let key_server = Value::String("external-controller".to_string()); @@ -93,12 +99,6 @@ impl Clash { } } - /// update the clash info - pub fn update_info(&mut self) -> Result<(), String> { - self.info = Clash::get_info(); - Ok(()) - } - /// run clash sidecar pub fn run_sidecar(&mut self) -> Result<(), String> { let app_dir = dirs::app_home_dir(); @@ -138,10 +138,56 @@ impl Clash { } /// restart clash sidecar - pub fn restart_sidecar(&mut self) -> Result<(), String> { - self.update_info()?; + /// should reactivate profile after restart + pub fn restart_sidecar(&mut self, profiles: &mut ProfilesConfig) -> Result<(), String> { + self.update_config(); self.drop_sidecar()?; - self.run_sidecar() + self.run_sidecar()?; + profiles.activate(&self) + } + + /// update the clash info + pub fn update_config(&mut self) { + self.config = Clash::get_config(); + self.info = Clash::get_info(&self.config); + } + + /// get clash config + fn get_config() -> Mapping { + config::read_yaml::(dirs::app_home_dir().join(CLASH_CONFIG)) + } + + /// save the clash config + fn save_config(&self) -> Result<(), String> { + config::save_yaml( + dirs::app_home_dir().join(CLASH_CONFIG), + &self.config, + Some("# Default Config For Clash Core\n\n"), + ) + } + + /// patch update the clash config + pub fn patch_config( + &mut self, + patch: Mapping, + profiles: &mut ProfilesConfig, + ) -> Result<(), String> { + for (key, value) in patch.iter() { + let value = value.clone(); + let key_str = key.as_str().clone().unwrap_or(""); + + // restart the clash + if key_str == "mixed-port" { + self.restart_sidecar(profiles)?; + } + + if self.config.contains_key(key) { + self.config[key] = value.clone(); + } else { + self.config.insert(key.clone(), value.clone()); + } + } + self.save_config() } } diff --git a/src-tauri/src/core/profiles.rs b/src-tauri/src/core/profiles.rs index 1581624..386d6b7 100644 --- a/src-tauri/src/core/profiles.rs +++ b/src-tauri/src/core/profiles.rs @@ -1,3 +1,4 @@ +use super::{Clash, ClashInfo}; use crate::utils::{config, dirs}; use reqwest::header::HeaderMap; use serde::{Deserialize, Serialize}; @@ -8,8 +9,6 @@ use std::fs::File; use std::io::Write; use std::time::{SystemTime, UNIX_EPOCH}; -use super::ClashInfo; - /// Define the `profiles.yaml` schema #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct ProfilesConfig { @@ -224,7 +223,7 @@ impl ProfilesConfig { } /// activate current profile - pub fn activate(&self, clash_config: ClashInfo) -> Result<(), String> { + pub fn activate(&self, clash: &Clash) -> Result<(), String> { let current = self.current.unwrap_or(0); match self.items.clone() { Some(items) => { @@ -233,11 +232,13 @@ impl ProfilesConfig { } let profile = items[current].clone(); + let clash_config = clash.config.clone(); + let clash_info = clash.info.clone(); tauri::async_runtime::spawn(async move { let mut count = 5; // retry times let mut err = String::from(""); while count > 0 { - match activate_profile(&profile, &clash_config).await { + match activate_profile(&profile, &clash_config, &clash_info).await { Ok(_) => return, Err(e) => err = e, } @@ -254,7 +255,11 @@ impl ProfilesConfig { } /// put the profile to clash -pub async fn activate_profile(profile_item: &ProfileItem, info: &ClashInfo) -> Result<(), String> { +pub async fn activate_profile( + profile_item: &ProfileItem, + clash_config: &Mapping, + clash_info: &ClashInfo, +) -> Result<(), String> { // temp profile's path let temp_path = temp_dir().join(PROFILE_TEMP); @@ -267,25 +272,46 @@ pub async fn activate_profile(profile_item: &ProfileItem, info: &ClashInfo) -> R let file_path = dirs::app_home_dir().join("profiles").join(file_name); if !file_path.exists() { - return Err(format!("profile `{:?}` not exists", file_path)); + return Err(format!( + "profile `{}` not exists", + file_path.as_os_str().to_str().unwrap() + )); } + // begin to generate the new profile config + let def_config = config::read_yaml::(file_path.clone()); + let mut new_config = Mapping::new(); + // Only the following fields are allowed: // proxies/proxy-providers/proxy-groups/rule-providers/rules - let config = config::read_yaml::(file_path.clone()); - let mut new_config = Mapping::new(); - vec![ + let valid_keys = vec![ "proxies", "proxy-providers", "proxy-groups", "rule-providers", "rules", - ] - .iter() - .map(|item| Value::String(item.to_string())) - .for_each(|key| { - if config.contains_key(&key) { - let value = config[&key].clone(); + ]; + valid_keys.iter().for_each(|key| { + let key = Value::String(key.to_string()); + if def_config.contains_key(&key) { + let value = def_config[&key].clone(); + new_config.insert(key, value); + } + }); + + // add some of the clash `config.yaml` config to it + let valid_keys = vec![ + "mixed-port", + "log-level", + "allow-lan", + "external-controller", + "secret", + "ipv6", + ]; + valid_keys.iter().for_each(|key| { + let key = Value::String(key.to_string()); + if clash_config.contains_key(&key) { + let value = clash_config[&key].clone(); new_config.insert(key, value); } }); @@ -297,12 +323,12 @@ pub async fn activate_profile(profile_item: &ProfileItem, info: &ClashInfo) -> R )? }; - let server = format!("http://{}/configs", info.server.clone().unwrap()); + let server = format!("http://{}/configs", clash_info.server.clone().unwrap()); let mut headers = HeaderMap::new(); headers.insert("Content-Type", "application/json".parse().unwrap()); - if let Some(secret) = info.secret.clone() { + if let Some(secret) = clash_info.secret.clone() { headers.insert( "Authorization", format!("Bearer {}", secret).parse().unwrap(), diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4953f1e..e219f88 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -41,14 +41,11 @@ fn main() -> std::io::Result<()> { } "restart_clash" => { let clash_state = app_handle.state::(); + let profiles_state = app_handle.state::(); let mut clash = clash_state.0.lock().unwrap(); - match clash.restart_sidecar() { + let mut profiles = profiles_state.0.lock().unwrap(); + match clash.restart_sidecar(&mut profiles) { Ok(_) => { - let profiles = app_handle.state::(); - let profiles = profiles.0.lock().unwrap(); - if let Err(err) = profiles.activate(clash.info.clone()) { - log::error!("{}", err); - } let window = app_handle.get_window("main").unwrap(); window.emit("restart_clash", "yes").unwrap(); } diff --git a/src-tauri/src/utils/config.rs b/src-tauri/src/utils/config.rs index 4d8e13e..04a33cc 100644 --- a/src-tauri/src/utils/config.rs +++ b/src-tauri/src/utils/config.rs @@ -1,6 +1,4 @@ -use crate::utils::dirs; use serde::{de::DeserializeOwned, Serialize}; -use serde_yaml::Mapping; use std::{fs, path::PathBuf}; /// read data from yaml as struct T @@ -32,17 +30,3 @@ pub fn save_yaml( Err(_) => Err("can not convert the data to yaml".into()), } } - -/// Get Clash Core Config `config.yaml` -pub fn read_clash() -> Mapping { - read_yaml::(dirs::app_home_dir().join("config.yaml")) -} - -/// Save the clash core Config `config.yaml` -pub fn save_clash(config: &Mapping) -> Result<(), String> { - save_yaml( - dirs::app_home_dir().join("config.yaml"), - config, - Some("# Default Config For Clash Core\n\n"), - ) -} diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 96cb9c4..21b18c1 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -39,7 +39,7 @@ pub fn resolve_setup(app: &App) { } *profiles = ProfilesConfig::read_file(); - if let Err(err) = profiles.activate(clash.info.clone()) { + if let Err(err) = profiles.activate(&clash) { log::error!("{}", err); }