Files
clash-verge-rev/src-tauri/src/core/clash.rs
T

399 lines
11 KiB
Rust
Raw Normal View History

2022-03-06 14:59:25 +08:00
use super::{PrfEnhancedResult, Profiles, Verge};
2022-03-03 01:52:02 +08:00
use crate::utils::{config, dirs, help};
2022-02-28 01:34:25 +08:00
use anyhow::{bail, Result};
2022-03-01 08:58:47 +08:00
use reqwest::header::HeaderMap;
2021-12-14 15:55:29 +08:00
use serde::{Deserialize, Serialize};
2022-01-07 23:29:20 +08:00
use serde_yaml::{Mapping, Value};
2022-03-03 01:52:02 +08:00
use std::{collections::HashMap, time::Duration};
2022-01-07 23:29:20 +08:00
use tauri::api::process::{Command, CommandChild, CommandEvent};
2022-03-06 15:40:16 +08:00
use tauri::Window;
2022-03-03 01:52:02 +08:00
use tokio::time::sleep;
2021-12-14 15:55:29 +08:00
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
2022-01-07 23:29:20 +08:00
pub struct ClashInfo {
/// clash sidecar status
pub status: String,
2021-12-17 02:15:40 +08:00
/// clash core port
pub port: Option<String>,
2021-12-14 15:55:29 +08:00
/// same as `external-controller`
pub server: Option<String>,
2022-01-07 23:29:20 +08:00
/// clash secret
2021-12-14 15:55:29 +08:00
pub secret: Option<String>,
}
2022-01-07 23:29:20 +08:00
pub struct Clash {
2022-01-21 02:31:44 +08:00
/// maintain the clash config
pub config: Mapping,
2022-01-07 23:29:20 +08:00
/// some info
pub info: ClashInfo,
/// clash sidecar
pub sidecar: Option<CommandChild>,
2022-03-06 15:40:16 +08:00
/// save the main window
pub window: Option<Window>,
2022-01-07 23:29:20 +08:00
}
impl Clash {
pub fn new() -> Clash {
2022-02-28 01:34:25 +08:00
let config = Clash::read_config();
2022-01-21 02:31:44 +08:00
let info = Clash::get_info(&config);
2022-01-20 23:41:08 +08:00
Clash {
2022-01-21 02:31:44 +08:00
config,
info,
2022-01-20 23:41:08 +08:00
sidecar: None,
2022-03-06 15:40:16 +08:00
window: None,
2022-01-20 23:41:08 +08:00
}
}
2022-02-28 01:34:25 +08:00
/// get clash config
fn read_config() -> Mapping {
config::read_yaml::<Mapping>(dirs::clash_path())
}
/// save the clash config
fn save_config(&self) -> Result<()> {
config::save_yaml(
dirs::clash_path(),
&self.config,
Some("# Default Config For Clash Core\n\n"),
)
}
2022-01-20 23:41:08 +08:00
/// parse the clash's config.yaml
/// get some information
2022-01-21 02:31:44 +08:00
fn get_info(clash_config: &Mapping) -> ClashInfo {
2022-01-07 23:29:20 +08:00
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());
let key_secret = Value::String("secret".to_string());
let port = match clash_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()),
_ => None,
},
_ => None,
};
let port = match port {
Some(_) => port,
None => match clash_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()),
_ => None,
},
_ => None,
},
};
let server = match clash_config.get(&key_server) {
Some(value) => match value {
Value::String(val_str) => Some(val_str.clone()),
_ => None,
},
_ => None,
};
let secret = match clash_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,
};
2022-01-20 23:41:08 +08:00
ClashInfo {
status: "init".into(),
port,
server,
secret,
2022-01-07 23:29:20 +08:00
}
}
2022-03-06 15:40:16 +08:00
/// save the main window
pub fn set_window(&mut self, win: Option<Window>) {
self.window = win;
}
2022-01-07 23:29:20 +08:00
/// run clash sidecar
2022-02-28 01:34:25 +08:00
pub fn run_sidecar(&mut self) -> Result<()> {
2022-01-07 23:29:20 +08:00
let app_dir = dirs::app_home_dir();
let app_dir = app_dir.as_os_str().to_str().unwrap();
match Command::new_sidecar("clash") {
Ok(cmd) => match cmd.args(["-d", app_dir]).spawn() {
Ok((mut rx, cmd_child)) => {
self.sidecar = Some(cmd_child);
// clash log
tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await {
match event {
2022-03-02 01:13:31 +08:00
CommandEvent::Stdout(line) => log::info!("[clash]: {}", line),
CommandEvent::Stderr(err) => log::error!("[clash]: {}", err),
2022-01-07 23:29:20 +08:00
_ => {}
}
}
});
Ok(())
}
2022-02-28 01:34:25 +08:00
Err(err) => bail!(err.to_string()),
2022-01-07 23:29:20 +08:00
},
2022-02-28 01:34:25 +08:00
Err(err) => bail!(err.to_string()),
2022-01-07 23:29:20 +08:00
}
}
/// drop clash sidecar
2022-02-28 01:34:25 +08:00
pub fn drop_sidecar(&mut self) -> Result<()> {
2022-01-07 23:29:20 +08:00
if let Some(sidecar) = self.sidecar.take() {
2022-02-28 01:34:25 +08:00
sidecar.kill()?;
2022-01-07 23:29:20 +08:00
}
Ok(())
}
/// restart clash sidecar
2022-01-21 02:31:44 +08:00
/// should reactivate profile after restart
2022-02-28 01:34:25 +08:00
pub fn restart_sidecar(&mut self, profiles: &mut Profiles) -> Result<()> {
2022-01-21 02:31:44 +08:00
self.update_config();
2022-01-07 23:29:20 +08:00
self.drop_sidecar()?;
2022-01-21 02:31:44 +08:00
self.run_sidecar()?;
2022-03-06 15:40:16 +08:00
self.activate(profiles, false)
2022-01-21 02:31:44 +08:00
}
/// update the clash info
pub fn update_config(&mut self) {
2022-02-28 01:34:25 +08:00
self.config = Clash::read_config();
2022-01-21 02:31:44 +08:00
self.info = Clash::get_info(&self.config);
}
/// patch update the clash config
pub fn patch_config(
&mut self,
patch: Mapping,
2022-01-21 02:50:13 +08:00
verge: &mut Verge,
2022-01-21 02:57:15 +08:00
profiles: &mut Profiles,
2022-02-28 01:34:25 +08:00
) -> Result<()> {
2022-01-21 02:31:44 +08:00
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)?;
2022-01-21 02:50:13 +08:00
let port = if value.is_number() {
match value.as_i64().clone() {
Some(num) => Some(format!("{num}")),
None => None,
}
} else {
match value.as_str().clone() {
Some(num) => Some(num.into()),
None => None,
}
};
verge.init_sysproxy(port);
2022-01-21 02:31:44 +08:00
}
2022-03-01 08:58:47 +08:00
self.config.insert(key.clone(), value);
2022-01-21 02:31:44 +08:00
}
self.save_config()
2022-01-07 23:29:20 +08:00
}
2022-02-25 02:09:39 +08:00
/// enable tun mode
/// only revise the config and restart the
2022-02-28 01:34:25 +08:00
pub fn tun_mode(&mut self, enable: bool) -> Result<()> {
2022-02-25 02:09:39 +08:00
let tun_key = Value::String("tun".into());
let tun_val = self.config.get(&tun_key);
let mut new_val = Mapping::new();
if tun_val.is_some() && tun_val.as_ref().unwrap().is_mapping() {
new_val = tun_val.as_ref().unwrap().as_mapping().unwrap().clone();
}
macro_rules! revise {
($map: expr, $key: expr, $val: expr) => {
let ret_key = Value::String($key.into());
if $map.contains_key(&ret_key) {
$map[&ret_key] = $val;
} else {
$map.insert(ret_key, $val);
}
};
}
macro_rules! append {
($map: expr, $key: expr, $val: expr) => {
let ret_key = Value::String($key.into());
if !$map.contains_key(&ret_key) {
$map.insert(ret_key, $val);
}
};
}
revise!(new_val, "enable", Value::from(enable));
append!(new_val, "stack", Value::from("gvisor"));
append!(new_val, "auto-route", Value::from(true));
append!(new_val, "auto-detect-interface", Value::from(true));
revise!(self.config, "tun", Value::from(new_val));
self.save_config()
}
2022-03-01 08:58:47 +08:00
2022-03-06 15:40:16 +08:00
/// activate the profile
/// generate a new profile to the temp_dir
/// then put the path to the clash core
2022-03-03 01:52:02 +08:00
fn _activate(info: ClashInfo, config: Mapping) -> Result<()> {
2022-03-01 08:58:47 +08:00
let temp_path = dirs::profiles_temp_path();
config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
tauri::async_runtime::spawn(async move {
2022-03-10 02:19:06 +08:00
// `external-controller` could be
// "127.0.0.1:9090" or ":9090"
// Todo: maybe it could support single port
2022-03-06 15:40:16 +08:00
let server = info.server.unwrap();
2022-03-10 02:19:06 +08:00
let server = match server.starts_with(":") {
true => format!("http://127.0.0.1{server}/configs"),
false => format!("http://{server}/configs"),
};
2022-03-01 08:58:47 +08:00
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse().unwrap());
if let Some(secret) = info.secret.as_ref() {
let secret = format!("Bearer {}", secret.clone()).parse().unwrap();
headers.insert("Authorization", secret);
}
let mut data = HashMap::new();
data.insert("path", temp_path.as_os_str().to_str().unwrap());
2022-03-06 14:59:25 +08:00
// retry 5 times
2022-03-01 08:58:47 +08:00
for _ in 0..5 {
match reqwest::ClientBuilder::new().no_proxy().build() {
2022-03-06 15:40:16 +08:00
Ok(client) => {
let builder = client.put(&server).headers(headers.clone()).json(&data);
match builder.send().await {
Ok(resp) => {
if resp.status() != 204 {
log::error!("failed to activate clash for status \"{}\"", resp.status());
}
// do not retry
break;
2022-03-06 14:59:25 +08:00
}
2022-03-06 15:40:16 +08:00
Err(err) => log::error!("failed to activate for `{err}`"),
2022-03-06 14:59:25 +08:00
}
2022-03-06 15:40:16 +08:00
}
2022-03-01 08:58:47 +08:00
Err(err) => log::error!("failed to activate for `{err}`"),
}
2022-03-06 14:59:25 +08:00
sleep(Duration::from_millis(500)).await;
2022-03-01 08:58:47 +08:00
}
});
Ok(())
}
2022-03-03 01:52:02 +08:00
2022-03-06 15:40:16 +08:00
/// enhanced profiles mode
/// only change the enhanced profiles
pub fn activate_enhanced(&self, profiles: &Profiles, delay: bool) -> Result<()> {
if self.window.is_none() {
bail!("failed to get the main window");
2022-03-03 01:52:02 +08:00
}
2022-03-06 15:40:16 +08:00
let win = self.window.clone().unwrap();
2022-03-03 01:52:02 +08:00
let event_name = help::get_uid("e");
2022-03-06 14:59:25 +08:00
let event_name = format!("enhanced-cb-{event_name}");
2022-03-03 01:52:02 +08:00
let info = self.info.clone();
let mut config = self.config.clone();
2022-03-03 01:56:47 +08:00
// generate the payload
2022-03-06 14:59:25 +08:00
let payload = profiles.gen_enhanced(event_name.clone())?;
2022-03-03 01:56:47 +08:00
2022-03-03 01:52:02 +08:00
win.once(&event_name, move |event| {
if let Some(result) = event.payload() {
2022-03-06 14:59:25 +08:00
let result: PrfEnhancedResult = serde_json::from_str(result).unwrap();
2022-03-03 01:52:02 +08:00
2022-03-06 14:59:25 +08:00
if let Some(data) = result.data {
2022-03-08 01:45:46 +08:00
// all of these can not be revised by script
// http/https/socks port should be under control
let not_allow: Vec<Value> = vec![
"port",
"socks-port",
"mixed-port",
"allow-lan",
"mode",
"external-controller",
"secret",
"log-level",
]
.iter()
.map(|&i| Value::from(i))
.collect();
2022-03-06 14:59:25 +08:00
for (key, value) in data.into_iter() {
2022-03-08 01:45:46 +08:00
if not_allow.iter().find(|&i| i == &key).is_none() {
config.insert(key, value);
}
2022-03-06 14:59:25 +08:00
}
2022-03-08 01:45:46 +08:00
2022-03-06 14:59:25 +08:00
Self::_activate(info, config).unwrap();
2022-03-03 01:52:02 +08:00
}
2022-03-06 14:59:25 +08:00
log::info!("profile enhanced status {}", result.status);
result.error.map(|error| log::error!("{error}"));
2022-03-03 01:52:02 +08:00
}
});
tauri::async_runtime::spawn(async move {
2022-03-06 14:59:25 +08:00
// wait the window setup during resolve app
if delay {
sleep(Duration::from_secs(2)).await;
}
2022-03-03 01:52:02 +08:00
win.emit("script-handler", payload).unwrap();
});
Ok(())
}
2022-03-06 15:40:16 +08:00
/// activate the profile
/// auto activate enhanced profile
pub fn activate(&self, profiles: &Profiles, delay: bool) -> Result<()> {
let gen_map = profiles.gen_activate()?;
let info = self.info.clone();
let mut config = self.config.clone();
for (key, value) in gen_map.into_iter() {
config.insert(key, value);
}
Self::_activate(info, config)?;
self.activate_enhanced(profiles, delay)
}
2022-01-07 23:29:20 +08:00
}
impl Default for Clash {
fn default() -> Self {
Clash::new()
}
}
impl Drop for Clash {
fn drop(&mut self) {
if let Err(err) = self.drop_sidecar() {
2022-02-28 01:34:25 +08:00
log::error!("{err}");
2022-01-07 23:29:20 +08:00
}
}
}