feat: manage clash config

This commit is contained in:
GyDi 2022-01-21 02:31:44 +08:00
parent 0bd29d71be
commit d49fd37656
No known key found for this signature in database
GPG Key ID: 1C95E0D3467B3084
6 changed files with 122 additions and 85 deletions

View File

@ -1,12 +1,7 @@
use crate::{ use crate::{
core::{ClashInfo, ProfileItem, ProfilesConfig, VergeConfig}, core::{ClashInfo, ProfileItem, ProfilesConfig, VergeConfig},
states::{ClashState, ProfilesState, VergeState}, states::{ClashState, ProfilesState, VergeState},
utils::{ utils::{dirs::app_home_dir, fetch::fetch_profile, sysopt::SysProxyConfig},
config::{read_clash, save_clash},
dirs::app_home_dir,
fetch::fetch_profile,
sysopt::SysProxyConfig,
},
}; };
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::process::Command; use std::process::Command;
@ -82,7 +77,7 @@ pub async fn update_profile(
let current = profiles.current.clone().unwrap_or(0); let current = profiles.current.clone().unwrap_or(0);
if current == index { if current == index {
let clash = clash.0.lock().unwrap(); let clash = clash.0.lock().unwrap();
profiles.activate(clash.info.clone()) profiles.activate(&clash)
} else { } else {
Ok(()) Ok(())
} }
@ -105,7 +100,7 @@ pub fn select_profile(
match profiles.put_current(index) { match profiles.put_current(index) {
Ok(()) => { Ok(()) => {
let clash = clash.0.lock().unwrap(); let clash = clash.0.lock().unwrap();
profiles.activate(clash.info.clone()) profiles.activate(&clash)
} }
Err(err) => Err(err), Err(err) => Err(err),
} }
@ -123,7 +118,7 @@ pub fn delete_profile(
Ok(change) => match change { Ok(change) => match change {
true => { true => {
let clash = clash_state.0.lock().unwrap(); let clash = clash_state.0.lock().unwrap();
profiles.activate(clash.info.clone()) profiles.activate(&clash)
} }
false => Ok(()), false => Ok(()),
}, },
@ -179,18 +174,10 @@ pub fn restart_sidecar(
profiles_state: State<'_, ProfilesState>, profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> { ) -> Result<(), String> {
let mut clash = clash_state.0.lock().unwrap(); let mut clash = clash_state.0.lock().unwrap();
let mut profiles = profiles_state.0.lock().unwrap();
match clash.restart_sidecar() { match clash.restart_sidecar(&mut profiles) {
Ok(_) => { Ok(_) => Ok(()),
let profiles = profiles_state.0.lock().unwrap();
match profiles.activate(clash.info.clone()) {
Ok(()) => Ok(()),
Err(err) => {
log::error!("{}", err);
Err(err)
}
}
}
Err(err) => { Err(err) => {
log::error!("{}", err); log::error!("{}", err);
Err(err) Err(err)
@ -203,26 +190,23 @@ pub fn restart_sidecar(
#[tauri::command] #[tauri::command]
pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result<ClashInfo, String> { pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result<ClashInfo, String> {
match clash_state.0.lock() { 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()), Err(_) => Err("failed to get clash lock".into()),
} }
} }
/// todo: need refactor
/// update the clash core config /// update the clash core config
/// after putting the change to the clash core /// after putting the change to the clash core
/// then we should save the latest config /// then we should save the latest config
#[tauri::command] #[tauri::command]
pub fn patch_clash_config(payload: Mapping) -> Result<(), String> { pub fn patch_clash_config(
let mut config = read_clash(); payload: Mapping,
for (key, value) in payload.iter() { clash_state: State<'_, ClashState>,
if config.contains_key(key) { profiles_state: State<'_, ProfilesState>,
config[key] = value.clone(); ) -> Result<(), String> {
} else { let mut clash = clash_state.0.lock().unwrap();
config.insert(key.clone(), value.clone()); let mut profiles = profiles_state.0.lock().unwrap();
} clash.patch_config(payload, &mut profiles)
}
save_clash(&config)
} }
/// get the system proxy /// get the system proxy

View File

@ -1,3 +1,4 @@
use super::ProfilesConfig;
use crate::utils::{config, dirs}; use crate::utils::{config, dirs};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Value}; use serde_yaml::{Mapping, Value};
@ -20,6 +21,9 @@ pub struct ClashInfo {
#[derive(Debug)] #[derive(Debug)]
pub struct Clash { pub struct Clash {
/// maintain the clash config
pub config: Mapping,
/// some info /// some info
pub info: ClashInfo, pub info: ClashInfo,
@ -32,17 +36,19 @@ static CLASH_CONFIG: &str = "config.yaml";
// todo: be able to change config field // todo: be able to change config field
impl Clash { impl Clash {
pub fn new() -> Clash { pub fn new() -> Clash {
let config = Clash::get_config();
let info = Clash::get_info(&config);
Clash { Clash {
info: Clash::get_info(), config,
info,
sidecar: None, sidecar: None,
} }
} }
/// parse the clash's config.yaml /// parse the clash's config.yaml
/// get some information /// get some information
fn get_info() -> ClashInfo { fn get_info(clash_config: &Mapping) -> ClashInfo {
let clash_config = config::read_yaml::<Mapping>(dirs::app_home_dir().join(CLASH_CONFIG));
let key_port_1 = Value::String("port".to_string()); let key_port_1 = Value::String("port".to_string());
let key_port_2 = Value::String("mixed-port".to_string()); let key_port_2 = Value::String("mixed-port".to_string());
let key_server = Value::String("external-controller".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 /// run clash sidecar
pub fn run_sidecar(&mut self) -> Result<(), String> { pub fn run_sidecar(&mut self) -> Result<(), String> {
let app_dir = dirs::app_home_dir(); let app_dir = dirs::app_home_dir();
@ -138,10 +138,56 @@ impl Clash {
} }
/// restart clash sidecar /// restart clash sidecar
pub fn restart_sidecar(&mut self) -> Result<(), String> { /// should reactivate profile after restart
self.update_info()?; pub fn restart_sidecar(&mut self, profiles: &mut ProfilesConfig) -> Result<(), String> {
self.update_config();
self.drop_sidecar()?; 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::<Mapping>(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()
} }
} }

View File

@ -1,3 +1,4 @@
use super::{Clash, ClashInfo};
use crate::utils::{config, dirs}; use crate::utils::{config, dirs};
use reqwest::header::HeaderMap; use reqwest::header::HeaderMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -8,8 +9,6 @@ use std::fs::File;
use std::io::Write; use std::io::Write;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use super::ClashInfo;
/// Define the `profiles.yaml` schema /// Define the `profiles.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)] #[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct ProfilesConfig { pub struct ProfilesConfig {
@ -224,7 +223,7 @@ impl ProfilesConfig {
} }
/// activate current profile /// 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); let current = self.current.unwrap_or(0);
match self.items.clone() { match self.items.clone() {
Some(items) => { Some(items) => {
@ -233,11 +232,13 @@ impl ProfilesConfig {
} }
let profile = items[current].clone(); let profile = items[current].clone();
let clash_config = clash.config.clone();
let clash_info = clash.info.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
let mut count = 5; // retry times let mut count = 5; // retry times
let mut err = String::from(""); let mut err = String::from("");
while count > 0 { while count > 0 {
match activate_profile(&profile, &clash_config).await { match activate_profile(&profile, &clash_config, &clash_info).await {
Ok(_) => return, Ok(_) => return,
Err(e) => err = e, Err(e) => err = e,
} }
@ -254,7 +255,11 @@ impl ProfilesConfig {
} }
/// put the profile to clash /// 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 // temp profile's path
let temp_path = temp_dir().join(PROFILE_TEMP); 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); let file_path = dirs::app_home_dir().join("profiles").join(file_name);
if !file_path.exists() { 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::<Mapping>(file_path.clone());
let mut new_config = Mapping::new();
// Only the following fields are allowed: // Only the following fields are allowed:
// proxies/proxy-providers/proxy-groups/rule-providers/rules // proxies/proxy-providers/proxy-groups/rule-providers/rules
let config = config::read_yaml::<Mapping>(file_path.clone()); let valid_keys = vec![
let mut new_config = Mapping::new();
vec![
"proxies", "proxies",
"proxy-providers", "proxy-providers",
"proxy-groups", "proxy-groups",
"rule-providers", "rule-providers",
"rules", "rules",
] ];
.iter() valid_keys.iter().for_each(|key| {
.map(|item| Value::String(item.to_string())) let key = Value::String(key.to_string());
.for_each(|key| { if def_config.contains_key(&key) {
if config.contains_key(&key) { let value = def_config[&key].clone();
let value = 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); 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(); let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse().unwrap()); 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( headers.insert(
"Authorization", "Authorization",
format!("Bearer {}", secret).parse().unwrap(), format!("Bearer {}", secret).parse().unwrap(),

View File

@ -41,14 +41,11 @@ fn main() -> std::io::Result<()> {
} }
"restart_clash" => { "restart_clash" => {
let clash_state = app_handle.state::<states::ClashState>(); let clash_state = app_handle.state::<states::ClashState>();
let profiles_state = app_handle.state::<states::ProfilesState>();
let mut clash = clash_state.0.lock().unwrap(); 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(_) => { Ok(_) => {
let profiles = app_handle.state::<states::ProfilesState>();
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(); let window = app_handle.get_window("main").unwrap();
window.emit("restart_clash", "yes").unwrap(); window.emit("restart_clash", "yes").unwrap();
} }

View File

@ -1,6 +1,4 @@
use crate::utils::dirs;
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use serde_yaml::Mapping;
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
/// read data from yaml as struct T /// read data from yaml as struct T
@ -32,17 +30,3 @@ pub fn save_yaml<T: Serialize>(
Err(_) => Err("can not convert the data to yaml".into()), Err(_) => Err("can not convert the data to yaml".into()),
} }
} }
/// Get Clash Core Config `config.yaml`
pub fn read_clash() -> Mapping {
read_yaml::<Mapping>(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"),
)
}

View File

@ -39,7 +39,7 @@ pub fn resolve_setup(app: &App) {
} }
*profiles = ProfilesConfig::read_file(); *profiles = ProfilesConfig::read_file();
if let Err(err) = profiles.activate(clash.info.clone()) { if let Err(err) = profiles.activate(&clash) {
log::error!("{}", err); log::error!("{}", err);
} }